diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..6d50327 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,42 @@ +{ + "permissions": { + "allow": [ + "Bash(git commit -m ':*)", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet build FlyshotReplacement.sln -v minimal 2>&1')", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet test tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj -v minimal 2>&1')", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet test FlyshotReplacement.sln --no-build -v minimal 2>&1')", + "Bash(DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet build FlyshotReplacement.sln --no-restore -v minimal)", + "Bash(DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet test tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj --no-build -v minimal)", + "Bash(python -c \"import json; json.load\\(open\\('.vscode/launch.json'\\)\\); json.load\\(open\\('.vscode/tasks.json'\\)\\); print\\('JSON valid.'\\)\")", + "Bash(python -c \"import json; json.load\\(open\\('.vscode/launch.json', encoding='utf-8'\\)\\); json.load\\(open\\('.vscode/tasks.json', encoding='utf-8'\\)\\); print\\('JSON valid.'\\)\")", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet build FlyshotReplacement.sln --no-restore -v minimal')", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj -v minimal')", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet test tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj -v minimal')", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet build FlyshotReplacement.sln --no-restore -v minimal 2>&1')", + "Bash(taskkill /PID 120812 /F)", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet build flyshot-replacement.sln --no-restore -v minimal 2>&1')", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet build /d/Dev/Codes/rvbust-code/FlyingShotPkg_3.15_VDA/flyshot-replacement/FlyshotReplacement.sln --no-restore -v minimal 2>&1')", + "Bash(capinfos \"20260430.pcap\")", + "Bash(tshark -r \"20260430.pcap\" -T fields -e frame.number -e frame.time_relative -e ip.src -e ip.dst -e udp.srcport -e udp.dstport -e data.len)", + "Bash(tshark -r \"20260430.pcap\" -Y \"udp.port==60015\" -T fields -e frame.number -e frame.time_relative -e ip.src -e udp.srcport -e udp.length -e data.len)", + "Bash(tshark -r \"20260430.pcap\" -Y \"udp.port==60015 and udp.srcport==60015\" -T fields -e frame.number -e frame.time_relative -e udp.length)", + "Bash(tshark -r \"20260430.pcap\" -Y \"udp.port==60015 and udp.dstport==60015\" -T fields -e frame.number -e frame.time_relative -e udp.length)", + "Bash(tshark -r \"20260430.pcap\" -Y \"udp.port==60015\" -T fields -e frame.number -e frame.time_relative -e ip.src -e ip.dst -e udp.srcport -e udp.dstport -e udp.length -e frame.protocols)", + "Bash(tshark -r \"20260430.pcap\" -Y \"udp.port==60015 and frame.number==1\" -V)", + "Bash(tshark -r \"20260430.pcap\" -Y \"udp.port==60015 and udp.dstport!=60015\" -T fields -e frame.number -e frame.time_relative -e ip.dst -e udp.dstport)", + "Bash(tshark -r \"20260430.pcap\" -Y \"udp.dstport==60015\" -T fields -e frame.number -e frame.time_relative -e ip.src -e udp.length)", + "Bash(tshark -r \"20260430.pcap\" -Y \"frj519\" -T fields -e frj519.type -e frj519.seq -e frj519.status -e frj519.j1 -e frj519.j2 -e frj519.j3 -e frj519.j4 -e frj519.j5 -e frj519.j6 -e frj519.j7 -e frj519.j8 -e frj519.j9 -e frj519.timestamp)", + "Bash(tshark -r \"20260430.pcap\" -Y \"frj519\" -T fields -e frj519.type -e frj519.seq -e frj519.status -e frj519.j1 -e frj519.j2 -e frj519.j3 -e frj519.j4 -e frj519.j5 -e frj519.j6 -e frj519.j7 -e frj519.j8 -e frj519.j9 -e frj519.timestamp -e frj519.x -e frj519.y -e frj519.z -e frj519.w -e frj519.p -e frj519.r)", + "Bash(tshark -r \"20260430.pcap\" -Y \"frj519\" -T fields -E header=y)", + "Bash(tshark -G fields)", + "Bash(tshark -r \"20260430.pcap\" -Y \"frj519\" -T fields -e frame.time_relative -e data)", + "Bash(tshark -r \"20260430.pcap\" -Y \"udp.dstport==60015\" -T fields -e frame.number -e frame.time_relative -e data)", + "Bash(tshark -r \"20260430.pcap\" -Y \"udp.port==60015 and udp.srcport==60015\" -T fields -e frame.number -e frame.time_relative -e data)", + "Bash(tshark -r \"20260430.pcap\" --disable-protocol frj519 -Y \"udp.dstport==60015\" -T fields -e frame.number -e frame.time_relative -e data)", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet build src/Flyshot.Runtime.Fanuc/Flyshot.Runtime.Fanuc.csproj --no-restore -v minimal 2>&1 | tail -20')", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet build FlyshotReplacement.sln --no-restore -v minimal 2>&1 | tail -25')", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet build FlyshotReplacement.sln --no-restore -v minimal 2>&1 | tail -15')", + "Bash(/bin/bash -lc 'DOTNET_CLI_HOME=/tmp NUGET_PACKAGES=/tmp/nuget-packages dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --no-restore -v minimal 2>&1 | tail -30')" + ] + } +} diff --git a/.codex b/.codex new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 8a30d25..72a39a3 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,7 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml +Config/Data/* +.dotnet-home/* +codex-dotnet-home/* +.dotnet-sdk8/* diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3793b36 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,67 @@ +{ + // VS Code 启动与调试配置 + // 依赖 C# 扩展(OmniSharp 或 C# Dev Kit)提供 coreclr 调试器。 + // 文档:https://code.visualstudio.com/docs/csharp/debugger-settings + "version": "0.2.0", + "configurations": [ + { + // 标准调试启动:编译并启动 Host,命中断点,浏览器自动打开首页 + "name": ".NET Core Launch (Host)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "run", + "--project", + "${workspaceFolder}/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj", + "--no-launch-profile" + ], + "cwd": "${workspaceFolder}", + "env": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5190" + }, + "stopAtEntry": false, + "console": "internalConsole", + "preLaunchTask": "build", + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)", + "uriFormat": "%s" + } + }, + { + // 热重载调试启动:自动编译、自动重启、断点保留;迭代 Web / 控制器层时首选 + "name": ".NET Core Watch (Host)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj", + "--no-launch-profile" + ], + "cwd": "${workspaceFolder}", + "env": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5190" + }, + "stopAtEntry": false, + "console": "integratedTerminal", + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)", + "uriFormat": "%s" + } + }, + { + // 附加到正在运行的 dotnet 进程(如已手动 `dotnet run` 或 Windows Service 模式) + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..140df5e --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,160 @@ +{ + // VS Code 任务配置 + // 文档:https://code.visualstudio.com/docs/editor/tasks + "version": "2.0.0", + "tasks": [ + { + // 构建整个解决方案,是 launch.json 启动前的默认 preLaunchTask + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/FlyshotReplacement.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-v", + "minimal" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$msCompile", + "presentation": { + "reveal": "silent", + "clear": true + } + }, + { + // 仅构建宿主项目,迭代 Web 层时比整解决方案快 + "label": "build-host", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-v", + "minimal" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + // 还原 NuGet 包,新增引用或克隆后第一次打开时使用 + "label": "restore", + "command": "dotnet", + "type": "process", + "args": [ + "restore", + "${workspaceFolder}/FlyshotReplacement.sln" + ], + "problemMatcher": [] + }, + { + // 清理所有项目的 bin/obj + "label": "clean", + "command": "dotnet", + "type": "process", + "args": [ + "clean", + "${workspaceFolder}/FlyshotReplacement.sln" + ], + "problemMatcher": "$msCompile" + }, + { + // 跑全部测试(领域 + 集成) + "label": "test", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "${workspaceFolder}/FlyshotReplacement.sln", + "--no-restore", + "-v", + "minimal" + ], + "group": { + "kind": "test", + "isDefault": true + }, + "problemMatcher": "$msCompile" + }, + { + // 仅跑领域 / 算法层测试,迭代规划逻辑时使用 + "label": "test-core", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "${workspaceFolder}/tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj", + "-v", + "minimal" + ], + "group": "test", + "problemMatcher": "$msCompile" + }, + { + // 仅跑宿主集成测试,迭代 HTTP / 控制器层时使用 + "label": "test-integration", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "${workspaceFolder}/tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj", + "-v", + "minimal" + ], + "group": "test", + "problemMatcher": "$msCompile" + }, + { + // 启动宿主,供 launch.json 的 watch 配置作为前置任务 + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj", + "--launch-profile", + "http" + ], + "isBackground": true, + "problemMatcher": { + "owner": "dotnet-watch", + "pattern": [ + { + "regexp": "^.*$", + "file": 1, + "location": 2, + "message": 3 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "^.*Watch run started.*$", + "endsPattern": "^.*Application started.*$" + } + } + }, + { + // Release 配置发布到 publish/,用于现场部署包打包 + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj", + "-c", + "Release", + "-o", + "${workspaceFolder}/publish" + ], + "problemMatcher": "$msCompile" + } + ] +} diff --git a/AGENTS.md b/AGENTS.md index 16cbc09..e6ca917 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ - 使用 `C# + .NET 8` - 提供跨平台独立服务端 -- 兼容现有 `50001/TCP+JSON` 上层接入语义 +- 以新的 ASP.NET Core HTTP API 作为唯一上层接口 - 重写轨迹生成、触发时序、FANUC 控制链路和状态监控 - Windows / Linux 都能运行完整服务端 - 只支持当前现场这套组合 @@ -18,6 +18,7 @@ - GUI 桌面程序 - 多机器人同时控制 - 面向多控制柜的通用平台化框架 +- 恢复旧 `50001/TCP+JSON` 网关 ## 2. 代码与资料边界 @@ -37,7 +38,12 @@ flyshot-replacement/ ├─ src/ │ ├─ Flyshot.Server.Host/ + │ ├─ Flyshot.ControllerClientCompat/ + │ ├─ Flyshot.Core.Config/ │ ├─ Flyshot.Core.Domain/ + │ ├─ Flyshot.Core.Planning/ + │ ├─ Flyshot.Core.Triggering/ + │ ├─ Flyshot.Runtime.Fanuc/ │ └─ Flyshot.Runtime.Common/ ├─ tests/ │ ├─ Flyshot.Server.IntegrationTests/ @@ -64,8 +70,10 @@ flyshot-replacement/ - `Flyshot.Core.Triggering` - `TrajectoryDO` 等价时间轴 - `shot_flags / offset_values / addr` 解析 -- `Flyshot.LegacyGateway` - - `50001/TCP+JSON` 兼容接入 +- `Flyshot.ControllerClientCompat` + - HTTP 控制器后端兼容服务 + - 旧 `ControllerClient` 语义适配 + - 不启动 `50001/TCP+JSON` 监听 - `Flyshot.Runtime.Fanuc` - `10010 / 10012 / 60015` - `Flyshot.Web.Status` @@ -84,6 +92,7 @@ flyshot-replacement/ ### 4.2 实现约束 +- 旧 `ControllerClient` 资料只作为接口语义参考;运行时入口以新 HTTP API 为准,不恢复旧 `50001/TCP+JSON` 网关。 - 旧协议兼容以“语义兼容”为主,不追求二进制逐字节一致。 - 轨迹规划必须与底层 Socket / HTTP / Web UI 解耦。 - 领域层不允许引用 ASP.NET Core、Socket、文件系统 API。 @@ -103,6 +112,15 @@ flyshot-replacement/ - 所有静态变量都必须提供 XML 注释。 - 关键代码块必须补充单行注释,说明该段逻辑为什么存在、在做什么,不允许只写空泛注释。 +### 4.5 机器人模型字段约定 + +- 当任务涉及六轴 `velocity / acceleration / jerk` 来源时,默认先查看机器人模型文件中的 `joint.limit`,不要先从抓包、导出轨迹或聊天记录反推。 +- 当前仓库约定:`velocity_eff = velocity_base`,`acceleration_eff = acceleration_base * acc_limit`,`jerk_eff = jerk_base * jerk_limit`。 +- `acc_limit / jerk_limit` 来自运行时 `RobotConfig.json`,它们是全局倍率,不是每轴单独配置。 +- 模型里的 `limit.effort` 目前只能当静态模型字段记录,不能直接当现场真实电流。 +- 如果用户问“电流是不是从这个模型文件提取的”,默认先明确区分:模型里的 `effort` 不等于 J519 反馈里的电机电流。 +- 相关固定表格文档见 `docs/robot-joint-limit-table-20260505.md`。 + ## 5. 构建与验证命令 在当前环境中,推荐使用下面两条命令: @@ -132,9 +150,24 @@ flyshot-replacement/ - `../analysis/ICSP_algorithm_reverse_analysis.md` - `../analysis/CommonMsg_protocol_analysis.md` - `../analysis/J519_stream_motion_analysis.md` +- `../analysis/UTTC_20260428_packet_validation.md` - `../analysis/FANUC_realtime_comm_analysis.md` - `../FlyingShot/FlyingShot/Include/ControllerClient/ControllerClient.h` +### 6.1 父目录资料引用约定 + +- 日常开发、测试和 Codex 会话默认从 `flyshot-replacement/` 根目录启动。 +- 当前仓库内的 `@` 引用默认只覆盖本仓库文件,不要假设它能索引父目录资料。 +- 引用父目录资料时,统一直接写明确路径,优先使用相对路径,例如: + - `../analysis/ICSP_algorithm_reverse_analysis.md` + - `../analysis/ControllerServer_analysis.md` + - `../FlyingShot/FlyingShot/Include/ControllerClient/ControllerClient.h` +- 当路径较长或跨工具复制时,可以使用绝对路径,但在文档和注释中优先保留相对路径写法,便于仓库整体搬迁。 +- 父目录中的 `analysis/`、`FlyingShot/`、`RobotController/`、`RPS/` 默认视为参考资料区,不在这些目录中继续落地新实现。 +- 新实现、测试、兼容层代码、设计文档和运行说明,都应优先写入 `flyshot-replacement/` 内部。 +- 如果父目录资料中的某段结论会长期影响本仓库实现,应在本仓库 `docs/` 中补充归纳说明,并标明来源路径,而不是要求后续开发反复回看聊天记录。 +- 如果需要引用父目录样本文件做测试输入,优先通过只读方式加载;只有在测试需要固化样本且样本已明确收敛时,才复制到本仓库测试数据目录。 + ## 7. 任务推进方式 - `README.md` 中的 Todo 需要随着阶段推进同步更新。 @@ -148,3 +181,13 @@ flyshot-replacement/ - `Flyshot.Server.Host` 已提供最小 `/healthz`。 - 最小集成测试已通过。 - 解决方案构建已通过。 +- 新 HTTP API / HTTP-only `ControllerClientCompat` 已覆盖旧 HTTP 控制器后端的主要兼容语义。 +- `Flyshot.Core.Planning` 已落地 `icsp` 与 `self-adapt-icsp`,并已完成旧系统导出轨迹对齐。 +- `Flyshot.Core.Triggering` 已能从 `shot_flags / offset_values / addr` 生成触发时间轴。 +- `Flyshot.Runtime.Fanuc` 已固化 `10010 / 10012 / 60015` 基础协议帧编解码,`10010` 状态帧以 `j519 协议.pcap` 和 `Rvbust/uttc-20260428/20260428.pcap` 真机抓包确认为 90B。 +- `Flyshot.Runtime.Fanuc` 已将 TCP 10010 的 `pose[6]`、`joint[6]`、`external_axes[3]` 和 `raw_tail_words[4]` 映射为明确状态帧字段,并在状态快照中保留尾部状态字诊断信息。 +- `Rvbust/uttc-20260428` 抓包确认 J519 命令目标为关节角 `deg`,而导出 `JointDetialTraj.txt` 为 `rad`;执行链路必须做单位转换。 +- `Rvbust/uttc-20260428` 抓包确认 `speed_ratio=0.7` 体现为 UDP 下发时间轴约 `1.427730x` 拉伸;本抓包机器人侧 `TCP 10012` 未出现 `0x2207 SetSpeedRatio`,不要把速度缩放只建模成单个机器人命令。J519 实发周期仍为 `t_send = k * 0.008`,原轨迹采样时间为 `t_traj = t_send * speed_ratio`,`UTTC_MS11` 的 `464` 行导出轨迹对应 `1322` 个主运行 J519 包。 +- `Rvbust/uttc-20260428` 抓包确认 `UTTC_MS11` 的 17 个 `shot_flags=true` 对应 17 个 UDP IO 脉冲,`io_keep_cycles=2` 对应约两周期清零。 +- `Flyshot.Runtime.Fanuc` 已具备基础 Socket 客户端、速度倍率/TCP/IO 参数命令和 J519 周期发送链路;稠密轨迹下发已按 `speed_ratio` 推进轨迹时间,J519 闭环状态判断与现场联调仍需补齐。 +- `ExecuteTrajectory` / `ExecuteFlyShotTraj` 已接入 `Planning + Triggering + Runtime`,不再只是兼容层内存赋值。 diff --git a/CLAUDE.md b/CLAUDE.md index 16cbc09..3c5cacf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,8 +64,10 @@ flyshot-replacement/ - `Flyshot.Core.Triggering` - `TrajectoryDO` 等价时间轴 - `shot_flags / offset_values / addr` 解析 -- `Flyshot.LegacyGateway` - - `50001/TCP+JSON` 兼容接入 +- `Flyshot.ControllerClientCompat` + - HTTP 控制器后端兼容服务 + - 旧 `ControllerClient` 语义适配 + - 不启动 `50001/TCP+JSON` 监听 - `Flyshot.Runtime.Fanuc` - `10010 / 10012 / 60015` - `Flyshot.Web.Status` @@ -132,6 +134,7 @@ flyshot-replacement/ - `../analysis/ICSP_algorithm_reverse_analysis.md` - `../analysis/CommonMsg_protocol_analysis.md` - `../analysis/J519_stream_motion_analysis.md` +- `../analysis/UTTC_20260428_packet_validation.md` - `../analysis/FANUC_realtime_comm_analysis.md` - `../FlyingShot/FlyingShot/Include/ControllerClient/ControllerClient.h` @@ -148,3 +151,7 @@ flyshot-replacement/ - `Flyshot.Server.Host` 已提供最小 `/healthz`。 - 最小集成测试已通过。 - 解决方案构建已通过。 +- `10010` 状态帧以 `j519 协议.pcap` 和 `Rvbust/uttc-20260428/20260428.pcap` 真机抓包确认为 90B。 +- `Rvbust/uttc-20260428` 抓包确认 J519 命令目标为关节角 `deg`,而导出 `JointDetialTraj.txt` 为 `rad`;执行链路必须做单位转换。 +- `Rvbust/uttc-20260428` 抓包确认 `speed_ratio=0.7` 体现为 UDP 下发时间轴约 `1.427730x` 拉伸;本抓包机器人侧 `TCP 10012` 未出现 `0x2207 SetSpeedRatio`。J519 实发周期仍为 `t_send = k * 0.008`,原轨迹采样时间为 `t_traj = t_send * speed_ratio`,`UTTC_MS11` 的 `464` 行导出轨迹对应 `1322` 个主运行 J519 包。 +- `Rvbust/uttc-20260428` 抓包确认 `UTTC_MS11` 的 17 个 `shot_flags=true` 对应 17 个 UDP IO 脉冲,`io_keep_cycles=2` 对应约两周期清零。 diff --git a/Config/Models/Expand/LR_Mate_200iD_7L_expand.robot b/Config/Models/Expand/LR_Mate_200iD_7L_expand.robot new file mode 100644 index 0000000..28248e6 Binary files /dev/null and b/Config/Models/Expand/LR_Mate_200iD_7L_expand.robot differ diff --git a/Config/Models/Expand/LR_Mate_200iD_expand.robot b/Config/Models/Expand/LR_Mate_200iD_expand.robot new file mode 100644 index 0000000..6cbeda6 Binary files /dev/null and b/Config/Models/Expand/LR_Mate_200iD_expand.robot differ diff --git a/Config/Models/LR_Mate_200iD.robot b/Config/Models/LR_Mate_200iD.robot new file mode 100644 index 0000000..91e7541 Binary files /dev/null and b/Config/Models/LR_Mate_200iD.robot differ diff --git a/Config/Models/LR_Mate_200iD_7L -.glb b/Config/Models/LR_Mate_200iD_7L -.glb new file mode 100644 index 0000000..21b4c4a Binary files /dev/null and b/Config/Models/LR_Mate_200iD_7L -.glb differ diff --git a/Config/Models/LR_Mate_200iD_7L.json b/Config/Models/LR_Mate_200iD_7L.json new file mode 100644 index 0000000..0c65b17 --- /dev/null +++ b/Config/Models/LR_Mate_200iD_7L.json @@ -0,0 +1,366 @@ +{ + "scenes": [ + { + "extras": { + "rvbust": { + "robotics": { + "bodies": [ + { + "active_manipulator_name": "ManipulatorName", + "controller_info": { + "analog_io": { + "imax": 100, + "imin": 0, + "omax": 100, + "omin": 0 + }, + "digital_io": { + "imax": 100, + "imin": 0, + "omax": 100, + "omin": 0 + }, + "name": "R30iB" + }, + "generic_info": { + "boundary_area": { + "height": 0.8689, + "length": 0.8194, + "pose": [ + 0.103025, + 0.0, + 0.10445, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "type": 2, + "width": 0.2349 + }, + "materials": [ + { + "color": [ + 0.15, + 0.15, + 0.15, + 1.0 + ], + "name": "FANUC_Black", + "texture_filename": "" + }, + { + "color": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "name": "FANUC_Generic", + "texture_filename": "" + }, + { + "color": [ + 0.278, + 0.278, + 0.278, + 1.0 + ], + "name": "FANUC_Grey", + "texture_filename": "" + }, + { + "color": [ + 1.0, + 1.0, + 0.0, + 1.0 + ], + "name": "FANUC_Yellow", + "texture_filename": "" + } + ], + "path_to_image": "./LR_Mate_200iD_7L.png" + }, + "joints": [ + { + "axis": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "child": "Link1", + "curr_position": 0.0, + "home_position": 0.0, + "limit": { + "acceleration": 26.9, + "effort": 0.0, + "jerk": 224.22, + "lower": -2.96, + "upper": 2.96, + "velocity": 6.45 + }, + "name": "Joint1", + "origin": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "parent": "BaseLink", + "type": 2 + }, + { + "axis": [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0 + ], + "child": "Link2", + "curr_position": 0.0, + "home_position": 0.0, + "limit": { + "acceleration": 22.54, + "effort": 0.0, + "jerk": 187.86, + "lower": -1.74, + "upper": 2.52, + "velocity": 5.41 + }, + "name": "Joint2", + "origin": [ + 0.05, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "parent": "Link1", + "type": 2 + }, + { + "axis": [ + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0 + ], + "child": "Link3", + "couple": { + "kin_lower": -1.22, + "kin_upper": 3.71, + "master_joint": "Joint2", + "multiplier": 1.0, + "offset": 0.0, + "poly_boundary": [ + -1.745, + 0.524, + -1.745, + 4.886, + -1.132, + 4.832, + 2.53, + 1.1677, + 2.53067, + -2.02841, + 1.3739, + -2.574281 + ] + }, + "curr_position": 0.0, + "home_position": 0.0, + "limit": { + "acceleration": 29.81, + "effort": 0.0, + "jerk": 248.46, + "lower": -2.59, + "upper": 4.88, + "velocity": 7.15 + }, + "name": "Joint3", + "origin": [ + 0.0, + 0.0, + 0.44, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "parent": "Link2", + "type": 2 + }, + { + "axis": [ + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0 + ], + "child": "Link4", + "curr_position": 0.0, + "home_position": 0.0, + "limit": { + "acceleration": 39.99, + "effort": 0.0, + "jerk": 333.3, + "lower": -3.31, + "upper": 3.31, + "velocity": 9.59 + }, + "name": "Joint4", + "origin": [ + 0.0, + 0.0, + 0.035, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "parent": "Link3", + "type": 2 + }, + { + "axis": [ + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0 + ], + "child": "Link5", + "curr_position": -1.5708, + "home_position": -1.5708, + "limit": { + "acceleration": 39.63, + "effort": 0.0, + "jerk": 330.27, + "lower": -2.18, + "upper": 2.18, + "velocity": 9.51 + }, + "name": "Joint5", + "origin": [ + 0.42, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "parent": "Link4", + "type": 2 + }, + { + "axis": [ + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0 + ], + "child": "Link6", + "curr_position": 0.0, + "home_position": 0.0, + "limit": { + "acceleration": 72.72, + "effort": 0.0, + "jerk": 606.01, + "lower": -6.28, + "upper": 6.28, + "velocity": 17.45 + }, + "name": "Joint6", + "origin": [ + 0.08, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "parent": "Link5", + "type": 2 + }, + { + "axis": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "child": "EffectorLink", + "curr_position": 0.0, + "home_position": 0.0, + "limit": { + "acceleration": 0.0, + "effort": 0.0, + "jerk": 0.0, + "lower": 0.0, + "upper": 0.0, + "velocity": 0.0 + }, + "name": "JointEffector", + "origin": [ + 0.0, + 0.0, + 0.0, + 0.7071067811865475, + 0.0, + 0.7071067811865475, + 0.0 + ], + "parent": "Link6", + "type": 1 + } + ], + "name": "FANUC_LR_Mate_200iD_7L", + "other": { + "base_transformation": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "robot_generic_info": { + "payload": 7.0, + "reach": 0.911, + "repeat": 0.01, + "vendor": "FANUC", + "weight": 27.0 + }, + "rvdf_version": "0.1.0" + } + ] + } + } + } + } + ] +} \ No newline at end of file diff --git a/Config/Models/LR_Mate_200iD_7L.robot b/Config/Models/LR_Mate_200iD_7L.robot new file mode 100644 index 0000000..21b4c4a Binary files /dev/null and b/Config/Models/LR_Mate_200iD_7L.robot differ diff --git a/Config/Models/workpiece.stl b/Config/Models/workpiece.stl new file mode 100644 index 0000000..d592468 Binary files /dev/null and b/Config/Models/workpiece.stl differ diff --git a/Config/RobotConfig.json b/Config/RobotConfig.json new file mode 100644 index 0000000..711ebf8 --- /dev/null +++ b/Config/RobotConfig.json @@ -0,0 +1,305 @@ +{ + "robot": { + "use_do": true, + "io_addr": [ + 7, + 8 + ], + "io_keep_cycles": 2, + "trigger_sample_index_offset_cycles": 6, + "acc_limit": 1, + "jerk_limit": 1, + "adapt_icsp_try_num": 5, + "speed_ratio": 1, + "planning_speed_scale": 0.74227, + "smooth_start_stop_timing": true + }, + "flying_shots": { + "UTTC_MS11": { + "traj_waypoints": [ + [ + 1.056731, + 0.011664811, + -0.017892333, + -0.01516874, + 0.021492079, + 0.009567846 + ], + [ + 0.8532358, + 0.03837953, + -0.19235304, + 0.0071595116, + 0.109054826, + 0.040055145 + ], + [ + 0.96600056, + 0.20607172, + -0.12233179, + -1.2394339, + 0.10493033, + 1.2958988 + ], + [ + 0.9618476, + 0.15288207, + -0.14867093, + -0.7176314, + 0.1764264, + 0.73228663 + ], + [ + 0.76189893, + -0.028442925, + -0.30919823, + 0.10463613, + 0.5615024, + -0.39399016 + ], + [ + 1.1271763, + 0.074403025, + -0.27347943, + -0.5227772, + 0.52098846, + 0.79633313 + ], + [ + 1.0555661, + 0.4026262, + -0.08746306, + 0.6301835, + 0.09644133, + -0.5463328 + ], + [ + 1.2300354, + 0.28612664, + -0.23486805, + -0.4868128, + 0.25369516, + 0.55347764 + ], + [ + 1.2144431, + -0.29855102, + -0.15202847, + -1.0205934, + 0.13317892, + 1.1246506 + ], + [ + 1.2840607, + -0.11222197, + -0.16805042, + -2.248135, + 0.2560587, + 2.4434967 + ], + [ + 1.3189346, + -0.25620222, + -0.12730704, + -2.285038, + 0.30872014, + 2.4765089 + ], + [ + 1.502615, + -0.25304365, + -0.23878741, + -1.2194318, + 0.46674785, + 1.5533328 + ], + [ + 1.07723, + -0.07387611, + -0.1707704, + -1.8916591, + 0.38677844, + 2.061968 + ], + [ + 1.3920237, + 0.08098731, + -0.2672306, + -0.9780007, + 0.4561093, + 0.9102286 + ], + [ + 1.9016331, + 0.023924276, + -0.58633333, + -0.8441697, + 0.76730615, + 1.4842151 + ], + [ + 1.9300697, + -0.06738541, + -0.56542397, + -0.892083, + 0.77194446, + 1.5293273 + ], + [ + 2.0611632, + -0.30327517, + -0.54225636, + -1.0395275, + 0.8505439, + 1.6429617 + ], + [ + 1.0921186, + -0.40034482, + -0.1803499, + 1.3524796, + 0.6210477, + -1.2159473 + ], + [ + 1.0521278, + -0.40034503, + -0.1803492, + 1.3524843, + 0.6210471, + -1.2159531 + ], + [ + 1.056731, + 0.011664811, + -0.017892333, + -0.01516874, + 0.021492079, + 0.009567846 + ] + ], + "shot_flags": [ + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + false + ], + "offset_values": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "addr": [ + [], + [ + 2, + 4 + ], + [ + 3, + 4, + 2 + ], + [ + 3, + 4, + 2 + ], + [ + 4, + 2 + ], + [ + 4, + 2 + ], + [ + 3, + 4 + ], + [ + 3, + 4 + ], + [ + 4, + 2 + ], + [ + 4, + 2 + ], + [ + 4, + 2 + ], + [ + 4, + 2 + ], + [ + 4, + 2 + ], + [ + 4, + 3 + ], + [ + 4, + 2 + ], + [ + 4, + 2 + ], + [ + 4, + 2 + ], + [ + 4, + 2 + ], + [ + 4, + 3 + ], + [] + ] + } + } +} diff --git a/FlyshotReplacement.sln b/FlyshotReplacement.sln index 0e0216d..c4901e0 100644 --- a/FlyshotReplacement.sln +++ b/FlyshotReplacement.sln @@ -23,6 +23,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyshot.Core.Planning", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyshot.Core.Triggering", "src\Flyshot.Core.Triggering\Flyshot.Core.Triggering.csproj", "{E4DDC34C-9AB6-4050-A927-3DF69804708A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyshot.ControllerClientCompat", "src\Flyshot.ControllerClientCompat\Flyshot.ControllerClientCompat.csproj", "{5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyshot.Runtime.Fanuc", "src\Flyshot.Runtime.Fanuc\Flyshot.Runtime.Fanuc.csproj", "{B705FA6C-19CA-44A8-882C-6CE26A5379C9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -129,6 +133,30 @@ Global {E4DDC34C-9AB6-4050-A927-3DF69804708A}.Release|x64.Build.0 = Release|Any CPU {E4DDC34C-9AB6-4050-A927-3DF69804708A}.Release|x86.ActiveCfg = Release|Any CPU {E4DDC34C-9AB6-4050-A927-3DF69804708A}.Release|x86.Build.0 = Release|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Debug|x64.ActiveCfg = Debug|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Debug|x64.Build.0 = Debug|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Debug|x86.ActiveCfg = Debug|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Debug|x86.Build.0 = Debug|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Release|Any CPU.Build.0 = Release|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Release|x64.ActiveCfg = Release|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Release|x64.Build.0 = Release|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Release|x86.ActiveCfg = Release|Any CPU + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3}.Release|x86.Build.0 = Release|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Debug|x64.ActiveCfg = Debug|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Debug|x64.Build.0 = Debug|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Debug|x86.Build.0 = Debug|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Release|Any CPU.Build.0 = Release|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Release|x64.ActiveCfg = Release|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Release|x64.Build.0 = Release|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Release|x86.ActiveCfg = Release|Any CPU + {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -142,5 +170,7 @@ Global {6CC8418D-2A13-4D70-8F94-585CD71F0B74} = {CB517CF5-2EF6-43A8-B335-ABD3A6FCE3BE} {154CA299-80D8-4BE2-B1C9-4BC133FA8B28} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} {E4DDC34C-9AB6-4050-A927-3DF69804708A} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} + {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} + {B705FA6C-19CA-44A8-882C-6CE26A5379C9} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} EndGlobalSection EndGlobal diff --git a/NLog.config b/NLog.config new file mode 100644 index 0000000..3435dc3 --- /dev/null +++ b/NLog.config @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 98be7c4..35de859 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 当前目标: -- 兼容现有 `50001/TCP+JSON` 上层接入语义 +- 以新的 ASP.NET Core HTTP API 作为唯一上层接口 - 重写轨迹生成、触发时序和 FANUC 实时控制链路 - 提供 Web 状态监控页面 - 在 Windows 和 Linux 上运行完整后台服务 @@ -13,12 +13,113 @@ - 这是长期运行的无头后台服务,不是 GUI 桌面程序。 - 第一版仅面向当前现场组合,后续再扩展机型与控制柜适配。 +- 当前仓库不再恢复旧 `50001/TCP+JSON` 监听入口;旧 `ControllerClient` 逆向资料只作为接口语义参考,不作为运行时目标。 +- 宿主只保留 ASP.NET Core HTTP 控制器层,以及其后端 `Flyshot.ControllerClientCompat` 兼容服务。 +- `ExecuteTrajectory` 与 `ExecuteFlyShotTraj` 已经接入 `Planning + Triggering + Runtime` 链路;Web 状态页已通过 `/status` 和 `/api/status/snapshot` 暴露当前兼容层与运行时状态。 +- `Flyshot.Core.Planning` 的 ICSP / self-adapt-icsp 轨迹已经完成旧系统导出轨迹对齐;`doubles` 仍未实现。 +- `Flyshot.Runtime.Fanuc` 已固化 `10010 / 10012 / 60015` 基础协议帧编解码。`10010` 状态通道以 `j519 协议.pcap` 和 `Rvbust/uttc-20260428/20260428.pcap` 真机抓包确认为 90B 固定帧。 +- 2026-04-28 UTTC 抓包确认:UDP 60015 命令 `target[0..5]` 为关节角度制 `deg`,`JointDetialTraj.txt` 为弧度制 `rad`,`speed_ratio=0.7` 体现为 UDP 下发时间轴约 `1.427730x` 拉伸;2026-04-30 实体机确认 `speed_ratio` 不影响生成的 `JointTraj.txt` 规划时长,当前实际生成约 `7.4s` 轨迹。 +- 2026-04-30 本机 `50001/TCP+JSON` 抓包确认:`ExecuteFlyShotTraj(save_traj=true,use_cache=false)` 请求只显式携带规划方法、保存、缓存和等待参数,不携带 `JointLimits / acc_limit / jerk_limit / velocity / acceleration / jerk`。因此旧系统不可见的有效规划限制不再继续假设来自公开链路,新系统按 replacement-only 内部参数限制规划加速度。 +- 真机 Socket 客户端已具备基础连接、程序启停、速度倍率/TCP/IO 参数命令和 J519 状态包驱动发送能力;稠密轨迹下发已按 `speed_ratio` 做执行时间缩放,并已用 0.5/0.7/1.0 三份 UTTC 抓包固化 J519 golden tests。真实 R30iB 全流程现场联调仍需执行。 +- 2026-05-11 对 `UTTC_MS11` 的独立 `ConfigRoot` 重新生成确认:flyshot 链路若继续用离散 `jerk` 作为阻断条件,会把新结果拉长到约 `16.576s / 33.152s`;只保留速度、加速度校验并跳过 flyshot jerk 阻断后,新 `1.0x / 0.5x` 时长回落到约 `7.552s / 15.104s`,与旧抓包主段 `7.408s / 14.808s` 的差异已很小,当前按可忽略处理。详见 `docs/uttc-ms11-jerk-validation-decision-20260511.md`。 +- `MoveJoint` 已按 `2026042802-mvpoint*.pcap` 复刻为点到点临时轨迹:当前关节到目标关节的关节空间直线,五次 smoothstep 起停,按 `status=15` 运动窗口复现 `40/55/77` 点,并由 J519 层完成 `rad -> deg` 下发。 +- 单程序只对应一台机器人,上传/删除/恢复飞拍轨迹统一读写运行目录 `Config/RobotConfig.json`,不再创建独立轨迹存储文件。 -当前 Todo: +单位约定总览: + +- 规划层、`JointDetialTraj.txt` 和运行时内部关节轨迹,默认按弧度制 `rad` 理解。 +- `UDP 60015` J519 命令 `target[0..5]` 和响应关节反馈按角度制 `deg` 理解;运行时下发前必须显式执行 `rad -> deg` 转换。 +- `TCP 10010` 状态通道是混合单位:`pose[0..2]` 更像 `mm`,`pose[3..5]` 更像 `deg`,`joint_or_ext[0..5]` 当前现场抓包更支持按 `rad` 理解。 +- 不要把“关节角”默认当成统一单位;在规划、状态监控和 J519 执行三条链路之间必须明确标注 `rad/deg`。 + +当前现场主链路的单位流转可简化为: + +| 位置 | 内容 | 当前更可信单位 | +| --- | --- | --- | +| 规划输入 / 轨迹算法 | 关节角 | `rad` | +| `JointDetialTraj.txt` / `JointTraj.txt` | 关节角 | `rad` | +| 运行时下发前内部轨迹 | 关节角 | `rad` | +| `UDP 60015` 命令 `target[0..5]` | 关节目标 | `deg` | +| `UDP 60015` 响应 `Joint` | 关节反馈 | `deg` | +| `TCP 10010` `pose[0..2]` | `X/Y/Z` | `mm` | +| `TCP 10010` `pose[3..5]` | 姿态角 | `deg` | +| `TCP 10010` `joint_or_ext[0..5]` | 关节状态 | 更像 `rad` | +| `TCP 10010` `joint_or_ext[6..8]` | 扩展轴槽位 | 当前样本为 `0` | + +`TCP 10010` 的正式字段表、样例帧和已确认/待确认说明见 `docs/fanuc-10010-state-frame.md`。 + +开发约定: + +- 建议从 `flyshot-replacement/` 根目录启动 IDE、终端和 Codex 会话。 +- 当前仓库内的 `@` 引用主要覆盖本仓库文件;引用父目录资料时,请直接写相对路径,如 `../analysis/ICSP_algorithm_reverse_analysis.md`。 +- 父目录中的 `analysis/`、`FlyingShot/`、`RobotController/`、`RPS/` 主要作为逆向参考资料和样本来源,新实现默认只落地在当前仓库。 + +当前已完成: - [x] 初始化独立仓库 - [x] 创建 `dotnet 8` 解决方案骨架 - [x] 打通最小宿主与 `/healthz` - [x] 建立领域模型与模块边界 - [x] 落地配置兼容与机器人模型解析 -- [ ] 落地轨迹规划、实时控制和 Web 状态页 +- [x] 落地 ICSP / self-adapt-icsp 轨迹规划与飞拍触发时间轴 +- [x] 完成 ICSP 轨迹导出结果与旧系统对齐 +- [x] 将 `ExecuteTrajectory` / `ExecuteFlyShotTraj` 接入 FANUC 运行时链路 +- [x] 落地 Web 状态页 +- [x] 落地浏览器内 OpenAPI 自动驱动的接口调试页(`/debug`),与 `Swagger:Enabled` 同步可见 +- [x] 固化 `10010 / 10012 / 60015` FANUC 基础协议帧编解码,确认 `10010` 状态帧为 90B +- [x] 使用本地 TCP/UDP 模拟器覆盖命令通道、状态通道和 J519 基础收发 +- [x] 补齐 `Get/SetSpeedRatio`、`Get/SetTCP`、`Get/SetIO` 真机命令体与响应解析 +- [x] 保留新 HTTP 接口路线,明确不再实现旧 `50001/TCP+JSON` 网关 +- [x] 将飞拍轨迹持久化收敛到运行目录 `Config/RobotConfig.json` + +剩余 Todo: + +1. 配置与测试基线 + - [x] 修正 `ConfigCompatibilityTests` 当前样本路径漂移:`Rvbust/EOL10_EAU_0/RobotConfig.json` 不再包含 `001`,应改用稳定样本或更新断言。 + - [x] 将 `RobotConfig.json` 中的 `use_do`、`io_keep_cycles`、`acc_limit`、`jerk_limit`、`adapt_icsp_try_num` 全部贯通到规划和执行链路。 + - [x] 将上传飞拍轨迹统一保存到运行目录 `Config/RobotConfig.json` 的 `flying_shots` 节点。 + - [x] 为新 HTTP API 补一份当前现场调用顺序文档,替代旧 `ControllerClient` 工作流:见 `docs/fanuc-field-runtime-workflow.md`。 + +2. 轨迹规划 + - [x] 补齐 ICSP 最终 `global_scale > 1.0` 失败判定,避免未收敛轨迹被当作有效结果执行。 + - [x] 将 self-adapt-icsp 的补点次数改为使用配置中的 `adapt_icsp_try_num`。 + - [ ] 新增 replacement-only 的 `planning_acceleration_scale` 规划加速度校准参数,用于复现旧服务端公开链路中抓不到的保守 effective limits;该参数只影响规划结果,不影响运行时 `speed_ratio`。 + - [ ] 如果现场仍需要 `method="doubles"`,实现 `TrajectoryDoubleS` 等价规划;否则在 HTTP 文档中明确标为不支持。 + - [ ] 把已完成对齐的旧系统轨迹样本固化为 golden tests,防止后续重构破坏轨迹一致性。 + - [x] 将 `Rvbust/uttc-20260428/Data/JointDetialTraj.txt` 固化为 J519 golden 样本:输入为 `rad`,下发为 `deg`,并按 `speed_ratio` 拉伸时间轴;覆盖 `2026042802-0.5/0.7/1.pcap`。 + - [x] 补齐飞拍 `save_traj` / `SaveTrajInfo` 的规划结果导出,将关节关键点、稠密关节轨迹、笛卡尔关键点、稠密笛卡尔轨迹和 ShotEvents 写入 `Config/Data/`。 + +3. FANUC TCP 10012 命令通道 + - [x] 补齐 `GetSpeedRatio` / `SetSpeedRatio` 真机命令体与响应解析。 + - [x] 补齐 `GetTCP` / `SetTCP` 真机命令体与响应解析。 + - [x] 补齐 `GetIO` / `SetIO` 真机命令体与响应解析。 + - [x] 所有命令响应必须检查 `result_code`,失败时返回可诊断错误,而不是只更新本地缓存。 + +4. FANUC TCP 10010 状态通道 + - [x] 用 `j519 协议.pcap` 和 `Rvbust/uttc-20260428/20260428.pcap` 中的 90B 真机状态帧扩充状态解析测试样本。 + - [x] 明确 `pose[6]`、`joint_or_ext[9]`、尾部状态字的字段语义,并映射到 `ControllerStateSnapshot`。 + - [x] 补充 `TCP 10010` 正式字段表与已确认/待确认说明:见 `docs/fanuc-10010-state-frame.md`。 + - [x] 补充断线清理和异常帧拒绝测试。 + - [x] 补充状态通道超时和重连策略,超时后标记陈旧状态并按退避策略自动重连。 + +5. FANUC UDP 60015 J519 运动链路 + - [x] 重新确认 J519 发送节拍与 `FanucControllerRuntime` 稠密轨迹循环的职责边界:`FanucJ519Client` 收到机器人 UDP status 后按该 status sequence 回发命令,`FanucControllerRuntime` 只按轨迹时间更新下一帧命令内容。 + - [x] 执行时将规划输出 `rad` 转为 J519 `deg` 目标,并按当前 `speed_ratio` 调整原轨迹采样时间尺度:第 `k` 个 J519 目标的实发时间为 `t_send = k * 0.008`,采样时间为 `t_traj = t_send * speed_ratio`,包数为 `floor(duration / (0.008 * speed_ratio)) + 1`。 + - [x] 补齐 `accept_cmd`、`received_cmd`、`sysrdy`、`rbt_inmotion` 状态位解析与启动前闭环检查;若已有 J519 响应且 `accept_cmd/sysrdy` 未就绪,则拒绝稠密轨迹执行。 + - [x] 校验序号递增、状态包 sequence 校准、响应滞后、丢包、停止包和最后一帧语义:UTTC golden tests 覆盖连续 seq、无重复 seq、响应滞后 2 到 8 帧、`lastData=0`;J519 客户端测试覆盖收到 status 后按 status sequence 回发命令和 type 2 状态输出停止包。 + - [x] 将飞拍 IO 触发的 `write_io_type/index/mask/value` 与现场控制柜实际 IO 地址逐项对齐;UTTC golden tests 确认 17 个触发点对应 17 个 UDP IO set 脉冲、17 个 clear 帧,mask 集合为 `10/12/14`。 + - [x] 将 `MoveJoint` 从单点最终目标改为临时 PTP 稠密轨迹:按 `status=15` 运动窗口统计,speed=1 抓包 40 点,speed=0.7 抓包 55 点,speed=0.5 抓包 77 点,路径为关节空间直线 + smoothstep 进度。 + - [x] `ExecuteFlyShotTraj(move_to_start=true)` 复用临时 PTP 稠密轨迹移动到规划起点,并等待运行时完成后再启动飞拍轨迹,避免第一帧 J519 目标突变导致控制柜报警。 + - [x] `ExecuteFlyShotTraj(wait=true)` 等待正式飞拍轨迹执行完成后再返回;HTTP `/execute_flyshot/` 已接入旧抓包中的 `wait` 字段,默认值为 `true`。 + +6. 真机联调与运行安全 + - [ ] 在真实 R30iB + `RVBUSTSM` 程序上验证 `Connect -> EnableRobot -> ExecuteFlyShotTraj -> StopMove -> DisableRobot -> Disconnect` 全流程。 + - [x] 实体机复核运行速度对轨迹生成时间的影响:`speed_ratio` 不影响 `IsFlyshotTrajectoryValid` / `SaveTrajectoryInfo` 生成的 `JointTraj.txt` 规划时长,当前实际生成约 `7.4s` 轨迹;运行阶段仅 J519 下发时长和包数按 `speed_ratio` 拉伸,UTTC_MS11 参考值为约 `7.4s` 与 `10.56s`。 + - [ ] 增加急停、伺服未就绪、程序未启动、网络断开、控制柜拒收命令等故障路径处理。 + - [ ] 给 HTTP 执行接口增加运行互斥、执行中拒绝重复轨迹、取消和超时控制。 + - [ ] 增加运行日志、协议摘要日志和状态快照导出,便于现场排查。 + +7. 发布与部署 + - [ ] 固化 Windows / Linux 启动脚本和 systemd 服务配置。 + - [ ] 补充生产配置模板、端口说明和现场部署检查表。 + - [ ] 给 Web 状态页增加程序状态和最近报警显示;J519 状态位已通过快照和状态页显示。 diff --git a/analysis/2026042802-1/2026042802-1_analysis_summary.json b/analysis/2026042802-1/2026042802-1_analysis_summary.json new file mode 100644 index 0000000..48ff058 --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_analysis_summary.json @@ -0,0 +1,21 @@ +{ + "pcap_path": "D:\\Dev\\Codes\\rvbust-code\\FlyingShotPkg_3.15_VDA\\Rvbust\\uttc-20260428\\2026042802-1.pcap", + "all_command_count": 1788, + "trigger_count": 17, + "existing_runtime_actual_send_exists": true, + "existing_runtime_actual_send_has_io_columns": false, + "existing_shot_events_exists": true, + "pcap_specific_combined_export_preexisting": false, + "average_max_error_deg": 4.241583591714439, + "max_error_deg": 11.130744251720401, + "average_rms_error_deg": 2.5406136706026206, + "max_error_axis_counter": { + "J6": 9, + "J4": 5, + "J5": 1, + "J2": 1, + "J1": 1 + }, + "order_only_addr_mismatch_count": 14, + "real_addr_mismatch_count": 0 +} \ No newline at end of file diff --git a/analysis/2026042802-1/2026042802-1_j519_actual_send_all_with_io.csv b/analysis/2026042802-1/2026042802-1_j519_actual_send_all_with_io.csv new file mode 100644 index 0000000..1f0de8f --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_j519_actual_send_all_with_io.csv @@ -0,0 +1,1789 @@ +frame_number,time_relative_s,sequence,last_data,write_io_type,write_io_index,write_io_mask,write_io_value,io_addrs,j1_deg,j2_deg,j3_deg,j4_deg,j5_deg,j6_deg,ext1_deg,ext2_deg,ext3_deg +2,9.2e-05,1380388,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +6,0.008042,1380389,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +8,0.016103,1380390,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +10,0.024087,1380391,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +14,0.032073,1380392,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +16,0.04007,1380393,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +18,0.048084,1380394,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +21,0.056044,1380395,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +24,0.06408,1380396,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +26,0.072112,1380397,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +30,0.080118,1380398,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +32,0.088052,1380399,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +34,0.096059,1380400,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +36,0.104122,1380401,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +40,0.112095,1380402,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +42,0.120068,1380403,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +44,0.128121,1380404,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +48,0.136108,1380405,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +50,0.144088,1380406,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +52,0.152121,1380407,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +55,0.160116,1380408,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +58,0.168083,1380409,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +60,0.176145,1380410,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +64,0.184056,1380411,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +66,0.192053,1380412,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +68,0.200116,1380413,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +71,0.208123,1380414,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +74,0.216095,1380415,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +76,0.224105,1380416,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +78,0.232117,1380417,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +81,0.240086,1380418,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +84,0.248052,1380419,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +86,0.256084,1380420,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +90,0.264146,1380421,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +92,0.272121,1380422,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +94,0.280145,1380423,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +98,0.288093,1380424,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +100,0.29609,1380425,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +102,0.304129,1380426,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +105,0.312064,1380427,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +108,0.320073,1380428,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +110,0.328113,1380429,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +114,0.336106,1380430,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +116,0.344098,1380431,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +118,0.352102,1380432,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +120,0.360118,1380433,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +124,0.368089,1380434,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +126,0.376053,1380435,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +128,0.384063,1380436,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +131,0.392101,1380437,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +134,0.400107,1380438,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +136,0.408112,1380439,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +140,0.416138,1380440,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +142,0.424102,1380441,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +144,0.432131,1380442,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +148,0.440068,1380443,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +150,0.448112,1380444,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +152,0.456127,1380445,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +155,0.464108,1380446,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +158,0.472097,1380447,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +160,0.480096,1380448,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +162,0.488135,1380449,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +165,0.496094,1380450,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +168,0.504115,1380451,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +170,0.512126,1380452,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +174,0.520103,1380453,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +176,0.528113,1380454,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +178,0.536131,1380455,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +181,0.544096,1380456,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +184,0.552088,1380457,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +186,0.560135,1380458,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +190,0.568168,1380459,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +192,0.576088,1380460,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +194,0.584141,1380461,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +198,0.5921,1380462,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +200,0.600124,1380463,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +202,0.608109,1380464,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +205,0.616139,1380465,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +209,0.624098,1380466,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +211,0.632094,1380467,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +213,0.640121,1380468,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +216,0.648099,1380469,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +219,0.656118,1380470,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +221,0.664117,1380471,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +225,0.672117,1380472,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +227,0.680103,1380473,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +229,0.68813,1380474,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +233,0.696119,1380475,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +235,0.70411,1380476,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +237,0.712146,1380477,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +240,0.720101,1380478,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +243,0.72812,1380479,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +245,0.736109,1380480,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +247,0.744108,1380481,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +251,0.752144,1380482,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +253,0.7601,1380483,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +255,0.768113,1380484,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +259,0.776145,1380485,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +261,0.784113,1380486,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +263,0.792131,1380487,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +266,0.800101,1380488,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +269,0.808106,1380489,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +271,0.816135,1380490,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +275,0.824112,1380491,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +277,0.832111,1380492,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +279,0.840104,1380493,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +283,0.848117,1380494,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +285,0.856108,1380495,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +287,0.864103,1380496,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +289,0.872137,1380497,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +292,0.880124,1380498,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +295,0.88812,1380499,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +297,0.896133,1380500,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +301,0.904112,1380501,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +303,0.912117,1380502,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +305,0.92016,1380503,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +309,0.928118,1380504,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +311,0.936117,1380505,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +313,0.944116,1380506,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +316,0.952118,1380507,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +319,0.960093,1380508,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +321,0.968122,1380509,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +325,0.976179,1380510,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +327,0.984102,1380511,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +329,0.992132,1380512,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +331,1.000116,1380513,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +335,1.008139,1380514,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +337,1.01614,1380515,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +339,1.024152,1380516,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +342,1.03211,1380517,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +345,1.040114,1380518,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +347,1.048121,1380519,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +350,1.056126,1380520,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +353,1.064125,1380521,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +355,1.072135,1380522,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +359,1.080119,1380523,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +361,1.088128,1380524,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +363,1.096125,1380525,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +366,1.104126,1380526,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +369,1.112127,1380527,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +371,1.120119,1380528,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +373,1.128144,1380529,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +376,1.136129,1380530,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +379,1.14412,1380531,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +381,1.152134,1380532,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +385,1.160137,1380533,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +387,1.168113,1380534,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +389,1.176158,1380535,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +393,1.184117,1380536,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +395,1.192112,1380537,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +397,1.20013,1380538,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +400,1.208106,1380539,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +403,1.216136,1380540,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +405,1.22415,1380541,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +409,1.232091,1380542,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +411,1.240116,1380543,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +413,1.248128,1380544,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +415,1.256154,1380545,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +419,1.26409,1380546,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +421,1.272128,1380547,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +423,1.280162,1380548,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +426,1.288136,1380549,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +429,1.296087,1380550,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +431,1.304134,1380551,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +435,1.312141,1380552,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +437,1.320131,1380553,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +439,1.32816,1380554,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +443,1.336117,1380555,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +445,1.344145,1380556,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +447,1.352159,1380557,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +450,1.360123,1380558,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +453,1.368119,1380559,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +455,1.376144,1380560,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +457,1.384139,1380561,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +461,1.392177,1380562,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +463,1.400135,1380563,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +465,1.408124,1380564,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +469,1.416145,1380565,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +471,1.42411,1380566,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +473,1.432147,1380567,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +476,1.440134,1380568,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +479,1.44812,1380569,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +481,1.45616,1380570,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +485,1.464149,1380571,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +487,1.472132,1380572,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +489,1.480157,1380573,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +493,1.488129,1380574,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +495,1.496132,1380575,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +497,1.504125,1380576,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +499,1.512159,1380577,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +502,1.520126,1380578,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +505,1.528147,1380579,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +507,1.536146,1380580,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +510,1.544144,1380581,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +513,1.552113,1380582,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +515,1.560153,1380583,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +519,1.568147,1380584,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +521,1.576114,1380585,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +523,1.584165,1380586,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +526,1.592143,1380587,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +529,1.600134,1380588,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +531,1.608146,1380589,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +535,1.616186,1380590,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +537,1.624142,1380591,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +539,1.63212,1380592,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +541,1.640148,1380593,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +545,1.648149,1380594,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +547,1.656146,1380595,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +549,1.664147,1380596,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +553,1.672142,1380597,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +555,1.680148,1380598,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +557,1.688123,1380599,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +560,1.696144,1380600,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +563,1.704125,1380601,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +565,1.712152,1380602,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +569,1.720139,1380603,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +571,1.728165,1380604,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +573,1.736161,1380605,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +576,1.744137,1380606,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +579,1.752057,1380607,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +581,1.760092,1380608,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +583,1.768162,1380609,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +586,1.776166,1380610,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +589,1.784098,1380611,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +591,1.792172,1380612,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +595,1.800131,1380613,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +597,1.808151,1380614,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +599,1.816205,1380615,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +603,1.824109,1380616,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +605,1.832149,1380617,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +607,1.84016,1380618,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +610,1.84813,1380619,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +613,1.856146,1380620,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +615,1.864164,1380621,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +619,1.872161,1380622,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +621,1.880147,1380623,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +623,1.888158,1380624,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +625,1.896159,1380625,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +629,1.904141,1380626,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +631,1.912134,1380627,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +633,1.920157,1380628,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +636,1.928141,1380629,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +639,1.936156,1380630,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +641,1.944168,1380631,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +645,1.952165,1380632,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +647,1.96015,1380633,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +649,1.968148,1380634,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +653,1.976158,1380635,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +655,1.984142,1380636,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +657,1.992132,1380637,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +660,2.000127,1380638,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +664,2.008133,1380639,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +666,2.01614,1380640,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +668,2.024144,1380641,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +671,2.032157,1380642,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +674,2.040143,1380643,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +676,2.048166,1380644,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +680,2.056157,1380645,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +682,2.064149,1380646,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +684,2.072158,1380647,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +687,2.080107,1380648,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +690,2.088111,1380649,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +692,2.096161,1380650,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +696,2.104207,1380651,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +698,2.112149,1380652,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +700,2.120154,1380653,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +704,2.12814,1380654,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +706,2.136142,1380655,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +708,2.144169,1380656,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +710,2.152117,1380657,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +714,2.160152,1380658,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +716,2.168131,1380659,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +718,2.176156,1380660,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +721,2.184118,1380661,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +724,2.192147,1380662,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +726,2.200174,1380663,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +730,2.20814,1380664,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +732,2.216109,1380665,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +734,2.224171,1380666,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +737,2.232151,1380667,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +740,2.240153,1380668,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +742,2.248168,1380669,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +745,2.25614,1380670,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +748,2.264154,1380671,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +750,2.272146,1380672,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +752,2.280117,1380673,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +756,2.288094,1380674,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +758,2.296134,1380675,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +760,2.304169,1380676,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +764,2.31215,1380677,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +766,2.320153,1380678,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +768,2.328165,1380679,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +771,2.336143,1380680,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +774,2.344157,1380681,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +776,2.352168,1380682,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +780,2.360152,1380683,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +782,2.368136,1380684,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +784,2.376146,1380685,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +788,2.384153,1380686,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +790,2.392148,1380687,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +792,2.400156,1380688,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +794,2.408153,1380689,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +797,2.416147,1380690,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +800,2.424147,1380691,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +802,2.432158,1380692,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +806,2.440202,1380693,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +808,2.448147,1380694,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +810,2.456162,1380695,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +814,2.464152,1380696,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +816,2.472147,1380697,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +818,2.480163,1380698,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +821,2.488139,1380699,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +824,2.496169,1380700,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +831,2.50415,1380701,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +835,2.512224,1380702,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +837,2.520149,1380703,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +839,2.528149,1380704,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +843,2.536181,1380705,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +847,2.544125,1380706,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +849,2.552152,1380707,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +851,2.560166,1380708,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +854,2.568165,1380709,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +857,2.576133,1380710,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +859,2.584191,1380711,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +863,2.592149,1380712,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +865,2.600143,1380713,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +867,2.608144,1380714,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +871,2.616152,1380715,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +873,2.624163,1380716,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +875,2.632138,1380717,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +878,2.640175,1380718,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +881,2.648153,1380719,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +883,2.656171,1380720,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +885,2.664174,1380721,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +888,2.672159,1380722,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +891,2.680173,1380723,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +893,2.688181,1380724,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +897,2.696161,1380725,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +899,2.704156,1380726,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +901,2.712164,1380727,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +905,2.720154,1380728,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +907,2.728163,1380729,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +909,2.73619,1380730,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +912,2.744149,1380731,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +915,2.752142,1380732,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +917,2.76018,1380733,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +921,2.768167,1380734,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +923,2.77618,1380735,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +925,2.784172,1380736,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +927,2.792167,1380737,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +931,2.800152,1380738,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +933,2.80815,1380739,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +935,2.816119,1380740,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +938,2.824152,1380741,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +941,2.832161,1380742,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +943,2.840196,1380743,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +947,2.848191,1380744,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +949,2.856146,1380745,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +951,2.864177,1380746,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +955,2.872156,1380747,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +957,2.880147,1380748,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +959,2.888173,1380749,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +962,2.896174,1380750,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +965,2.904164,1380751,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +969,2.912169,1380752,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +971,2.920187,1380753,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +977,2.928118,1380754,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +979,2.936118,1380755,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +983,2.944182,1380756,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +987,2.952167,1380757,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +989,2.960165,1380758,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +991,2.968179,1380759,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +994,2.976168,1380760,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +997,2.984158,1380761,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +999,2.992193,1380762,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1003,3.000159,1380763,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1005,3.008151,1380764,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1007,3.016207,1380765,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1011,3.024155,1380766,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1013,3.03218,1380767,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1015,3.040143,1380768,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1017,3.048202,1380769,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1021,3.056164,1380770,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1023,3.064175,1380771,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1025,3.072181,1380772,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1028,3.080167,1380773,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1031,3.088186,1380774,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1033,3.096196,1380775,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1037,3.104181,1380776,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1039,3.11217,1380777,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1041,3.1202,1380778,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1044,3.128169,1380779,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1047,3.136161,1380780,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1049,3.144182,1380781,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1053,3.152158,1380782,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1055,3.16018,1380783,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1057,3.168176,1380784,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1059,3.176183,1380785,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1063,3.184226,1380786,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1065,3.192176,1380787,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1067,3.200187,1380788,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1071,3.208161,1380789,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1073,3.216174,1380790,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1075,3.2242,1380791,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1078,3.232179,1380792,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1081,3.240168,1380793,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1083,3.248185,1380794,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1087,3.256183,1380795,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1089,3.264159,1380796,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1091,3.272203,1380797,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1095,3.280166,1380798,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1097,3.288164,1380799,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1099,3.296176,1380800,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1101,3.304177,1380801,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1104,3.312177,1380802,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1107,3.320183,1380803,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1109,3.328168,1380804,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1113,3.33619,1380805,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1115,3.344194,1380806,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1117,3.352185,1380807,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1121,3.360173,1380808,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1123,3.368175,1380809,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1125,3.376191,1380810,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1128,3.384179,1380811,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1131,3.392192,1380812,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1133,3.400193,1380813,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1137,3.40818,1380814,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1139,3.416178,1380815,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1141,3.424169,1380816,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1143,3.432197,1380817,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1147,3.440188,1380818,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1149,3.448172,1380819,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1151,3.456165,1380820,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1154,3.464122,1380821,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1157,3.472185,1380822,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1159,3.480186,1380823,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1163,3.488208,1380824,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1165,3.49616,1380825,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1167,3.504192,1380826,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1171,3.512181,1380827,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1173,3.52016,1380828,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1175,3.528169,1380829,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1178,3.536174,1380830,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1181,3.544192,1380831,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1183,3.55218,1380832,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1185,3.560185,1380833,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1189,3.568183,1380834,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1192,3.576183,1380835,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1194,3.584203,1380836,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1198,3.592184,1380837,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1200,3.600156,1380838,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1202,3.608208,1380839,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1205,3.616182,1380840,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1208,3.62418,1380841,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1210,3.632209,1380842,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1213,3.640179,1380843,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1216,3.648191,1380844,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1218,3.656187,1380845,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1222,3.664194,1380846,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1224,3.672174,1380847,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1226,3.680198,1380848,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1228,3.688193,1380849,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1232,3.696199,1380850,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1234,3.704191,1380851,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1236,3.712207,1380852,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1239,3.72016,1380853,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1242,3.728181,1380854,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1244,3.736203,1380855,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1248,3.744165,1380856,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1250,3.752191,1380857,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1252,3.760179,1380858,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1256,3.768192,1380859,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1258,3.77625,1380860,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1260,3.784197,1380861,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1263,3.792195,1380862,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1266,3.800176,1380863,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1268,3.808208,1380864,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1270,3.816184,1380865,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1274,3.824227,1380866,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1276,3.832201,1380867,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1278,3.840182,1380868,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1282,3.848183,1380869,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1284,3.856146,1380870,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1286,3.864205,1380871,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1289,3.872176,1380872,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1292,3.880194,1380873,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1294,3.888184,1380874,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1298,3.896171,1380875,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1300,3.904202,1380876,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1302,3.912183,1380877,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1306,3.920182,1380878,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1308,3.92817,1380879,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1310,3.936193,1380880,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1312,3.944207,1380881,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1315,3.952189,1380882,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1318,3.960206,1380883,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1320,3.968189,1380884,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1323,3.976187,1380885,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1326,3.984174,1380886,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1328,3.992208,1380887,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1332,4.000188,1380888,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1335,4.008181,1380889,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1337,4.016203,1380890,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1340,4.024172,1380891,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1343,4.032193,1380892,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1345,4.04019,1380893,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1349,4.048284,1380894,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1351,4.056185,1380895,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1353,4.064169,1380896,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1355,4.07221,1380897,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1359,4.080189,1380898,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1361,4.088189,1380899,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1363,4.096216,1380900,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1367,4.104184,1380901,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1369,4.112149,1380902,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1371,4.120207,1380903,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1374,4.128196,1380904,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1377,4.136196,1380905,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1379,4.14421,1380906,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1383,4.152185,1380907,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1385,4.160192,1380908,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1387,4.16819,1380909,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1390,4.176135,1380910,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1393,4.184186,1380911,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1395,4.1922,1380912,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1397,4.200181,1380913,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1400,4.208161,1380914,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1403,4.216195,1380915,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1405,4.224184,1380916,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1409,4.232205,1380917,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1411,4.240173,1380918,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1413,4.248248,1380919,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1417,4.2562,1380920,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1419,4.264181,1380921,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1421,4.27221,1380922,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1424,4.280187,1380923,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1427,4.288203,1380924,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1429,4.296204,1380925,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1433,4.304193,1380926,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1435,4.312181,1380927,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1437,4.3202,1380928,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1439,4.328183,1380929,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1443,4.336183,1380930,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1445,4.344212,1380931,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1447,4.352224,1380932,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1450,4.3602,1380933,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1453,4.368198,1380934,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1455,4.376196,1380935,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1459,4.384209,1380936,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1461,4.392199,1380937,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1463,4.400217,1380938,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1467,4.408176,1380939,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1469,4.416188,1380940,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1471,4.424216,1380941,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1475,4.432185,1380942,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1478,4.44017,1380943,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1480,4.448196,1380944,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1482,4.45621,1380945,0,2,1,0,0,[],60.546226501464844,0.6683387756347656,-1.0251551866531372,-0.869085967540741,1.2314136028289795,0.5481805801391602,0.0,0.0,0.0 +1485,4.464185,1380946,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1488,4.472185,1380947,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1490,4.48019,1380948,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1494,4.488146,1380949,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1496,4.496151,1380950,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1498,4.504071,1380951,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1501,4.512107,1380952,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1504,4.520104,1380953,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1506,4.528108,1380954,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1510,4.536208,1380955,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1512,4.544202,1380956,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1514,4.552218,1380957,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1518,4.560076,1380958,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1520,4.568184,1380959,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1522,4.576168,1380960,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1525,4.584187,1380961,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1529,4.592162,1380962,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1531,4.600156,1380963,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1533,4.608176,1380964,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1536,4.616167,1380965,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1539,4.624197,1380966,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1541,4.632218,1380967,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1545,4.640225,1380968,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1547,4.648217,1380969,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1549,4.656213,1380970,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1552,4.664211,1380971,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1555,4.672195,1380972,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1557,4.680252,1380973,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1560,4.688192,1380974,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1563,4.696187,1380975,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1565,4.704193,1380976,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1567,4.712212,1380977,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1571,4.72022,1380978,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1573,4.728208,1380979,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1575,4.736228,1380980,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1579,4.744208,1380981,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1581,4.752208,1380982,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1583,4.760208,1380983,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1586,4.76821,1380984,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1589,4.776264,1380985,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1591,4.784214,1380986,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1595,4.792203,1380987,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1597,4.800163,1380988,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1599,4.808214,1380989,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1603,4.81619,1380990,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1605,4.824191,1380991,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1607,4.832211,1380992,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1609,4.840209,1380993,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1612,4.848207,1380994,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1615,4.856181,1380995,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1617,4.864245,1380996,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1621,4.872224,1380997,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1623,4.880204,1380998,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1625,4.888228,1380999,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1629,4.896222,1381000,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1631,4.904217,1381001,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1633,4.912207,1381002,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1636,4.920215,1381003,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1639,4.928201,1381004,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1641,4.936246,1381005,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1645,4.944254,1381006,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1647,4.952153,1381007,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1649,4.960202,1381008,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1651,4.968212,1381009,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1655,4.976227,1381010,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1657,4.984199,1381011,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1659,4.992251,1381012,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1662,5.000173,1381013,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1665,5.008185,1381014,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1667,5.016175,1381015,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1671,5.024158,1381016,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1673,5.032216,1381017,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1675,5.040222,1381018,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1679,5.048184,1381019,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1681,5.056248,1381020,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1683,5.06421,1381021,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1686,5.072222,1381022,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1689,5.080177,1381023,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1691,5.088219,1381024,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1693,5.096225,1381025,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1696,5.104227,1381026,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1699,5.112213,1381027,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1701,5.120221,1381028,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1705,5.128222,1381029,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1707,5.136199,1381030,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1709,5.144244,1381031,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1713,5.152219,1381032,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1715,5.160193,1381033,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1717,5.168219,1381034,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1720,5.176197,1381035,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1723,5.184226,1381036,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1725,5.192227,1381037,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1729,5.200216,1381038,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1731,5.208206,1381039,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1733,5.216268,1381040,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1735,5.224219,1381041,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1739,5.232205,1381042,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1741,5.240194,1381043,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1743,5.248212,1381044,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1746,5.256222,1381045,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1749,5.264216,1381046,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1751,5.272256,1381047,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1755,5.280221,1381048,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1757,5.288205,1381049,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1759,5.296262,1381050,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1763,5.304208,1381051,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1765,5.312248,1381052,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1767,5.320205,1381053,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1770,5.328206,1381054,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1773,5.336209,1381055,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1775,5.34421,1381056,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1777,5.352264,1381057,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1781,5.360156,1381058,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1783,5.368249,1381059,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1785,5.376226,1381060,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1789,5.384221,1381061,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1791,5.392187,1381062,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1793,5.400265,1381063,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1796,5.408208,1381064,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1799,5.416196,1381065,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1801,5.424209,1381066,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1805,5.432228,1381067,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1807,5.440128,1381068,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1809,5.448158,1381069,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1813,5.456123,1381070,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1815,5.464103,1381071,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1817,5.472149,1381072,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1819,5.480157,1381073,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1822,5.488124,1381074,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1825,5.496147,1381075,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1828,5.504148,1381076,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1831,5.512131,1381077,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1834,5.520125,1381078,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1836,5.528141,1381079,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1840,5.53611,1381080,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1842,5.544126,1381081,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1844,5.552151,1381082,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1847,5.560113,1381083,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1850,5.568145,1381084,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1852,5.576169,1381085,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1856,5.584152,1381086,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1858,5.592136,1381087,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1860,5.600131,1381088,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1863,5.60827,1381089,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +1867,5.616223,1381090,0,2,1,0,0,[],60.545536041259766,0.6684186458587646,-1.0257467031478882,-0.8689018487930298,1.2317007780075073,0.5481721758842468,0.0,0.0,0.0 +1869,5.624228,1381091,0,2,1,0,0,[],60.540706634521484,0.6689381003379822,-1.029887318611145,-0.8674814701080322,1.2337685823440552,0.5479970574378967,0.0,0.0,0.0 +1871,5.632146,1381092,0,2,1,0,0,[],60.52760314941406,0.670348048210144,-1.0411262512207031,-0.8636261820793152,1.239380955696106,0.5475217700004578,0.0,0.0,0.0 +1874,5.64021,1381093,0,2,1,0,0,[],60.50208282470703,0.6730937361717224,-1.0630125999450684,-0.856118381023407,1.2503104209899902,0.5465962290763855,0.0,0.0,0.0 +1877,5.648214,1381094,0,2,1,0,0,[],60.46001052856445,0.6776204705238342,-1.0990954637527466,-0.8437407612800598,1.268329381942749,0.5450703501701355,0.0,0.0,0.0 +1879,5.656146,1381095,0,2,1,0,0,[],60.39724349975586,0.6843733787536621,-1.1529239416122437,-0.825275719165802,1.2952100038528442,0.5427939295768738,0.0,0.0,0.0 +1883,5.664176,1381096,0,2,1,0,0,[],60.30964660644531,0.6937978267669678,-1.228047251701355,-0.799505889415741,1.332724690437317,0.5396170616149902,0.0,0.0,0.0 +1885,5.672178,1381097,0,2,1,0,0,[],60.193084716796875,0.7063390016555786,-1.3280144929885864,-0.7652137279510498,1.382645845413208,0.5353895425796509,0.0,0.0,0.0 +1887,5.680188,1381098,0,2,1,0,0,[],60.04341125488281,0.7224421501159668,-1.4563747644424438,-0.7211818099021912,1.4467458724975586,0.5299612879753113,0.0,0.0,0.0 +1891,5.688208,1381099,0,2,1,0,0,[],59.85649490356445,0.7425525188446045,-1.6166772842407227,-0.6661925911903381,1.5267969369888306,0.523182213306427,0.0,0.0,0.0 +1893,5.696221,1381100,0,2,1,0,0,[],59.62819290161133,0.7671154141426086,-1.8124711513519287,-0.5990286469459534,1.624571442604065,0.5149022340774536,0.0,0.0,0.0 +1895,5.704271,1381101,0,2,1,0,0,[],59.3543701171875,0.7965760231018066,-2.0473053455352783,-0.5184724926948547,1.7418419122695923,0.5049713253974915,0.0,0.0,0.0 +1898,5.712227,1381102,0,2,1,0,0,[],59.0308837890625,0.8313796520233154,-2.3247294425964355,-0.4233066737651825,1.8803805112838745,0.49323928356170654,0.0,0.0,0.0 +1901,5.72022,1381103,0,2,1,0,0,[],58.653602600097656,0.8719714879989624,-2.648292064666748,-0.3123137056827545,2.041959762573242,0.4795560836791992,0.0,0.0,0.0 +1903,5.728216,1381104,0,2,1,0,0,[],58.21847915649414,0.9187910556793213,-3.0214576721191406,-0.18434323370456696,2.2283098697662354,0.4638136029243469,0.0,0.0,0.0 +1905,5.736282,1381105,0,2,1,0,0,[],57.72578811645508,0.9720275402069092,-3.443992853164673,-0.041173454374074936,2.4393341541290283,0.4477355182170868,0.0,0.0,0.0 +1908,5.744209,1381106,0,2,1,0,0,[],57.18168258666992,1.031528353691101,-3.9106132984161377,0.11141803115606308,2.672441005706787,0.4355469346046448,0.0,0.0,0.0 +1911,5.752259,1381107,0,2,1,0,0,[],56.592735290527344,1.097116231918335,-4.4156718254089355,0.2673657536506653,2.9248580932617188,0.43165314197540283,0.0,0.0,0.0 +1913,5.760241,1381108,0,2,1,0,0,[],55.96553039550781,1.1686139106750488,-4.95352029800415,0.42060425877571106,3.193814277648926,0.44045934081077576,0.0,0.0,0.0 +1917,5.76828,1381109,0,2,1,0,0,[],55.306644439697266,1.2458442449569702,-5.518511772155762,0.5650680065155029,3.4765374660491943,0.46637076139450073,0.0,0.0,0.0 +1919,5.776294,1381110,0,2,1,0,0,[],54.62265396118164,1.3286298513412476,-6.104998588562012,0.6946916580200195,3.770256519317627,0.5137926340103149,0.0,0.0,0.0 +1921,5.784271,1381111,0,2,1,0,0,[],53.92013168334961,1.4167934656143188,-6.707333564758301,0.8034095764160156,4.07219934463501,0.5871302485466003,0.0,0.0,0.0 +1925,5.792266,1381112,0,2,1,0,0,[],53.205665588378906,1.5101580619812012,-7.319868087768555,0.8851564526557922,4.379594326019287,0.690788745880127,0.0,0.0,0.0 +1927,5.800251,1381113,0,2,1,0,0,[],52.48582077026367,1.6085461378097534,-7.93695592880249,0.9338666796684265,4.689670085906982,0.8291734457015991,0.0,0.0,0.0 +1929,5.808251,1381114,0,2,1,0,0,[],51.76718521118164,1.7117806673049927,-8.552948951721191,0.9434748888015747,4.999654293060303,1.0066895484924316,0.0,0.0,0.0 +1932,5.816223,1381115,0,2,1,0,0,[],51.056331634521484,1.8196842670440674,-9.162199020385742,0.9079155325889587,5.30677604675293,1.227742314338684,0.0,0.0,0.0 +1935,5.82422,1381116,0,2,1,0,0,[],50.359840393066406,1.9320796728134155,-9.75905990600586,0.8211231231689453,5.6082634925842285,1.4967368841171265,0.0,0.0,0.0 +1937,5.832248,1381117,0,2,1,0,0,[],49.68428421020508,2.0487897396087646,-10.337882995605469,0.6770322918891907,5.9013447761535645,1.8180785179138184,0.0,0.0,0.0 +1941,5.840207,1381118,0,2,1,0,0,[],49.03624725341797,2.1696372032165527,-10.893021583557129,0.46957749128341675,6.183248043060303,2.1961724758148193,0.0,0.0,0.0 +1943,5.848276,1381119,0,2,1,0,0,[],48.42176818847656,2.2944467067718506,-11.41928768157959,0.19329547882080078,6.451427936553955,2.6349575519561768,0.0,0.0,0.0 +1945,5.856222,1381120,0,2,1,0,0,[],47.84242248535156,2.423058032989502,-11.9153470993042,-0.1522408276796341,6.705226898193359,3.134470224380493,0.0,0.0,0.0 +1947,5.864256,1381121,0,2,1,0,0,[],47.297569274902344,2.555319309234619,-12.381784439086914,-0.5649498701095581,6.944929599761963,3.6928038597106934,0.0,0.0,0.0 +1951,5.872255,1381122,0,2,1,0,0,[],46.78653335571289,2.691077947616577,-12.819197654724121,-1.0427316427230835,7.170825481414795,4.308036804199219,0.0,0.0,0.0 +1953,5.880252,1381123,0,2,1,0,0,[],46.30865478515625,2.830181837081909,-13.22818374633789,-1.5834864377975464,7.383205890655518,4.978248596191406,0.0,0.0,0.0 +1955,5.888271,1381124,0,2,1,10,10,"[2, 4]",45.8632698059082,2.9724788665771484,-13.609341621398926,-2.1851141452789307,7.582361221313477,5.701517581939697,0.0,0.0,0.0 +1958,5.896184,1381125,0,2,1,0,0,[],45.449710845947266,3.117816925048828,-13.963269233703613,-2.845515012741089,7.768581867218018,6.475922584533691,0.0,0.0,0.0 +1961,5.904272,1381126,0,2,1,10,0,[],45.06731414794922,3.2660434246063232,-14.290565490722656,-3.562589168548584,7.9421586990356445,7.2995429039001465,0.0,0.0,0.0 +1963,5.912261,1381127,0,2,1,0,0,[],44.71541213989258,3.417006254196167,-14.591826438903809,-4.3342366218566895,8.10338306427002,8.170456886291504,0.0,0.0,0.0 +1967,5.92027,1381128,0,2,1,0,0,[],44.39334487915039,3.5705533027648926,-14.86765193939209,-5.1583571434021,8.252544403076172,9.08674430847168,0.0,0.0,0.0 +1969,5.92822,1381129,0,2,1,0,0,[],44.10044479370117,3.726532459259033,-15.11863899230957,-6.032851696014404,8.389933586120605,10.046483039855957,0.0,0.0,0.0 +1971,5.936288,1381130,0,2,1,0,0,[],43.8360481262207,3.884791135787964,-15.345385551452637,-6.9556193351745605,8.515841484069824,11.047752380371094,0.0,0.0,0.0 +1975,5.944248,1381131,0,2,1,0,0,[],43.599483489990234,4.045177459716797,-15.548490524291992,-7.924561023712158,8.630558967590332,12.088630676269531,0.0,0.0,0.0 +1977,5.952244,1381132,0,2,1,0,0,[],43.39009475708008,4.207539081573486,-15.728551864624023,-8.937576293945312,8.734376907348633,13.167198181152344,0.0,0.0,0.0 +1979,5.960256,1381133,0,2,1,0,0,[],43.20721435546875,4.371723651885986,-15.886167526245117,-9.992565155029297,8.827584266662598,14.281532287597656,0.0,0.0,0.0 +1982,5.968245,1381134,0,2,1,0,0,[],43.050174713134766,4.53757905960083,-16.021934509277344,-11.08742904663086,8.910473823547363,15.429712295532227,0.0,0.0,0.0 +1985,5.9762,1381135,0,2,1,0,0,[],42.918312072753906,4.704953193664551,-16.13645362854004,-12.22006607055664,8.9833345413208,16.609817504882812,0.0,0.0,0.0 +1987,5.984199,1381136,0,2,1,0,0,[],42.81096267700195,4.873693466186523,-16.23031997680664,-13.38837718963623,9.04645824432373,17.81992530822754,0.0,0.0,0.0 +1989,5.992253,1381137,0,2,1,0,0,[],42.72745895385742,5.0436482429504395,-16.30413246154785,-14.590263366699219,9.100133895874023,19.058116912841797,0.0,0.0,0.0 +1993,6.000313,1381138,0,2,1,0,0,[],42.667137145996094,5.214664936065674,-16.358489990234375,-15.823622703552246,9.144654273986816,20.32246971130371,0.0,0.0,0.0 +1995,6.008223,1381139,0,2,1,0,0,[],42.629337310791016,5.386590957641602,-16.39398956298828,-17.08635711669922,9.180309295654297,21.61106300354004,0.0,0.0,0.0 +1997,6.01625,1381140,0,2,1,0,0,[],42.61338424682617,5.559275150299072,-16.411231994628906,-18.376365661621094,9.207387924194336,22.92197608947754,0.0,0.0,0.0 +2001,6.024247,1381141,0,2,1,0,0,[],42.618621826171875,5.7325639724731445,-16.410810470581055,-19.69154930114746,9.22618293762207,24.253286361694336,0.0,0.0,0.0 +2003,6.032253,1381142,0,2,1,0,0,[],42.64438247680664,5.906306266784668,-16.393325805664062,-21.029808044433594,9.236984252929688,25.603073120117188,0.0,0.0,0.0 +2005,6.040248,1381143,0,2,1,0,0,[],42.689998626708984,6.080349445343018,-16.359376907348633,-22.389041900634766,9.240081787109375,26.969417572021484,0.0,0.0,0.0 +2008,6.04817,1381144,0,2,1,0,0,[],42.75480651855469,6.254540920257568,-16.309560775756836,-23.76715087890625,9.235767364501953,28.35039520263672,0.0,0.0,0.0 +2011,6.056226,1381145,0,2,1,0,0,[],42.83814239501953,6.428729057312012,-16.244476318359375,-25.162033081054688,9.22433090209961,29.74408531188965,0.0,0.0,0.0 +2013,6.064274,1381146,0,2,1,0,0,[],42.9393424987793,6.602761268615723,-16.16472053527832,-26.571592330932617,9.206063270568848,31.148569107055664,0.0,0.0,0.0 +2017,6.072289,1381147,0,2,1,0,0,[],43.0577392578125,6.776485443115234,-16.070890426635742,-27.993724822998047,9.181254386901855,32.56192398071289,0.0,0.0,0.0 +2019,6.080226,1381148,0,2,1,0,0,[],43.19266891479492,6.94974946975708,-15.963587760925293,-29.426334381103516,9.150195121765137,33.98223114013672,0.0,0.0,0.0 +2021,6.088249,1381149,0,2,1,0,0,[],43.34346389770508,7.122400760650635,-15.843406677246094,-30.867319107055664,9.113177299499512,35.407562255859375,0.0,0.0,0.0 +2025,6.096275,1381150,0,2,1,0,0,[],43.509464263916016,7.294287204742432,-15.710947036743164,-32.314579010009766,9.070490837097168,36.83600616455078,0.0,0.0,0.0 +2027,6.104269,1381151,0,2,1,0,0,[],43.69000244140625,7.465257167816162,-15.56680679321289,-33.766014099121094,9.022424697875977,38.26563262939453,0.0,0.0,0.0 +2029,6.112216,1381152,0,2,1,0,0,[],43.8844108581543,7.635157585144043,-15.411584854125977,-35.21952438354492,8.96927261352539,39.69452667236328,0.0,0.0,0.0 +2031,6.120264,1381153,0,2,1,0,0,[],44.0920295715332,7.803836822509766,-15.245877265930176,-36.673011779785156,8.911322593688965,41.120765686035156,0.0,0.0,0.0 +2035,6.128263,1381154,0,2,1,0,0,[],44.31218719482422,7.971142292022705,-15.070283889770508,-38.12437438964844,8.84886646270752,42.54242706298828,0.0,0.0,0.0 +2037,6.136277,1381155,0,2,1,0,0,[],44.544227600097656,8.136921882629395,-14.885401725769043,-39.57151412963867,8.782194137573242,43.95759201049805,0.0,0.0,0.0 +2039,6.14425,1381156,0,2,1,0,0,[],44.7874755859375,8.301023483276367,-14.691829681396484,-41.0123291015625,8.711597442626953,45.36433792114258,0.0,0.0,0.0 +2042,6.152247,1381157,0,2,1,0,0,[],45.04127502441406,8.463294982910156,-14.490164756774902,-42.44471740722656,8.63736629486084,46.7607421875,0.0,0.0,0.0 +2045,6.160259,1381158,0,2,1,0,0,[],45.30495834350586,8.623583793640137,-14.281005859375,-43.86658477783203,8.55979061126709,48.1448860168457,0.0,0.0,0.0 +2047,6.168282,1381159,0,2,1,0,0,[],45.57785415649414,8.78173828125,-14.064950942993164,-45.27582931518555,8.479162216186523,49.51484680175781,0.0,0.0,0.0 +2051,6.176192,1381160,0,2,1,0,0,[],45.85930633544922,8.937605857849121,-13.842597961425781,-46.67034912109375,8.395771026611328,50.86870574951172,0.0,0.0,0.0 +2053,6.184205,1381161,0,2,1,0,0,[],46.14864730834961,9.091033935546875,-13.614544868469238,-48.04804611206055,8.309908866882324,52.20454025268555,0.0,0.0,0.0 +2055,6.192248,1381162,0,2,1,0,0,[],46.44520950317383,9.241870880126953,-13.381389617919922,-49.406822204589844,8.221863746643066,53.52042770385742,0.0,0.0,0.0 +2058,6.200272,1381163,0,2,1,0,0,[],46.74833297729492,9.389963150024414,-13.143731117248535,-50.744571685791016,8.131928443908691,54.81444549560547,0.0,0.0,0.0 +2061,6.208222,1381164,0,2,1,0,0,[],47.05734634399414,9.535161018371582,-12.902166366577148,-52.05919647216797,8.040393829345703,56.084678649902344,0.0,0.0,0.0 +2063,6.216261,1381165,0,2,1,0,0,[],47.371585845947266,9.6773099899292,-12.657293319702148,-53.348602294921875,7.947548866271973,57.329200744628906,0.0,0.0,0.0 +2067,6.22429,1381166,0,2,1,0,0,[],47.690391540527344,9.816259384155273,-12.409710884094238,-54.61068344116211,7.85368537902832,58.54609298706055,0.0,0.0,0.0 +2069,6.232219,1381167,0,2,1,0,0,[],48.01309585571289,9.951855659484863,-12.160017013549805,-55.84334182739258,7.759093761444092,59.733436584472656,0.0,0.0,0.0 +2071,6.240279,1381168,0,2,1,0,0,[],48.33903121948242,10.08394718170166,-11.908809661865234,-57.04447555541992,7.664064407348633,60.889305114746094,0.0,0.0,0.0 +2073,6.248288,1381169,0,2,1,0,0,[],48.66753387451172,10.212381362915039,-11.656686782836914,-58.21199035644531,7.568887710571289,62.01177978515625,0.0,0.0,0.0 +2077,6.256277,1381170,0,2,1,0,0,[],48.99794006347656,10.337007522583008,-11.40424633026123,-59.343780517578125,7.473855018615723,63.098941802978516,0.0,0.0,0.0 +2079,6.264224,1381171,0,2,1,0,0,[],49.329586029052734,10.457671165466309,-11.15208625793457,-60.437747955322266,7.379256725311279,64.14886474609375,0.0,0.0,0.0 +2081,6.272268,1381172,0,2,1,0,0,[],49.66180419921875,10.574222564697266,-10.900805473327637,-61.49179458618164,7.2853827476501465,65.15962982177734,0.0,0.0,0.0 +2085,6.280251,1381173,0,2,1,0,0,[],49.99393081665039,10.686507225036621,-10.6510009765625,-62.503814697265625,7.1925249099731445,66.12931823730469,0.0,0.0,0.0 +2087,6.288225,1381174,0,2,1,0,0,[],50.32530212402344,10.794373512268066,-10.403271675109863,-63.471717834472656,7.100972652435303,67.0560073852539,0.0,0.0,0.0 +2089,6.296286,1381175,0,2,1,0,0,[],50.655250549316406,10.897669792175293,-10.158214569091797,-64.39339447021484,7.011017322540283,67.93777465820312,0.0,0.0,0.0 +2092,6.304194,1381176,0,2,1,0,0,[],50.98310852050781,10.996244430541992,-9.916428565979004,-65.26675415039062,6.92294979095459,68.772705078125,0.0,0.0,0.0 +2095,6.312274,1381177,0,2,1,0,0,[],51.30821990966797,11.089942932128906,-9.678512573242188,-66.08969116210938,6.83705997467041,69.55886840820312,0.0,0.0,0.0 +2097,6.320263,1381178,0,2,1,0,0,[],51.629913330078125,11.17861557006836,-9.445062637329102,-66.86009979248047,6.753638744354248,70.29434967041016,0.0,0.0,0.0 +2101,6.328289,1381179,0,2,1,0,0,[],51.94752502441406,11.26210880279541,-9.21667766571045,-67.57588958740234,6.672976493835449,70.97722625732422,0.0,0.0,0.0 +2103,6.336278,1381180,0,2,1,0,0,[],52.2603874206543,11.340270042419434,-8.993956565856934,-68.2349624633789,6.595364570617676,71.60557556152344,0.0,0.0,0.0 +2105,6.344284,1381181,0,2,1,0,0,[],52.567840576171875,11.412948608398438,-8.777496337890625,-68.83521270751953,6.521092891693115,72.17747497558594,0.0,0.0,0.0 +2108,6.352272,1381182,0,2,1,0,0,[],52.86921691894531,11.47999095916748,-8.567895889282227,-69.3745346069336,6.4504523277282715,72.69100952148438,0.0,0.0,0.0 +2111,6.360248,1381183,0,2,1,0,0,[],53.16385269165039,11.541244506835938,-8.365752220153809,-69.85084533691406,6.383733749389648,73.14424896240234,0.0,0.0,0.0 +2113,6.368152,1381184,0,2,1,0,0,[],53.451080322265625,11.596558570861816,-8.171664237976074,-70.26203155517578,6.321227073669434,73.53528594970703,0.0,0.0,0.0 +2115,6.376266,1381185,0,2,1,0,0,[],53.7302360534668,11.645779609680176,-7.986229419708252,-70.60599517822266,6.263223648071289,73.8621826171875,0.0,0.0,0.0 +2118,6.384268,1381186,0,2,1,0,0,[],54.00065612792969,11.688756942749023,-7.810046672821045,-70.8806381225586,6.2100138664245605,74.12303161621094,0.0,0.0,0.0 +2121,6.392249,1381187,0,2,1,0,0,[],54.26167678833008,11.725336074829102,-7.643712997436523,-71.08385467529297,6.161888122558594,74.31591033935547,0.0,0.0,0.0 +2123,6.400261,1381188,0,2,1,0,0,[],54.51262664794922,11.755367279052734,-7.487827777862549,-71.21355438232422,6.119137763977051,74.43888854980469,0.0,0.0,0.0 +2127,6.408316,1381189,0,2,1,0,0,[],54.75284957885742,11.778696060180664,-7.342987537384033,-71.26763916015625,6.082052230834961,74.49004364013672,0.0,0.0,0.0 +2129,6.416243,1381190,0,2,1,0,0,[],54.98167419433594,11.795171737670898,-7.209791660308838,-71.2439956665039,6.0509233474731445,74.46746826171875,0.0,0.0,0.0 +2131,6.424291,1381191,0,2,1,0,0,[],55.19843673706055,11.804641723632812,-7.088837623596191,-71.14053344726562,6.026041030883789,74.3692398071289,0.0,0.0,0.0 +2135,6.432175,1381192,0,2,1,0,0,[],55.40247344970703,11.806953430175781,-6.980720520019531,-70.95516967773438,6.007696151733398,74.19343566894531,0.0,0.0,0.0 +2137,6.440273,1381193,0,2,1,0,0,[],55.59329605102539,11.801998138427734,-6.885760307312012,-70.68736267089844,5.99623966217041,73.93926239013672,0.0,0.0,0.0 +2139,6.448286,1381194,0,2,1,0,0,[],55.770713806152344,11.789743423461914,-6.803775787353516,-70.33940887451172,5.992125511169434,73.60791778564453,0.0,0.0,0.0 +2142,6.456245,1381195,0,2,1,0,0,[],55.9345817565918,11.770164489746094,-6.734533309936523,-69.91387176513672,5.995821952819824,73.20083618164062,0.0,0.0,0.0 +2145,6.464246,1381196,0,2,1,0,0,[],56.084747314453125,11.743237495422363,-6.677799701690674,-69.41333770751953,6.007794380187988,72.71942138671875,0.0,0.0,0.0 +2147,6.472255,1381197,0,2,1,0,0,[],56.2210578918457,11.70893669128418,-6.633341312408447,-68.84039306640625,6.028511047363281,72.16509246826172,0.0,0.0,0.0 +2151,6.480252,1381198,0,2,1,14,14,"[2, 3, 4]",56.34336471557617,11.66723918914795,-6.600923538208008,-68.1976089477539,6.058437347412109,71.5392837524414,0.0,0.0,0.0 +2153,6.488246,1381199,0,2,1,0,0,[],56.451515197753906,11.618118286132812,-6.580313682556152,-67.4875717163086,6.09804105758667,70.84339141845703,0.0,0.0,0.0 +2155,6.496271,1381200,0,2,1,14,0,[],56.54535675048828,11.561552047729492,-6.571277618408203,-66.71285247802734,6.147788047790527,70.078857421875,0.0,0.0,0.0 +2157,6.504287,1381201,0,2,1,0,0,[],56.6247444152832,11.497514724731445,-6.573581218719482,-65.87604522705078,6.208146095275879,69.24707794189453,0.0,0.0,0.0 +2161,6.512269,1381202,0,2,1,0,0,[],56.68952178955078,11.425981521606445,-6.586991310119629,-64.9797134399414,6.279581069946289,68.3494873046875,0.0,0.0,0.0 +2163,6.52027,1381203,0,2,1,0,0,[],56.73953628540039,11.346928596496582,-6.611274719238281,-64.02645111083984,6.362560749053955,67.38749694824219,0.0,0.0,0.0 +2165,6.528267,1381204,0,2,1,0,0,[],56.7746467590332,11.260331153869629,-6.646196365356445,-63.018821716308594,6.457551002502441,66.36253356933594,0.0,0.0,0.0 +2168,6.536289,1381205,0,2,1,0,0,[],56.7946891784668,11.166165351867676,-6.691524028778076,-61.95942306518555,6.565019130706787,65.2760009765625,0.0,0.0,0.0 +2171,6.544261,1381206,0,2,1,0,0,[],56.79952621459961,11.06440544128418,-6.747023105621338,-60.85082244873047,6.685431480407715,64.12933349609375,0.0,0.0,0.0 +2173,6.552283,1381207,0,2,1,0,0,[],56.78899383544922,10.955028533935547,-6.812459945678711,-59.69560623168945,6.8192548751831055,62.92394256591797,0.0,0.0,0.0 +2177,6.560251,1381208,0,2,1,0,0,[],56.7629508972168,10.838008880615234,-6.887601375579834,-58.49635314941406,6.966956615447998,61.66124725341797,0.0,0.0,0.0 +2179,6.568283,1381209,0,2,1,0,0,[],56.72124099731445,10.713322639465332,-6.972212791442871,-57.255638122558594,7.129003047943115,60.3426628112793,0.0,0.0,0.0 +2181,6.57631,1381210,0,2,1,0,0,[],56.66371536254883,10.580945014953613,-7.066061496734619,-55.97604751586914,7.305861473083496,58.9696159362793,0.0,0.0,0.0 +2185,6.584262,1381211,0,2,1,0,0,[],56.59022521972656,10.440851211547852,-7.1689133644104,-54.660160064697266,7.497997760772705,57.54351806640625,0.0,0.0,0.0 +2187,6.592275,1381212,0,2,1,0,0,[],56.500614166259766,10.293018341064453,-7.280534744262695,-53.31055450439453,7.705879211425781,56.065792083740234,0.0,0.0,0.0 +2189,6.6003,1381213,0,2,1,0,0,[],56.39473342895508,10.137420654296875,-7.400691986083984,-51.929805755615234,7.929973125457764,54.5378532409668,0.0,0.0,0.0 +2192,6.608268,1381214,0,2,1,0,0,[],56.272438049316406,9.97403335571289,-7.529151439666748,-50.52050018310547,8.170744895935059,52.96112060546875,0.0,0.0,0.0 +2195,6.616263,1381215,0,2,1,0,0,[],56.133567810058594,9.80283260345459,-7.665679454803467,-49.0852165222168,8.42866325378418,51.33701705932617,0.0,0.0,0.0 +2197,6.624219,1381216,0,2,1,0,0,[],55.97797393798828,9.623794555664062,-7.810041904449463,-47.62653350830078,8.704193115234375,49.66695785522461,0.0,0.0,0.0 +2199,6.632246,1381217,0,2,1,0,0,[],55.805511474609375,9.436893463134766,-7.962005138397217,-46.147029876708984,8.997801780700684,47.952362060546875,0.0,0.0,0.0 +2202,6.640252,1381218,0,2,1,0,0,[],55.616024017333984,9.242105484008789,-8.121335983276367,-44.649288177490234,9.309956550598145,46.19464874267578,0.0,0.0,0.0 +2205,6.648287,1381219,0,2,1,0,0,[],55.40936279296875,9.039405822753906,-8.287800788879395,-43.135887145996094,9.64112377166748,44.39523696899414,0.0,0.0,0.0 +2207,6.656264,1381220,0,2,1,0,0,[],55.18537521362305,8.82876968383789,-8.461165428161621,-41.609405517578125,9.99177074432373,42.5555419921875,0.0,0.0,0.0 +2211,6.664276,1381221,0,2,1,0,0,[],54.94397735595703,8.610200881958008,-8.641189575195312,-40.0723991394043,10.36229133605957,40.67711639404297,0.0,0.0,0.0 +2213,6.672247,1381222,0,2,1,0,0,[],54.68583297729492,8.383989334106445,-8.827570915222168,-38.52714157104492,10.752269744873047,38.762935638427734,0.0,0.0,0.0 +2215,6.680267,1381223,0,2,1,0,0,[],54.41206359863281,8.150609016418457,-9.019962310791016,-36.975746154785156,11.160787582397461,36.81686782836914,0.0,0.0,0.0 +2218,6.688251,1381224,0,2,1,0,0,[],54.12379837036133,7.910537242889404,-9.218017578125,-35.42030715942383,11.586917877197266,34.842796325683594,0.0,0.0,0.0 +2221,6.696259,1381225,0,2,1,0,0,[],53.822174072265625,7.664249420166016,-9.421392440795898,-33.86293029785156,12.029733657836914,32.844608306884766,0.0,0.0,0.0 +2223,6.704225,1381226,0,2,1,14,14,"[2, 3, 4]",53.50831985473633,7.412222862243652,-9.629739761352539,-32.30571365356445,12.488306045532227,30.826189041137695,0.0,0.0,0.0 +2227,6.712268,1381227,0,2,1,0,0,[],53.18336486816406,7.154932975769043,-9.842713356018066,-30.750762939453125,12.961709976196289,28.79141616821289,0.0,0.0,0.0 +2229,6.720286,1381228,0,2,1,14,0,[],52.84844207763672,6.892857074737549,-10.059968948364258,-29.200178146362305,13.449018478393555,26.744176864624023,0.0,0.0,0.0 +2231,6.728291,1381229,0,2,1,0,0,[],52.50468826293945,6.626471042633057,-10.281159400939941,-27.65605926513672,13.949302673339844,24.688352584838867,0.0,0.0,0.0 +2235,6.7363,1381230,0,2,1,0,0,[],52.15322494506836,6.356251239776611,-10.505939483642578,-26.120512008666992,14.461636543273926,22.627830505371094,0.0,0.0,0.0 +2237,6.744264,1381231,0,2,1,0,0,[],51.79519271850586,6.0826735496521,-10.733962059020996,-24.59563636779785,14.985093116760254,20.566492080688477,0.0,0.0,0.0 +2239,6.752262,1381232,0,2,1,0,0,[],51.43172073364258,5.806215286254883,-10.964882850646973,-23.083532333374023,15.518744468688965,18.508220672607422,0.0,0.0,0.0 +2241,6.760302,1381233,0,2,1,0,0,[],51.06393814086914,5.527352809906006,-11.198354721069336,-21.5863037109375,16.061664581298828,16.45690155029297,0.0,0.0,0.0 +2245,6.768286,1381234,0,2,1,0,0,[],50.69297790527344,5.246561527252197,-11.434032440185547,-20.10605239868164,16.612926483154297,14.416418075561523,0.0,0.0,0.0 +2247,6.776299,1381235,0,2,1,0,0,[],50.31997299194336,4.96431827545166,-11.67156982421875,-18.644880294799805,17.171600341796875,12.390652656555176,0.0,0.0,0.0 +2249,6.78429,1381236,0,2,1,0,0,[],49.94605255126953,4.681099891662598,-11.910621643066406,-17.20488929748535,17.73676300048828,10.383488655090332,0.0,0.0,0.0 +2252,6.792206,1381237,0,2,1,0,0,[],49.572349548339844,4.3973822593688965,-12.15084171295166,-15.788178443908691,18.307485580444336,8.398812294006348,0.0,0.0,0.0 +2255,6.800273,1381238,0,2,1,0,0,[],49.19999313354492,4.113641738891602,-12.39188289642334,-14.396852493286133,18.882841110229492,6.440505027770996,0.0,0.0,0.0 +2257,6.808286,1381239,0,2,1,0,0,[],48.83012008666992,3.830355167388916,-12.633400917053223,-13.033013343811035,19.46190071105957,4.512451171875,0.0,0.0,0.0 +2261,6.816273,1381240,0,2,1,0,0,[],48.4638557434082,3.5479981899261475,-12.875048637390137,-11.698760986328125,20.04374122619629,2.618534564971924,0.0,0.0,0.0 +2263,6.82427,1381241,0,2,1,0,0,[],48.10233688354492,3.267047882080078,-13.11648178100586,-10.396198272705078,20.627431869506836,0.762639045715332,0.0,0.0,0.0 +2265,6.832273,1381242,0,2,1,0,0,[],47.74668884277344,2.9879801273345947,-13.357353210449219,-9.127426147460938,21.212047576904297,-1.0513520240783691,0.0,0.0,0.0 +2269,6.840251,1381243,0,2,1,0,0,[],47.39805221557617,2.7112715244293213,-13.59731674194336,-7.894547939300537,21.796659469604492,-2.8195548057556152,0.0,0.0,0.0 +2271,6.84828,1381244,0,2,1,0,0,[],47.057552337646484,2.4373981952667236,-13.836027145385742,-6.69966459274292,22.380342483520508,-4.5380859375,0.0,0.0,0.0 +2273,6.856262,1381245,0,2,1,0,0,[],46.726318359375,2.166836977005005,-14.073139190673828,-5.544878005981445,22.962167739868164,-6.203061103820801,0.0,0.0,0.0 +2276,6.864195,1381246,0,2,1,0,0,[],46.40549087524414,1.9000638723373413,-14.308305740356445,-4.4322896003723145,23.541210174560547,-7.8105974197387695,0.0,0.0,0.0 +2279,6.872286,1381247,0,2,1,0,0,[],46.09619140625,1.6375553607940674,-14.541181564331055,-3.364001989364624,24.116540908813477,-9.356810569763184,0.0,0.0,0.0 +2281,6.880256,1381248,0,2,1,0,0,[],45.799556732177734,1.379787802696228,-14.7714204788208,-2.342116117477417,24.687232971191406,-10.83781623840332,0.0,0.0,0.0 +2283,6.888245,1381249,0,2,1,0,0,[],45.516719818115234,1.1272376775741577,-14.998676300048828,-1.3687344789505005,25.252361297607422,-12.249732971191406,0.0,0.0,0.0 +2287,6.896258,1381250,0,2,1,0,0,[],45.248809814453125,0.8803811073303223,-15.222603797912598,-0.4459584057331085,25.810997009277344,-13.588674545288086,0.0,0.0,0.0 +2289,6.904252,1381251,0,2,1,0,0,[],44.9969596862793,0.6396946907043457,-15.442856788635254,0.4241100549697876,26.362213134765625,-14.850759506225586,0.0,0.0,0.0 +2291,6.912256,1381252,0,2,1,0,0,[],44.762298583984375,0.4056546986103058,-15.659090042114258,1.2393691539764404,26.905080795288086,-16.032102584838867,0.0,0.0,0.0 +2295,6.920267,1381253,0,2,1,0,0,[],44.54595947265625,0.17873752117156982,-15.870956420898438,1.9977171421051025,27.438676834106445,-17.128820419311523,0.0,0.0,0.0 +2297,6.928269,1381254,0,2,1,0,0,[],44.34907150268555,-0.040580447763204575,-16.07811164855957,2.697052001953125,27.962072372436523,-18.13702964782715,0.0,0.0,0.0 +2299,6.936287,1381255,0,2,1,0,0,[],44.17277145385742,-0.2518228590488434,-16.28020668029785,3.3352723121643066,28.47433853149414,-19.052845001220703,0.0,0.0,0.0 +2302,6.944282,1381256,0,2,1,0,0,[],44.018184661865234,-0.4545133411884308,-16.476900100708008,3.910275936126709,28.974552154541016,-19.872385025024414,0.0,0.0,0.0 +2305,6.952212,1381257,0,2,1,0,0,[],43.886451721191406,-0.6481754779815674,-16.667842864990234,4.419960975646973,29.461782455444336,-20.591766357421875,0.0,0.0,0.0 +2307,6.960279,1381258,0,2,1,0,0,[],43.77869415283203,-0.832332968711853,-16.852689743041992,4.8622260093688965,29.935102462768555,-21.207103729248047,0.0,0.0,0.0 +2311,6.968349,1381259,0,2,1,0,0,[],43.696048736572266,-1.0065094232559204,-17.03109359741211,5.234969139099121,30.393587112426758,-21.714513778686523,0.0,0.0,0.0 +2313,6.976281,1381260,0,2,1,0,0,[],43.639644622802734,-1.170228362083435,-17.202713012695312,5.536088466644287,30.8363094329834,-22.1101131439209,0.0,0.0,0.0 +2315,6.984319,1381261,0,2,1,0,0,[],43.610618591308594,-1.3230136632919312,-17.367197036743164,5.763481616973877,31.262340545654297,-22.390016555786133,0.0,0.0,0.0 +2319,6.992276,1381262,0,2,1,0,0,[],43.6100959777832,-1.4643886089324951,-17.524202346801758,5.915048122406006,31.670753479003906,-22.550342559814453,0.0,0.0,0.0 +2321,7.000291,1381263,0,2,1,0,0,[],43.63921356201172,-1.5938771963119507,-17.673381805419922,5.988685131072998,32.06062316894531,-22.587207794189453,0.0,0.0,0.0 +2323,7.008265,1381264,0,2,1,0,0,[],43.698970794677734,-1.7110426425933838,-17.814414978027344,5.982594966888428,32.431087493896484,-22.49722671508789,0.0,0.0,0.0 +2325,7.016224,1381265,0,2,1,0,0,[],43.789100646972656,-1.8158531188964844,-17.94723129272461,5.898062229156494,32.78196334838867,-22.28207015991211,0.0,0.0,0.0 +2328,7.024258,1381266,0,2,1,0,0,[],43.908592224121094,-1.9085112810134888,-18.0718994140625,5.738160610198975,33.11344909667969,-21.946352005004883,0.0,0.0,0.0 +2331,7.032269,1381267,0,2,1,0,0,[],44.056419372558594,-1.9892228841781616,-18.18849754333496,5.505985260009766,33.42576599121094,-21.494718551635742,0.0,0.0,0.0 +2333,7.040274,1381268,0,2,1,0,0,[],44.231563568115234,-2.0581936836242676,-18.297096252441406,5.2046332359313965,33.719120025634766,-20.931814193725586,0.0,0.0,0.0 +2336,7.048266,1381269,0,2,1,10,10,"[2, 4]",44.433006286621094,-2.1156294345855713,-18.39777374267578,4.8371992111206055,33.99372482299805,-20.26228904724121,0.0,0.0,0.0 +2339,7.056293,1381270,0,2,1,0,0,[],44.65972137451172,-2.1617355346679688,-18.490602493286133,4.406780242919922,34.24979019165039,-19.490787506103516,0.0,0.0,0.0 +2341,7.064278,1381271,0,2,1,10,0,[],44.91068649291992,-2.196718215942383,-18.575654983520508,3.9164717197418213,34.48752212524414,-18.621957778930664,0.0,0.0,0.0 +2345,7.07228,1381272,0,2,1,0,0,[],45.18488311767578,-2.220782518386841,-18.65300941467285,3.3693697452545166,34.7071418762207,-17.660446166992188,0.0,0.0,0.0 +2347,7.080205,1381273,0,2,1,0,0,[],45.48128890991211,-2.2341349124908447,-18.72273826599121,2.7685701847076416,34.90885543823242,-16.610898971557617,0.0,0.0,0.0 +2349,7.088281,1381274,0,2,1,0,0,[],45.798885345458984,-2.236980438232422,-18.784914016723633,2.11716890335083,35.092872619628906,-15.477965354919434,0.0,0.0,0.0 +2352,7.096258,1381275,0,2,1,0,0,[],46.13664627075195,-2.229525327682495,-18.839614868164062,1.418262243270874,35.25940704345703,-14.266289710998535,0.0,0.0,0.0 +2355,7.104269,1381276,0,2,1,0,0,[],46.49354934692383,-2.211974859237671,-18.886911392211914,0.6749456524848938,35.408668518066406,-12.98051929473877,0.0,0.0,0.0 +2357,7.112315,1381277,0,2,1,0,0,[],46.86857604980469,-2.184535264968872,-18.9268798828125,-0.10968462377786636,35.540870666503906,-11.6253023147583,0.0,0.0,0.0 +2361,7.120303,1381278,0,2,1,0,0,[],47.26070785522461,-2.147411584854126,-18.9595947265625,-0.9325327277183533,35.656219482421875,-10.205284118652344,0.0,0.0,0.0 +2363,7.128294,1381279,0,2,1,0,0,[],47.66891860961914,-2.1008100509643555,-18.985130310058594,-1.7905027866363525,35.75493240356445,-8.725112915039062,0.0,0.0,0.0 +2365,7.136301,1381280,0,2,1,0,0,[],48.092185974121094,-2.044936180114746,-19.00356101989746,-2.6804986000061035,35.83721923828125,-7.189434051513672,0.0,0.0,0.0 +2367,7.14426,1381281,0,2,1,0,0,[],48.52949142456055,-1.979995846748352,-19.01496124267578,-3.5994246006011963,35.90328598022461,-5.602895736694336,0.0,0.0,0.0 +2371,7.152292,1381282,0,2,1,0,0,[],48.97981262207031,-1.9061944484710693,-19.0194034576416,-4.544184684753418,35.95335006713867,-3.970144510269165,0.0,0.0,0.0 +2373,7.160215,1381283,0,2,1,0,0,[],49.4421272277832,-1.8237379789352417,-19.016963958740234,-5.511682510375977,35.98761749267578,-2.2958266735076904,0.0,0.0,0.0 +2375,7.168286,1381284,0,2,1,0,0,[],49.91541290283203,-1.7328320741653442,-19.00771713256836,-6.4988226890563965,36.00630569458008,-0.5845897793769836,0.0,0.0,0.0 +2379,7.176266,1381285,0,2,1,0,0,[],50.39865493774414,-1.633682370185852,-18.991737365722656,-7.502509117126465,36.009620666503906,1.1589196920394897,0.0,0.0,0.0 +2381,7.18428,1381286,0,2,1,0,0,[],50.89081954956055,-1.5264946222305298,-18.969097137451172,-8.519645690917969,35.99777603149414,2.9300546646118164,0.0,0.0,0.0 +2383,7.192283,1381287,0,2,1,0,0,[],51.39089584350586,-1.411474585533142,-18.93987274169922,-9.547137260437012,35.97098159790039,4.72416877746582,0.0,0.0,0.0 +2386,7.200297,1381288,0,2,1,0,0,[],51.89786148071289,-1.288827896118164,-18.904136657714844,-10.581886291503906,35.929447174072266,6.536614418029785,0.0,0.0,0.0 +2389,7.208261,1381289,0,2,1,0,0,[],52.41068649291992,-1.15876042842865,-18.86196517944336,-11.620798110961914,35.873390197753906,8.36274528503418,0.0,0.0,0.0 +2391,7.216275,1381290,0,2,1,0,0,[],52.92835998535156,-1.0214776992797852,-18.813432693481445,-12.66077709197998,35.80301284790039,10.197914123535156,0.0,0.0,0.0 +2395,7.22429,1381291,0,2,1,0,0,[],53.449851989746094,-0.8771854043006897,-18.75861167907715,-13.698725700378418,35.718536376953125,12.037473678588867,0.0,0.0,0.0 +2397,7.232241,1381292,0,2,1,0,0,[],53.974143981933594,-0.7260894179344177,-18.69757843017578,-14.731549263000488,35.62016296386719,13.876778602600098,0.0,0.0,0.0 +2399,7.240306,1381293,0,2,1,0,0,[],54.50021743774414,-0.5683953762054443,-18.630407333374023,-15.75615119934082,35.50811004638672,15.711180686950684,0.0,0.0,0.0 +2402,7.248261,1381294,0,2,1,0,0,[],55.02704620361328,-0.4043090343475342,-18.557170867919922,-16.76943588256836,35.38258361816406,17.536033630371094,0.0,0.0,0.0 +2405,7.256274,1381295,0,2,1,0,0,[],55.553611755371094,-0.23403601348400116,-18.477943420410156,-17.768308639526367,35.24380111694336,19.346691131591797,0.0,0.0,0.0 +2407,7.26427,1381296,0,2,1,0,0,[],56.07889175415039,-0.05778209865093231,-18.39280128479004,-18.749670028686523,35.09196853637695,21.138504028320312,0.0,0.0,0.0 +2409,7.272295,1381297,0,2,1,0,0,[],56.601863861083984,0.12424702942371368,-18.301816940307617,-19.71042823791504,34.92729568481445,22.906827926635742,0.0,0.0,0.0 +2412,7.280271,1381298,0,2,1,0,0,[],57.12150955200195,0.31184566020965576,-18.205066680908203,-20.647483825683594,34.75,24.647014617919922,0.0,0.0,0.0 +2415,7.28827,1381299,0,2,1,0,0,[],57.636802673339844,0.5048080682754517,-18.102622985839844,-21.557743072509766,34.5602912902832,26.35441780090332,0.0,0.0,0.0 +2417,7.296277,1381300,0,2,1,0,0,[],58.146724700927734,0.7029285430908203,-17.99456214904785,-22.438108444213867,34.35837936401367,28.024391174316406,0.0,0.0,0.0 +2421,7.304372,1381301,0,2,1,0,0,[],58.65025329589844,0.9060013890266418,-17.88095474243164,-23.285486221313477,34.14447021484375,29.652286529541016,0.0,0.0,0.0 +2423,7.312278,1381302,0,2,1,0,0,[],59.14636993408203,1.1138207912445068,-17.76188087463379,-24.096778869628906,33.918785095214844,31.233457565307617,0.0,0.0,0.0 +2425,7.320297,1381303,0,2,1,0,0,[],59.63404846191406,1.3261812925338745,-17.637409210205078,-24.8688907623291,33.68152618408203,32.76325607299805,0.0,0.0,0.0 +2429,7.328272,1381304,0,2,1,0,0,[],60.112266540527344,1.542876958847046,-17.507617950439453,-25.598726272583008,33.43291091918945,34.23704147338867,0.0,0.0,0.0 +2431,7.336301,1381305,0,2,1,0,0,[],60.58000946044922,1.7637020349502563,-17.37257957458496,-26.283187866210938,33.17314529418945,35.6501579284668,0.0,0.0,0.0 +2433,7.344299,1381306,0,2,1,0,0,[],61.036251068115234,1.9884510040283203,-17.23236846923828,-26.91918182373047,32.90244674682617,36.997962951660156,0.0,0.0,0.0 +2436,7.352298,1381307,0,2,1,0,0,[],61.4799690246582,2.2169179916381836,-17.087060928344727,-27.503610610961914,32.62102127075195,38.27581024169922,0.0,0.0,0.0 +2439,7.360267,1381308,0,2,1,0,0,[],61.91014099121094,2.448897361755371,-16.936729431152344,-28.03338050842285,32.32908248901367,39.47904968261719,0.0,0.0,0.0 +2441,7.368276,1381309,0,2,1,0,0,[],62.32575225830078,2.6841835975646973,-16.781448364257812,-28.50539207458496,32.02684020996094,40.6030387878418,0.0,0.0,0.0 +2445,7.37629,1381310,0,2,1,0,0,[],62.725772857666016,2.9225707054138184,-16.621292114257812,-28.91655158996582,31.714508056640625,41.643131256103516,0.0,0.0,0.0 +2447,7.384223,1381311,0,2,1,0,0,[],63.109188079833984,3.1638529300689697,-16.456335067749023,-29.263763427734375,31.39229393005371,42.59467315673828,0.0,0.0,0.0 +2449,7.392294,1381312,0,2,1,0,0,[],63.474971771240234,3.407824754714966,-16.286653518676758,-29.54393196105957,31.06041145324707,43.453025817871094,0.0,0.0,0.0 +2451,7.400319,1381313,0,2,1,0,0,[],63.82210159301758,3.654280424118042,-16.11231803894043,-29.753957748413086,30.719072341918945,44.213539123535156,0.0,0.0,0.0 +2455,7.408281,1381314,0,2,1,0,0,[],64.1495590209961,3.9030139446258545,-15.933405876159668,-29.890748977661133,30.368484497070312,44.87156295776367,0.0,0.0,0.0 +2457,7.416267,1381315,0,2,1,0,0,[],64.45632934570312,4.153820037841797,-15.749991416931152,-29.951208114624023,30.008861541748047,45.422454833984375,0.0,0.0,0.0 +2459,7.424299,1381316,0,2,1,0,0,[],64.74142456054688,4.406497478485107,-15.562151908874512,-29.932403564453125,29.640419006347656,45.86179733276367,0.0,0.0,0.0 +2462,7.432266,1381317,0,2,1,0,0,[],65.00468444824219,4.660923480987549,-15.370050430297852,-29.83422088623047,29.26347541809082,46.18914031982422,0.0,0.0,0.0 +2465,7.440279,1381318,0,2,1,0,0,[],65.24661254882812,4.917041778564453,-15.173914909362793,-29.65890884399414,28.878421783447266,46.40736770629883,0.0,0.0,0.0 +2471,7.448311,1381319,0,2,1,0,0,[],65.46772766113281,5.174797534942627,-14.973979949951172,-29.408790588378906,28.485660552978516,46.51945877075195,0.0,0.0,0.0 +2475,7.456306,1381320,0,2,1,0,0,[],65.66857147216797,5.434135913848877,-14.770476341247559,-29.08618927001953,28.085590362548828,46.52839279174805,0.0,0.0,0.0 +2477,7.464289,1381321,0,2,1,10,10,"[2, 4]",65.84967041015625,5.695003032684326,-14.563636779785156,-28.69342613220215,27.678611755371094,46.437156677246094,0.0,0.0,0.0 +2479,7.472265,1381322,0,2,1,0,0,[],66.01153564453125,5.957343578338623,-14.353693008422852,-28.232824325561523,27.265119552612305,46.24872589111328,0.0,0.0,0.0 +2483,7.480284,1381323,0,2,1,10,0,[],66.15470886230469,6.221103191375732,-14.140877723693848,-27.70670509338379,26.845518112182617,45.96609115600586,0.0,0.0,0.0 +2485,7.488276,1381324,0,2,1,0,0,[],66.27971649169922,6.486227035522461,-13.925422668457031,-27.11739158630371,26.420204162597656,45.592227935791016,0.0,0.0,0.0 +2487,7.496294,1381325,0,2,1,0,0,[],66.38707733154297,6.752660751342773,-13.707559585571289,-26.467205047607422,25.98957633972168,45.13011932373047,0.0,0.0,0.0 +2490,7.504256,1381326,0,2,1,0,0,[],66.4773178100586,7.020349979400635,-13.48752212524414,-25.75847053527832,25.554035186767578,44.58274841308594,0.0,0.0,0.0 +2493,7.512274,1381327,0,2,1,0,0,[],66.55097961425781,7.289239883422852,-13.265542030334473,-24.993507385253906,25.11397933959961,43.953094482421875,0.0,0.0,0.0 +2495,7.520303,1381328,0,2,1,0,0,[],66.60858154296875,7.559276103973389,-13.041850090026855,-24.174640655517578,24.66980743408203,43.244144439697266,0.0,0.0,0.0 +2497,7.528301,1381329,0,2,1,0,0,[],66.65064239501953,7.8304033279418945,-12.816679954528809,-23.304189682006836,24.221920013427734,42.45887756347656,0.0,0.0,0.0 +2500,7.536285,1381330,0,2,1,0,0,[],66.67770385742188,8.102567672729492,-12.590263366699219,-22.384479522705078,23.770713806152344,41.600276947021484,0.0,0.0,0.0 +2503,7.544162,1381331,0,2,1,0,0,[],66.69027709960938,8.375714302062988,-12.362833023071289,-21.41783332824707,23.31658935546875,40.671321868896484,0.0,0.0,0.0 +2505,7.552293,1381332,0,2,1,0,0,[],66.68890380859375,8.649788856506348,-12.13461971282959,-20.406570434570312,22.859947204589844,39.67499923706055,0.0,0.0,0.0 +2509,7.560296,1381333,0,2,1,0,0,[],66.67410278320312,8.924736022949219,-11.90585708618164,-19.35301399230957,22.401185989379883,38.61428451538086,0.0,0.0,0.0 +2511,7.568278,1381334,0,2,1,0,0,[],66.64640808105469,9.200502395629883,-11.676776885986328,-18.25948715209961,21.940702438354492,37.492164611816406,0.0,0.0,0.0 +2513,7.576318,1381335,0,2,1,0,0,[],66.60633850097656,9.477031707763672,-11.447611808776855,-17.128313064575195,21.478897094726562,36.31161880493164,0.0,0.0,0.0 +2516,7.584282,1381336,0,2,1,0,0,[],66.55442810058594,9.754270553588867,-11.218592643737793,-15.961812973022461,21.016170501708984,35.07563018798828,0.0,0.0,0.0 +2519,7.59229,1381337,0,2,1,0,0,[],66.49120330810547,10.032164573669434,-10.989952087402344,-14.762309074401855,20.552921295166016,33.78718566894531,0.0,0.0,0.0 +2521,7.600292,1381338,0,2,1,0,0,[],66.41718292236328,10.31065845489502,-10.761923789978027,-13.532124519348145,20.089548110961914,32.449256896972656,0.0,0.0,0.0 +2525,7.608307,1381339,0,2,1,0,0,[],66.3329086303711,10.58969783782959,-10.534737586975098,-12.273581504821777,19.626449584960938,31.064834594726562,0.0,0.0,0.0 +2527,7.61627,1381340,0,2,1,0,0,[],66.2388916015625,10.86922836303711,-10.30862808227539,-10.989002227783203,19.164026260375977,29.63689613342285,0.0,0.0,0.0 +2529,7.624286,1381341,0,2,1,0,0,[],66.13566589355469,11.149194717407227,-10.08382511138916,-9.680708885192871,18.70267677307129,28.168426513671875,0.0,0.0,0.0 +2533,7.632284,1381342,0,2,1,0,0,[],66.02376556396484,11.429543495178223,-9.860563278198242,-8.351024627685547,18.2427978515625,26.662405014038086,0.0,0.0,0.0 +2535,7.640288,1381343,0,2,1,0,0,[],65.9037094116211,11.71021842956543,-9.63907241821289,-7.002270698547363,17.784793853759766,25.121814727783203,0.0,0.0,0.0 +2537,7.648305,1381344,0,2,1,0,0,[],65.7760238647461,11.991166114807129,-9.419586181640625,-5.636770725250244,17.329059600830078,23.549638748168945,0.0,0.0,0.0 +2539,7.656296,1381345,0,2,1,0,0,[],65.6412353515625,12.272332191467285,-9.202337265014648,-4.2568464279174805,16.875995635986328,21.9488582611084,0.0,0.0,0.0 +2543,7.664272,1381346,0,2,1,0,0,[],65.4998779296875,12.553661346435547,-8.987555503845215,-2.8648200035095215,16.426000595092773,20.32245445251465,0.0,0.0,0.0 +2545,7.672275,1381347,0,2,1,0,0,[],65.35247802734375,12.835099220275879,-8.77547550201416,-1.4630144834518433,15.979475021362305,18.673410415649414,0.0,0.0,0.0 +2547,7.680293,1381348,0,2,1,0,0,[],65.1995620727539,13.11659049987793,-8.566329002380371,-0.05375171825289726,15.536816596984863,17.004709243774414,0.0,0.0,0.0 +2550,7.688306,1381349,0,2,1,0,0,[],65.0416488647461,13.39808177947998,-8.360346794128418,1.3606456518173218,15.09842586517334,15.319329261779785,0.0,0.0,0.0 +2553,7.696278,1381350,0,2,1,0,0,[],64.8792724609375,13.679518699645996,-8.15776252746582,2.777855396270752,14.664701461791992,13.620255470275879,0.0,0.0,0.0 +2555,7.704293,1381351,0,2,1,0,0,[],64.71295928955078,13.960844993591309,-7.958807945251465,4.195554733276367,14.236042022705078,11.910470008850098,0.0,0.0,0.0 +2559,7.712309,1381352,0,2,1,0,0,[],64.5432357788086,14.2420072555542,-7.763714790344238,5.611422061920166,13.812847137451172,10.192953109741211,0.0,0.0,0.0 +2561,7.720276,1381353,0,2,1,0,0,[],64.37063598632812,14.522950172424316,-7.572716236114502,7.023134231567383,13.395515441894531,8.470687866210938,0.0,0.0,0.0 +2563,7.728288,1381354,0,2,1,0,0,[],64.1956787109375,14.803620338439941,-7.386043548583984,8.428369522094727,12.984447479248047,6.7466559410095215,0.0,0.0,0.0 +2567,7.736269,1381355,0,2,1,0,0,[],64.01889038085938,15.083962440490723,-7.203929424285889,9.824804306030273,12.58004093170166,5.023839950561523,0.0,0.0,0.0 +2569,7.74431,1381356,0,2,1,0,0,[],63.840797424316406,15.363921165466309,-7.026605606079102,11.21011734008789,12.182695388793945,3.30522084236145,0.0,0.0,0.0 +2571,7.752304,1381357,0,2,1,0,0,[],63.66193771362305,15.64344310760498,-6.854304790496826,12.581986427307129,11.792811393737793,1.593781590461731,0.0,0.0,0.0 +2574,7.760291,1381358,0,2,1,0,0,[],63.482826232910156,15.922472953796387,-6.687259197235107,13.938088417053223,11.410785675048828,-0.10749620944261551,0.0,0.0,0.0 +2577,7.768281,1381359,0,2,1,0,0,[],63.30399703979492,16.200956344604492,-6.525700569152832,15.276101112365723,11.037019729614258,-1.7956305742263794,0.0,0.0,0.0 +2579,7.776348,1381360,0,2,1,0,0,[],63.125972747802734,16.478839874267578,-6.369861125946045,16.59370231628418,10.671911239624023,-3.467639446258545,0.0,0.0,0.0 +2581,7.784306,1381361,0,2,1,0,0,[],62.94928741455078,16.756065368652344,-6.219973564147949,17.888568878173828,10.3158597946167,-5.120540618896484,0.0,0.0,0.0 +2585,7.792318,1381362,0,2,1,0,0,[],62.77445983886719,17.032583236694336,-6.076269626617432,19.15838050842285,9.96926498413086,-6.751352310180664,0.0,0.0,0.0 +2587,7.800342,1381363,0,2,1,0,0,[],62.60202407836914,17.308334350585938,-5.938981533050537,20.40081214904785,9.632525444030762,-8.35709285736084,0.0,0.0,0.0 +2589,7.808288,1381364,0,2,1,0,0,[],62.43250274658203,17.58326530456543,-5.8083415031433105,21.613542556762695,9.306039810180664,-9.934779167175293,0.0,0.0,0.0 +2593,7.816294,1381365,0,2,1,0,0,[],62.266422271728516,17.857324600219727,-5.684582233428955,22.794248580932617,8.990208625793457,-11.481431007385254,0.0,0.0,0.0 +2595,7.824273,1381366,0,2,1,0,0,[],62.104312896728516,18.13045310974121,-5.567935466766357,23.940610885620117,8.685430526733398,-12.994064331054688,0.0,0.0,0.0 +2597,7.832291,1381367,0,2,1,0,0,[],61.94670486450195,18.402599334716797,-5.458632946014404,25.050304412841797,8.392104148864746,-14.469698905944824,0.0,0.0,0.0 +2600,7.840272,1381368,0,2,1,0,0,[],61.79411697387695,18.673707962036133,-5.356907844543457,26.12100601196289,8.110629081726074,-15.905351638793945,0.0,0.0,0.0 +2603,7.848284,1381369,0,2,1,0,0,[],61.64707946777344,18.943723678588867,-5.262991428375244,27.150394439697266,7.841404438018799,-17.29804039001465,0.0,0.0,0.0 +2605,7.856325,1381370,0,2,1,0,0,[],61.50612258911133,19.21259117126465,-5.177116870880127,28.13614845275879,7.584829330444336,-18.644784927368164,0.0,0.0,0.0 +2613,7.864321,1381371,0,2,1,0,0,[],61.37177276611328,19.48025894165039,-5.099515438079834,29.075942993164062,7.34130334854126,-19.94260025024414,0.0,0.0,0.0 +2615,7.872303,1381372,0,2,1,0,0,[],61.24455642700195,19.74666976928711,-5.030419826507568,29.967458724975586,7.111225605010986,-21.188507080078125,0.0,0.0,0.0 +2617,7.880327,1381373,0,2,1,0,0,[],61.124996185302734,20.011768341064453,-4.970061779022217,30.80837059020996,6.894994258880615,-22.3795223236084,0.0,0.0,0.0 +2621,7.888293,1381374,0,2,1,0,0,[],61.01362609863281,20.275503158569336,-4.918673992156982,31.596359252929688,6.693009853363037,-23.512662887573242,0.0,0.0,0.0 +2623,7.896282,1381375,0,2,1,0,0,[],60.91096878051758,20.537817001342773,-4.87648868560791,32.329097747802734,6.505670547485352,-24.584949493408203,0.0,0.0,0.0 +2625,7.904304,1381376,0,2,1,0,0,[],60.81755447387695,20.79865837097168,-4.843737602233887,33.004268646240234,6.333376407623291,-25.59339714050293,0.0,0.0,0.0 +2627,7.912288,1381377,0,2,1,0,0,[],60.733909606933594,21.057968139648438,-4.820653438568115,33.61954879760742,6.176525592803955,-26.535024642944336,0.0,0.0,0.0 +2630,7.920276,1381378,0,2,1,0,0,[],60.66055679321289,21.31569480895996,-4.807467937469482,34.172611236572266,6.035518169403076,-27.40685272216797,0.0,0.0,0.0 +2633,7.928272,1381379,0,2,1,0,0,[],60.59803009033203,21.5717830657959,-4.804413795471191,34.661136627197266,5.910752773284912,-28.205896377563477,0.0,0.0,0.0 +2635,7.936288,1381380,0,2,1,0,0,[],60.54684829711914,21.8261775970459,-4.811722755432129,35.08280563354492,5.802628993988037,-28.929174423217773,0.0,0.0,0.0 +2639,7.944318,1381381,0,2,1,0,0,[],60.50754928588867,22.078824996948242,-4.82962703704834,35.43528747558594,5.711545467376709,-29.57370376586914,0.0,0.0,0.0 +2641,7.95229,1381382,0,2,1,0,0,[],60.48065185546875,22.329669952392578,-4.858358860015869,35.716270446777344,5.637901782989502,-30.136505126953125,0.0,0.0,0.0 +2643,7.960324,1381383,0,2,1,0,0,[],60.4666862487793,22.578657150268555,-4.898150444030762,35.923423767089844,5.582096576690674,-30.614593505859375,0.0,0.0,0.0 +2647,7.968292,1381384,0,2,1,0,0,[],60.46617889404297,22.825735092163086,-4.949234485626221,36.05442810058594,5.544529914855957,-31.004989624023438,0.0,0.0,0.0 +2649,7.976283,1381385,0,2,1,0,0,[],60.47965621948242,23.070844650268555,-5.011842250823975,36.106964111328125,5.525600433349609,-31.30470848083496,0.0,0.0,0.0 +2651,7.984312,1381386,0,2,1,0,0,[],60.507476806640625,23.3138427734375,-5.086086273193359,36.07946014404297,5.525520324707031,-31.511686325073242,0.0,0.0,0.0 +2654,7.992195,1381387,0,2,1,0,0,[],60.54932403564453,23.55422592163086,-5.171608924865723,35.97331237792969,5.543774127960205,-31.62742042541504,0.0,0.0,0.0 +2657,8.00028,1381388,0,2,1,0,0,[],60.60472869873047,23.791400909423828,-5.267937660217285,35.790626525878906,5.579668045043945,-31.65428352355957,0.0,0.0,0.0 +2659,8.008329,1381389,0,2,1,0,0,[],60.673213958740234,24.024778366088867,-5.374601364135742,35.53350830078125,5.632508754730225,-31.594646453857422,0.0,0.0,0.0 +2663,8.016347,1381390,0,2,1,0,0,[],60.75430679321289,24.253767013549805,-5.491127014160156,35.20407485961914,5.701602458953857,-31.450878143310547,0.0,0.0,0.0 +2665,8.024277,1381391,0,2,1,12,12,"[3, 4]",60.847530364990234,24.47777557373047,-5.617043972015381,34.8044319152832,5.786255359649658,-31.2253475189209,0.0,0.0,0.0 +2667,8.032258,1381392,0,2,1,0,0,[],60.95240783691406,24.69621467590332,-5.75187873840332,34.33668518066406,5.885775089263916,-30.920429229736328,0.0,0.0,0.0 +2669,8.040307,1381393,0,2,1,12,0,[],61.06847381591797,24.908491134643555,-5.895160675048828,33.80295181274414,5.999467372894287,-30.538488388061523,0.0,0.0,0.0 +2673,8.048277,1381394,0,2,1,0,0,[],61.19524383544922,25.114015579223633,-6.046416759490967,33.2053337097168,6.126638412475586,-30.081899642944336,0.0,0.0,0.0 +2675,8.056227,1381395,0,2,1,0,0,[],61.33224868774414,25.312196731567383,-6.205175876617432,32.54594039916992,6.266595363616943,-29.55303382873535,0.0,0.0,0.0 +2677,8.064284,1381396,0,2,1,0,0,[],61.47901153564453,25.502445220947266,-6.370965003967285,31.826887130737305,6.418644428253174,-28.954256057739258,0.0,0.0,0.0 +2680,8.072281,1381397,0,2,1,0,0,[],61.63505935668945,25.684167861938477,-6.543313503265381,31.05027961730957,6.582091808319092,-28.28794288635254,0.0,0.0,0.0 +2683,8.080274,1381398,0,2,1,0,0,[],61.79991912841797,25.856773376464844,-6.721748352050781,30.218225479125977,6.756244659423828,-27.556459426879883,0.0,0.0,0.0 +2685,8.088316,1381399,0,2,1,0,0,[],61.97311019897461,26.019672393798828,-6.905797481536865,29.33283805847168,6.940408706665039,-26.76218032836914,0.0,0.0,0.0 +2688,8.096287,1381400,0,2,1,0,0,[],62.15416717529297,26.172273635864258,-7.094989776611328,28.396223068237305,7.1338911056518555,-25.907472610473633,0.0,0.0,0.0 +2691,8.104293,1381401,0,2,1,0,0,[],62.34260559082031,26.313987731933594,-7.288852691650391,27.410490036010742,7.33599853515625,-24.994709014892578,0.0,0.0,0.0 +2693,8.112318,1381402,0,2,1,0,0,[],62.537960052490234,26.44422149658203,-7.48691463470459,26.37775230407715,7.546036243438721,-24.02625846862793,0.0,0.0,0.0 +2697,8.120246,1381403,0,2,1,0,0,[],62.739749908447266,26.5623836517334,-7.6887030601501465,25.300113677978516,7.763312339782715,-23.004493713378906,0.0,0.0,0.0 +2699,8.12829,1381404,0,2,1,0,0,[],62.947505950927734,26.66788673400879,-7.893746376037598,24.1796875,7.9871320724487305,-21.931781768798828,0.0,0.0,0.0 +2701,8.136348,1381405,0,2,1,0,0,[],63.16074752807617,26.760135650634766,-8.101572036743164,23.01858139038086,8.216802597045898,-20.81049346923828,0.0,0.0,0.0 +2704,8.144281,1381406,0,2,1,0,0,[],63.379005432128906,26.838542938232422,-8.3117094039917,21.818904876708984,8.451629638671875,-19.643001556396484,0.0,0.0,0.0 +2707,8.152307,1381407,0,2,1,0,0,[],63.60179901123047,26.902515411376953,-8.52368450164795,20.582765579223633,8.69092082977295,-18.43167495727539,0.0,0.0,0.0 +2709,8.160282,1381408,0,2,1,0,0,[],63.82865905761719,26.95146369934082,-8.737027168273926,19.31227684020996,8.933981895446777,-17.178884506225586,0.0,0.0,0.0 +2711,8.168326,1381409,0,2,1,0,0,[],64.05911254882812,26.98479461669922,-8.951264381408691,18.009544372558594,9.180119514465332,-15.887001037597656,0.0,0.0,0.0 +2714,8.176278,1381410,0,2,1,0,0,[],64.29267883300781,27.001920700073242,-9.165925025939941,16.676677703857422,9.428640365600586,-14.558393478393555,0.0,0.0,0.0 +2717,8.18428,1381411,0,2,1,0,0,[],64.52888488769531,27.002248764038086,-9.380536079406738,15.315788269042969,9.678850173950195,-13.195432662963867,0.0,0.0,0.0 +2719,8.192279,1381412,0,2,1,0,0,[],64.76726531982422,26.985187530517578,-9.594626426696777,13.928983688354492,9.93005657196045,-11.80048942565918,0.0,0.0,0.0 +2723,8.200319,1381413,0,2,1,0,0,[],65.00733184814453,26.95014762878418,-9.807723045349121,12.518373489379883,10.181565284729004,-10.375933647155762,0.0,0.0,0.0 +2725,8.208302,1381414,0,2,1,0,0,[],65.24861907958984,26.896535873413086,-10.019354820251465,11.086067199707031,10.432683944702148,-8.9241361618042,0.0,0.0,0.0 +2727,8.216283,1381415,0,2,1,0,0,[],65.49064636230469,26.82376480102539,-10.229049682617188,9.634174346923828,10.682717323303223,-7.44746732711792,0.0,0.0,0.0 +2731,8.22431,1381416,0,2,1,0,0,[],65.73294067382812,26.73124122619629,-10.436335563659668,8.164803504943848,10.9309720993042,-5.948297023773193,0.0,0.0,0.0 +2733,8.232276,1381417,0,2,1,0,0,[],65.97503662109375,26.618375778198242,-10.640740394592285,6.680065155029297,11.176755905151367,-4.428996562957764,0.0,0.0,0.0 +2735,8.240324,1381418,0,2,1,0,0,[],66.21644592285156,26.484575271606445,-10.841793060302734,5.182067394256592,11.4193754196167,-2.891935110092163,0.0,0.0,0.0 +2738,8.248291,1381419,0,2,1,0,0,[],66.45670318603516,26.32925033569336,-11.039019584655762,3.6729202270507812,11.658135414123535,-1.3394839763641357,0.0,0.0,0.0 +2741,8.256261,1381420,0,2,1,0,0,[],66.6953353881836,26.151809692382812,-11.231949806213379,2.1547324657440186,11.89234447479248,0.22598673403263092,0.0,0.0,0.0 +2743,8.264334,1381421,0,2,1,0,0,[],66.93185424804688,25.951662063598633,-11.420110702514648,0.6296135783195496,12.121307373046875,1.8021067380905151,0.0,0.0,0.0 +2747,8.272317,1381422,0,2,1,0,0,[],67.16580200195312,25.72821807861328,-11.603031158447266,-0.9003270864486694,12.344331741333008,3.386505603790283,0.0,0.0,0.0 +2749,8.280313,1381423,0,2,1,0,0,[],67.39669799804688,25.480884552001953,-11.780238151550293,-2.4329802989959717,12.560723304748535,4.976812839508057,0.0,0.0,0.0 +2751,8.288305,1381424,0,2,1,0,0,[],67.62406158447266,25.20907211303711,-11.951260566711426,-3.9662365913391113,12.76978874206543,6.5706586837768555,0.0,0.0,0.0 +2753,8.296315,1381425,0,2,1,0,0,[],67.84742736816406,24.912189483642578,-12.115626335144043,-5.497986793518066,12.970834732055664,8.165671348571777,0.0,0.0,0.0 +2757,8.304267,1381426,0,2,1,0,0,[],68.06631469726562,24.58964729309082,-12.272863388061523,-7.026121616363525,13.163167953491211,9.759482383728027,0.0,0.0,0.0 +2759,8.312315,1381427,0,2,1,0,0,[],68.2802505493164,24.24085235595703,-12.422499656677246,-8.548531532287598,13.346094131469727,11.349720001220703,0.0,0.0,0.0 +2761,8.320225,1381428,0,2,1,0,0,[],68.48876190185547,23.86521339416504,-12.56406307220459,-10.063108444213867,13.518919944763184,12.934014320373535,0.0,0.0,0.0 +2764,8.328309,1381429,0,2,1,0,0,[],68.69137573242188,23.462142944335938,-12.697081565856934,-11.567741394042969,13.680953025817871,14.509994506835938,0.0,0.0,0.0 +2767,8.336353,1381430,0,2,1,0,0,[],68.88761138916016,23.03104591369629,-12.821083068847656,-13.060321807861328,13.831499099731445,16.07529067993164,0.0,0.0,0.0 +2769,8.344308,1381431,0,2,1,0,0,[],69.0770034790039,22.571334838867188,-12.935595512390137,-14.538740158081055,13.969864845275879,17.627532958984375,0.0,0.0,0.0 +2773,8.352347,1381432,0,2,1,0,0,[],69.25907135009766,22.082416534423828,-13.04014778137207,-16.00088882446289,14.095355987548828,19.164348602294922,0.0,0.0,0.0 +2775,8.360294,1381433,0,2,1,0,0,[],69.43333435058594,21.563701629638672,-13.134267807006836,-17.44465446472168,14.207279205322266,20.68337059020996,0.0,0.0,0.0 +2777,8.368302,1381434,0,2,1,0,0,[],69.59933471679688,21.014598846435547,-13.217482566833496,-18.86793327331543,14.30494213104248,22.182226181030273,0.0,0.0,0.0 +2781,8.37625,1381435,0,2,1,0,0,[],69.75658416748047,20.43451499938965,-13.289320945739746,-20.268611907958984,14.387650489807129,23.658546447753906,0.0,0.0,0.0 +2783,8.384302,1381436,0,2,1,0,0,[],69.90460968017578,19.82286262512207,-13.349309921264648,-21.644580841064453,14.454710960388184,25.109960556030273,0.0,0.0,0.0 +2785,8.392325,1381437,0,2,1,0,0,[],70.04293823242188,19.17905044555664,-13.396979331970215,-22.99373435974121,14.5054292678833,26.53409767150879,0.0,0.0,0.0 +2788,8.400292,1381438,0,2,1,0,0,[],70.17110443115234,18.502485275268555,-13.431855201721191,-24.3139591217041,14.539112091064453,27.928586959838867,0.0,0.0,0.0 +2791,8.408295,1381439,0,2,1,0,0,[],70.28861999511719,17.792577743530273,-13.45346736907959,-25.6031494140625,14.55506706237793,29.291059494018555,0.0,0.0,0.0 +2793,8.416292,1381440,0,2,1,0,0,[],70.39501190185547,17.048736572265625,-13.461342811584473,-26.859193801879883,14.552599906921387,30.619142532348633,0.0,0.0,0.0 +2795,8.424315,1381441,0,2,1,0,0,[],70.48981475830078,16.27037239074707,-13.455009460449219,-28.079984664916992,14.531018257141113,31.910470962524414,0.0,0.0,0.0 +2798,8.432291,1381442,0,2,1,0,0,[],70.57279205322266,15.457621574401855,-13.434285163879395,-29.264324188232422,14.490030288696289,33.163673400878906,0.0,0.0,0.0 +2801,8.440296,1381443,0,2,1,0,0,[],70.6443099975586,14.612449645996094,-13.399711608886719,-30.413301467895508,14.430357933044434,34.379905700683594,0.0,0.0,0.0 +2803,8.448219,1381444,0,2,1,0,0,[],70.70482635498047,13.737109184265137,-13.351944923400879,-31.52836799621582,14.352880477905273,35.56071090698242,0.0,0.0,0.0 +2807,8.456261,1381445,0,2,1,0,0,[],70.75481414794922,12.833852767944336,-13.291641235351562,-32.6109733581543,14.258479118347168,36.707637786865234,0.0,0.0,0.0 +2809,8.464301,1381446,0,2,1,0,0,[],70.79473114013672,11.904932022094727,-13.219456672668457,-33.662567138671875,14.148032188415527,37.82223129272461,0.0,0.0,0.0 +2811,8.472317,1381447,0,2,1,12,12,"[3, 4]",70.8250503540039,10.95259952545166,-13.136046409606934,-34.68459701538086,14.022420883178711,38.90604019165039,0.0,0.0,0.0 +2814,8.480253,1381448,0,2,1,0,0,[],70.84622192382812,9.979106903076172,-13.042067527770996,-35.67851257324219,13.882524490356445,39.96061325073242,0.0,0.0,0.0 +2817,8.488291,1381449,0,2,1,12,0,[],70.85871887207031,8.986706733703613,-12.9381742477417,-36.64576721191406,13.72922134399414,40.987491607666016,0.0,0.0,0.0 +2819,8.496324,1381450,0,2,1,0,0,[],70.86299896240234,7.9776506423950195,-12.825024604797363,-37.58780288696289,13.563393592834473,41.98822784423828,0.0,0.0,0.0 +2823,8.504378,1381451,0,2,1,0,0,[],70.8595199584961,6.954191207885742,-12.703272819519043,-38.50607681274414,13.385920524597168,42.96436309814453,0.0,0.0,0.0 +2825,8.512322,1381452,0,2,1,0,0,[],70.84876251220703,5.918580532073975,-12.573575019836426,-39.402034759521484,13.197680473327637,43.917449951171875,0.0,0.0,0.0 +2827,8.520339,1381453,0,2,1,0,0,[],70.8311767578125,4.873070240020752,-12.436588287353516,-40.277122497558594,12.999554634094238,44.849029541015625,0.0,0.0,0.0 +2831,8.528288,1381454,0,2,1,0,0,[],70.80723571777344,3.819912910461426,-12.292967796325684,-41.13279724121094,12.7924222946167,45.76065444946289,0.0,0.0,0.0 +2833,8.536314,1381455,0,2,1,0,0,[],70.77738952636719,2.7613606452941895,-12.1433687210083,-41.97050476074219,12.577163696289062,46.653865814208984,0.0,0.0,0.0 +2835,8.544294,1381456,0,2,1,0,0,[],70.74211120605469,1.6996655464172363,-11.988449096679688,-42.791690826416016,12.354658126831055,47.53021240234375,0.0,0.0,0.0 +2837,8.552328,1381457,0,2,1,0,0,[],70.70186614990234,0.6370795965194702,-11.828863143920898,-43.59781265258789,12.125784873962402,48.39124298095703,0.0,0.0,0.0 +2841,8.560321,1381458,0,2,1,0,0,[],70.65711212158203,-0.4241448640823364,-11.665266990661621,-44.39031219482422,11.891425132751465,49.238502502441406,0.0,0.0,0.0 +2844,8.568303,1381459,0,2,1,0,0,[],70.60830688476562,-1.4817558526992798,-11.498316764831543,-45.17063903808594,11.652458190917969,50.07353591918945,0.0,0.0,0.0 +2846,8.57637,1381460,0,2,1,0,0,[],70.5559310913086,-2.533501148223877,-11.328668594360352,-45.940250396728516,11.409764289855957,50.89789581298828,0.0,0.0,0.0 +2849,8.584331,1381461,0,2,1,0,0,[],70.50043487548828,-3.5771286487579346,-11.156978607177734,-46.70058822631836,11.16422176361084,51.71311950683594,0.0,0.0,0.0 +2852,8.592326,1381462,0,2,1,0,0,[],70.4422836303711,-4.610386371612549,-10.983901977539062,-47.45310592651367,10.916711807250977,52.5207633972168,0.0,0.0,0.0 +2854,8.600321,1381463,0,2,1,0,0,[],70.38194274902344,-5.631021976470947,-10.81009578704834,-48.199249267578125,10.668113708496094,53.32236862182617,0.0,0.0,0.0 +2858,8.608353,1381464,0,2,1,0,0,[],70.31987762451172,-6.636783123016357,-10.636214256286621,-48.94047164916992,10.419307708740234,54.119483947753906,0.0,0.0,0.0 +2860,8.616288,1381465,0,2,1,0,0,[],70.25654602050781,-7.625418186187744,-10.46291446685791,-49.67822265625,10.171173095703125,54.91365432739258,0.0,0.0,0.0 +2862,8.624302,1381466,0,2,1,0,0,[],70.19242095947266,-8.594675064086914,-10.290853500366211,-50.413944244384766,9.924590110778809,55.70642852783203,0.0,0.0,0.0 +2865,8.6323,1381467,0,2,1,0,0,[],70.1279525756836,-9.542301177978516,-10.120684623718262,-51.14909744262695,9.680438041687012,56.49935531616211,0.0,0.0,0.0 +2868,8.640302,1381468,0,2,1,0,0,[],70.06361389160156,-10.466044425964355,-9.953065872192383,-51.8851203918457,9.439597129821777,57.293975830078125,0.0,0.0,0.0 +2870,8.648349,1381469,0,2,1,0,0,[],69.99986267089844,-11.363653182983398,-9.788652420043945,-52.623470306396484,9.202946662902832,58.09183883666992,0.0,0.0,0.0 +2874,8.656291,1381470,0,2,1,0,0,[],69.93717193603516,-12.232874870300293,-9.62809944152832,-53.36559295654297,8.971366882324219,58.894493103027344,0.0,0.0,0.0 +2876,8.664315,1381471,0,2,1,0,0,[],69.87599182128906,-13.071456909179688,-9.472064971923828,-54.112937927246094,8.74573802947998,59.70348358154297,0.0,0.0,0.0 +2878,8.672311,1381472,0,2,1,0,0,[],69.81680297851562,-13.877148628234863,-9.321202278137207,-54.86695861816406,8.526939392089844,60.520355224609375,0.0,0.0,0.0 +2880,8.680301,1381473,0,2,1,0,0,[],69.76004791259766,-14.647696495056152,-9.176169395446777,-55.62910079956055,8.315850257873535,61.34666061401367,0.0,0.0,0.0 +2884,8.688351,1381474,0,2,1,0,0,[],69.70620727539062,-15.38084888458252,-9.037622451782227,-56.40081024169922,8.113351821899414,62.18394088745117,0.0,0.0,0.0 +2886,8.696283,1381475,0,2,1,0,0,[],69.65573120117188,-16.07435417175293,-8.90621566772461,-57.18354415893555,7.920322895050049,63.033748626708984,0.0,0.0,0.0 +2888,8.704356,1381476,0,2,1,0,0,[],69.60909271240234,-16.72595977783203,-8.782605171203613,-57.9787483215332,7.737643241882324,63.897621154785156,0.0,0.0,0.0 +2892,8.71231,1381477,0,2,1,0,0,[],69.5667495727539,-17.33344268798828,-8.667440414428711,-58.7878532409668,7.566183090209961,64.77709197998047,0.0,0.0,0.0 +2894,8.720294,1381478,0,2,1,0,0,[],69.52893829345703,-17.89590835571289,-8.561015129089355,-59.61151123046875,7.406376838684082,65.67284393310547,0.0,0.0,0.0 +2896,8.728305,1381479,0,2,1,0,0,[],69.49559020996094,-18.41424560546875,-8.46314811706543,-60.44931411743164,7.258070468902588,66.58439636230469,0.0,0.0,0.0 +2899,8.73633,1381480,0,2,1,0,0,[],69.46662139892578,-18.889480590820312,-8.37362289428711,-61.30078125,7.121067523956299,67.5112075805664,0.0,0.0,0.0 +2902,8.744311,1381481,0,2,1,0,0,[],69.44195556640625,-19.322633743286133,-8.29222297668457,-62.16543197631836,6.995171070098877,68.45271301269531,0.0,0.0,0.0 +2904,8.752293,1381482,0,2,1,0,0,[],69.42150115966797,-19.7147274017334,-8.218732833862305,-63.04278564453125,6.880184650421143,69.40837097167969,0.0,0.0,0.0 +2908,8.760361,1381483,0,2,1,10,10,"[2, 4]",69.40516662597656,-20.066781997680664,-8.152935981750488,-63.93235397338867,6.775912284851074,70.37761688232422,0.0,0.0,0.0 +2910,8.768294,1381484,0,2,1,0,0,[],69.39288330078125,-20.37982177734375,-8.094615936279297,-64.83366394042969,6.682157039642334,71.35990142822266,0.0,0.0,0.0 +2912,8.77637,1381485,0,2,1,10,0,[],69.38455200195312,-20.654869079589844,-8.043557167053223,-65.74622344970703,6.598722457885742,72.35468292236328,0.0,0.0,0.0 +2916,8.784301,1381486,0,2,1,0,0,[],69.38009643554688,-20.892942428588867,-7.999543190002441,-66.66956329345703,6.525412082672119,73.36139678955078,0.0,0.0,0.0 +2918,8.792292,1381487,0,2,1,0,0,[],69.37942504882812,-21.095067977905273,-7.962357997894287,-67.60318756103516,6.462029457092285,74.3794937133789,0.0,0.0,0.0 +2920,8.800329,1381488,0,2,1,0,0,[],69.38246154785156,-21.262264251708984,-7.9317851066589355,-68.546630859375,6.4083781242370605,75.40841674804688,0.0,0.0,0.0 +2922,8.808323,1381489,0,2,1,0,0,[],69.38910675048828,-21.395557403564453,-7.9076080322265625,-69.4993896484375,6.364261627197266,76.44762420654297,0.0,0.0,0.0 +2925,8.816311,1381490,0,2,1,0,0,[],69.39929962158203,-21.49596405029297,-7.88961124420166,-70.46100616455078,6.329483509063721,77.49655151367188,0.0,0.0,0.0 +2928,8.824321,1381491,0,2,1,0,0,[],69.41293334960938,-21.564512252807617,-7.8775787353515625,-71.43098449707031,6.303846836090088,78.55464935302734,0.0,0.0,0.0 +2930,8.832322,1381492,0,2,1,0,0,[],69.42992401123047,-21.602218627929688,-7.871293544769287,-72.4088363647461,6.287155628204346,79.62136840820312,0.0,0.0,0.0 +2934,8.840338,1381493,0,2,1,0,0,[],69.45020294189453,-21.610109329223633,-7.870539665222168,-73.39409637451172,6.279212951660156,80.69615173339844,0.0,0.0,0.0 +2936,8.848292,1381494,0,2,1,0,0,[],69.47367858886719,-21.589202880859375,-7.875101566314697,-74.38627624511719,6.27982234954834,81.7784423828125,0.0,0.0,0.0 +2938,8.856361,1381495,0,2,1,0,0,[],69.50025177001953,-21.540523529052734,-7.884762287139893,-75.38489532470703,6.288787841796875,82.8676986694336,0.0,0.0,0.0 +2942,8.86432,1381496,0,2,1,0,0,[],69.52986145019531,-21.4650936126709,-7.899306297302246,-76.38946533203125,6.305912494659424,83.96336364746094,0.0,0.0,0.0 +2944,8.87229,1381497,0,2,1,0,0,[],69.5624008178711,-21.363933563232422,-7.918517112731934,-77.39950561523438,6.330999851226807,85.06488037109375,0.0,0.0,0.0 +2946,8.880292,1381498,0,2,1,0,0,[],69.5978012084961,-21.238065719604492,-7.942178249359131,-78.41454315185547,6.363853931427002,86.17169952392578,0.0,0.0,0.0 +2949,8.8883,1381499,0,2,1,0,0,[],69.63597106933594,-21.088512420654297,-7.97007417678833,-79.43408966064453,6.404277324676514,87.28326416015625,0.0,0.0,0.0 +2952,8.896286,1381500,0,2,1,0,0,[],69.67682647705078,-20.916296005249023,-8.001988410949707,-80.45765686035156,6.45207405090332,88.3990249633789,0.0,0.0,0.0 +2954,8.904352,1381501,0,2,1,0,0,[],69.72028350830078,-20.72243881225586,-8.037704467773438,-81.48477935791016,6.507047176361084,89.5184326171875,0.0,0.0,0.0 +2958,8.912335,1381502,0,2,1,0,0,[],69.76625061035156,-20.50796127319336,-8.077007293701172,-82.51496124267578,6.569001197814941,90.64093017578125,0.0,0.0,0.0 +2960,8.920289,1381503,0,2,1,0,0,[],69.81465148925781,-20.273887634277344,-8.119678497314453,-83.54772186279297,6.6377387046813965,91.76596069335938,0.0,0.0,0.0 +2962,8.928346,1381504,0,2,1,0,0,[],69.86540222167969,-20.021238327026367,-8.165504455566406,-84.58258819580078,6.713063716888428,92.89298248291016,0.0,0.0,0.0 +2964,8.936363,1381505,0,2,1,0,0,[],69.91840362548828,-19.751035690307617,-8.21426773071289,-85.61907196044922,6.794779300689697,94.02143096923828,0.0,0.0,0.0 +2968,8.944296,1381506,0,2,1,0,0,[],69.97358703613281,-19.46430206298828,-8.265751838684082,-86.65669250488281,6.882688999176025,95.1507568359375,0.0,0.0,0.0 +2970,8.952309,1381507,0,2,1,0,0,[],70.03086853027344,-19.162057876586914,-8.319740295410156,-87.6949691772461,6.976596832275391,96.28041076660156,0.0,0.0,0.0 +2972,8.960337,1381508,0,2,1,0,0,[],70.09014892578125,-18.84532928466797,-8.376018524169922,-88.7334213256836,7.076305389404297,97.40983581542969,0.0,0.0,0.0 +2975,8.968339,1381509,0,2,1,0,0,[],70.15135192871094,-18.515132904052734,-8.434370040893555,-89.77156066894531,7.181619167327881,98.53848266601562,0.0,0.0,0.0 +2978,8.976291,1381510,0,2,1,0,0,[],70.21439361572266,-18.172494888305664,-8.494576454162598,-90.80891418457031,7.292341232299805,99.6657943725586,0.0,0.0,0.0 +2980,8.984348,1381511,0,2,1,0,0,[],70.27918243408203,-17.81843376159668,-8.556424140930176,-91.84498596191406,7.408275127410889,100.79122161865234,0.0,0.0,0.0 +2984,8.99232,1381512,0,2,1,0,0,[],70.34564208984375,-17.453975677490234,-8.619696617126465,-92.87931060791016,7.529224395751953,101.9142074584961,0.0,0.0,0.0 +2986,9.00033,1381513,0,2,1,0,0,[],70.41368865966797,-17.08013916015625,-8.684176445007324,-93.9113998413086,7.65499210357666,103.03421020507812,0.0,0.0,0.0 +2988,9.008336,1381514,0,2,1,0,0,[],70.48323059082031,-16.697948455810547,-8.749648094177246,-94.9407730102539,7.785382270812988,104.15066528320312,0.0,0.0,0.0 +2992,9.016324,1381515,0,2,1,0,0,[],70.55418395996094,-16.30842399597168,-8.815895080566406,-95.9669418334961,7.920198440551758,105.26301574707031,0.0,0.0,0.0 +2994,9.024317,1381516,0,2,1,0,0,[],70.62646484375,-15.912589073181152,-8.882701873779297,-96.98942565917969,8.059244155883789,106.3707275390625,0.0,0.0,0.0 +2996,9.032292,1381517,0,2,1,0,0,[],70.69998931884766,-15.511465072631836,-8.949851989746094,-98.00775146484375,8.202322006225586,107.47322845458984,0.0,0.0,0.0 +2999,9.040306,1381518,0,2,1,0,0,[],70.77466583251953,-15.106074333190918,-9.017128944396973,-99.02143859863281,8.349236488342285,108.56997680664062,0.0,0.0,0.0 +3002,9.04831,1381519,0,2,1,0,0,[],70.85042572021484,-14.69743824005127,-9.084317207336426,-100.02999114990234,8.49979019165039,109.66041564941406,0.0,0.0,0.0 +3004,9.05634,1381520,0,2,1,0,0,[],70.92716979980469,-14.286579132080078,-9.151200294494629,-101.0329360961914,8.653788566589355,110.7439956665039,0.0,0.0,0.0 +3006,9.064342,1381521,0,2,1,0,0,[],71.00482177734375,-13.874519348144531,-9.217561721801758,-102.02978515625,8.81103229522705,111.82015991210938,0.0,0.0,0.0 +3009,9.072295,1381522,0,2,1,0,0,[],71.08329010009766,-13.4622802734375,-9.283185005187988,-103.02006530761719,8.97132682800293,112.88835906982422,0.0,0.0,0.0 +3012,9.080318,1381523,0,2,1,0,0,[],71.16249084472656,-13.050884246826172,-9.347855567932129,-104.0032958984375,9.134475708007812,113.94803619384766,0.0,0.0,0.0 +3014,9.088315,1381524,0,2,1,0,0,[],71.24234008789062,-12.641353607177734,-9.411355018615723,-104.97898864746094,9.300281524658203,114.99864196777344,0.0,0.0,0.0 +3018,9.096364,1381525,0,2,1,0,0,[],71.32275390625,-12.234709739685059,-9.473469734191895,-105.94666290283203,9.468548774719238,116.03962707519531,0.0,0.0,0.0 +3020,9.104355,1381526,0,2,1,0,0,[],71.40364837646484,-11.831975936889648,-9.533981323242188,-106.90583038330078,9.639079093933105,117.07042694091797,0.0,0.0,0.0 +3022,9.112354,1381527,0,2,1,0,0,[],71.48493957519531,-11.434172630310059,-9.592674255371094,-107.85602569580078,9.811677932739258,118.09049987792969,0.0,0.0,0.0 +3025,9.120297,1381528,0,2,1,0,0,[],71.56654357910156,-11.042322158813477,-9.649333000183105,-108.7967529296875,9.986148834228516,119.09928131103516,0.0,0.0,0.0 +3028,9.128334,1381529,0,2,1,0,0,[],71.64836883544922,-10.657447814941406,-9.703740119934082,-109.7275390625,10.162293434143066,120.09622955322266,0.0,0.0,0.0 +3030,9.136373,1381530,0,2,1,0,0,[],71.73033905029297,-10.280570030212402,-9.755681037902832,-110.64789581298828,10.339917182922363,121.08079528808594,0.0,0.0,0.0 +3034,9.144291,1381531,0,2,1,0,0,[],71.8123550415039,-9.912712097167969,-9.804938316345215,-111.55734252929688,10.518821716308594,122.05240631103516,0.0,0.0,0.0 +3036,9.152314,1381532,0,2,1,0,0,[],71.89434814453125,-9.55489444732666,-9.851296424865723,-112.45539855957031,10.698812484741211,123.01052856445312,0.0,0.0,0.0 +3038,9.160324,1381533,0,2,1,0,0,[],71.97622680664062,-9.208141326904297,-9.894539833068848,-113.34158325195312,10.879692077636719,123.95460510253906,0.0,0.0,0.0 +3042,9.16832,1381534,0,2,1,0,0,[],72.05790710449219,-8.873473167419434,-9.934450149536133,-114.21540832519531,11.061264038085938,124.88407897949219,0.0,0.0,0.0 +3044,9.176294,1381535,0,2,1,0,0,[],72.1393051147461,-8.551911354064941,-9.970812797546387,-115.07640075683594,11.243330955505371,125.79839324951172,0.0,0.0,0.0 +3046,9.184347,1381536,0,2,1,0,0,[],72.2203369140625,-8.24448013305664,-10.003412246704102,-115.92407989501953,11.425698280334473,126.69700622558594,0.0,0.0,0.0 +3048,9.19232,1381537,0,2,1,0,0,[],72.30091094970703,-7.952199935913086,-10.032031059265137,-116.75794982910156,11.60816764831543,127.57936096191406,0.0,0.0,0.0 +3052,9.200328,1381538,0,2,1,0,0,[],72.38094329833984,-7.676093101501465,-10.056452751159668,-117.57754516601562,11.790544509887695,128.4449005126953,0.0,0.0,0.0 +3054,9.208348,1381539,0,2,1,0,0,[],72.46035766601562,-7.417181491851807,-10.076462745666504,-118.38237762451172,11.972630500793457,129.29307556152344,0.0,0.0,0.0 +3056,9.216306,1381540,0,2,1,0,0,[],72.5390625,-7.176487445831299,-10.091843605041504,-119.17195892333984,12.154230117797852,130.1233367919922,0.0,0.0,0.0 +3059,9.224309,1381541,0,2,1,0,0,[],72.61698150634766,-6.955032825469971,-10.10237979888916,-119.94581604003906,12.335145950317383,130.93511962890625,0.0,0.0,0.0 +3062,9.232291,1381542,0,2,1,0,0,[],72.69401550292969,-6.75383996963501,-10.107853889465332,-120.70346069335938,12.515182495117188,131.72787475585938,0.0,0.0,0.0 +3064,9.240344,1381543,0,2,1,0,0,[],72.77008819580078,-6.573930263519287,-10.108051300048828,-121.44441986083984,12.694143295288086,132.50106811523438,0.0,0.0,0.0 +3068,9.248363,1381544,0,2,1,0,0,[],72.8451156616211,-6.416326522827148,-10.102755546569824,-122.16820526123047,12.871830940246582,133.25411987304688,0.0,0.0,0.0 +3070,9.256291,1381545,0,2,1,0,0,[],72.91900634765625,-6.282049655914307,-10.091750144958496,-122.87433624267578,13.048049926757812,133.9864959716797,0.0,0.0,0.0 +3072,9.264346,1381546,0,2,1,0,0,[],72.99168395996094,-6.172122955322266,-10.07481861114502,-123.56233215332031,13.222602844238281,134.6976318359375,0.0,0.0,0.0 +3076,9.272329,1381547,0,2,1,0,0,[],73.06306457519531,-6.087567329406738,-10.05174446105957,-124.23170471191406,13.395294189453125,135.38697814941406,0.0,0.0,0.0 +3078,9.280331,1381548,0,2,1,0,0,[],73.133056640625,-6.02940559387207,-10.022313117980957,-124.8819808959961,13.565926551818848,136.05398559570312,0.0,0.0,0.0 +3080,9.288321,1381549,0,2,1,0,0,[],73.20156860351562,-5.998659610748291,-9.986307144165039,-125.5126724243164,13.73430347442627,136.69810485839844,0.0,0.0,0.0 +3083,9.296322,1381550,0,2,1,0,0,[],73.2685317993164,-5.99635124206543,-9.943510055541992,-126.12329864501953,13.900229454040527,137.3187713623047,0.0,0.0,0.0 +3086,9.304356,1381551,0,2,1,0,0,[],73.33385467529297,-6.023502349853516,-9.893707275390625,-126.71338653564453,14.063507080078125,137.91543579101562,0.0,0.0,0.0 +3088,9.312341,1381552,0,2,1,0,0,[],73.39745330810547,-6.081135272979736,-9.83668041229248,-127.28244018554688,14.223939895629883,138.487548828125,0.0,0.0,0.0 +3090,9.320352,1381553,0,2,1,0,0,[],73.45923614501953,-6.170271873474121,-9.772214889526367,-127.82998657226562,14.381331443786621,139.03456115722656,0.0,0.0,0.0 +3094,9.328369,1381554,0,2,1,0,0,[],73.51912689208984,-6.291934013366699,-9.700094223022461,-128.3555450439453,14.535486221313477,139.555908203125,0.0,0.0,0.0 +3096,9.336375,1381555,0,2,1,0,0,[],73.57704162597656,-6.4471435546875,-9.620102882385254,-128.85862731933594,14.686205863952637,140.05105590820312,0.0,0.0,0.0 +3098,9.344335,1381556,0,2,1,0,0,[],73.63304901123047,-6.636194229125977,-9.532256126403809,-129.33848571777344,14.833406448364258,140.519287109375,0.0,0.0,0.0 +3102,9.352347,1381557,0,2,1,0,0,[],73.68771362304688,-6.857228755950928,-9.437260627746582,-129.79359436035156,14.977326393127441,140.9595184326172,0.0,0.0,0.0 +3104,9.360294,1381558,0,2,1,0,0,[],73.74168395996094,-7.107993125915527,-9.33594799041748,-130.2222442626953,15.118266105651855,141.3705596923828,0.0,0.0,0.0 +3106,9.368304,1381559,0,2,1,0,0,[],73.79561614990234,-7.386232852935791,-9.22914981842041,-130.62277221679688,15.256525993347168,141.751220703125,0.0,0.0,0.0 +3109,9.376335,1381560,0,2,1,0,0,[],73.85015106201172,-7.689694881439209,-9.117698669433594,-130.99349975585938,15.392406463623047,142.10031127929688,0.0,0.0,0.0 +3112,9.384332,1381561,0,2,1,10,10,"[2, 4]",73.90592956542969,-8.016124725341797,-9.002426147460938,-131.3327178955078,15.526206970214844,142.41668701171875,0.0,0.0,0.0 +3114,9.392355,1381562,0,2,1,0,0,[],73.96361541748047,-8.363268852233887,-8.884164810180664,-131.63876342773438,15.65822696685791,142.69912719726562,0.0,0.0,0.0 +3118,9.400331,1381563,0,2,1,10,0,[],74.02384948730469,-8.728874206542969,-8.76374626159668,-131.9099578857422,15.788768768310547,142.9464569091797,0.0,0.0,0.0 +3120,9.408335,1381564,0,2,1,0,0,[],74.0872802734375,-9.110684394836426,-8.642003059387207,-132.14459228515625,15.918130874633789,143.1575164794922,0.0,0.0,0.0 +3122,9.416278,1381565,0,2,1,0,0,[],74.1545639038086,-9.506447792053223,-8.519765853881836,-132.34100341796875,16.046613693237305,143.3311004638672,0.0,0.0,0.0 +3126,9.424269,1381566,0,2,1,0,0,[],74.22633361816406,-9.913909912109375,-8.397869110107422,-132.4975128173828,16.174516677856445,143.46603393554688,0.0,0.0,0.0 +3128,9.432309,1381567,0,2,1,0,0,[],74.30325317382812,-10.330816268920898,-8.277142524719238,-132.61241149902344,16.302141189575195,143.56112670898438,0.0,0.0,0.0 +3130,9.440327,1381568,0,2,1,0,0,[],74.3859634399414,-10.754914283752441,-8.158419609069824,-132.6840362548828,16.429786682128906,143.61520385742188,0.0,0.0,0.0 +3132,9.448367,1381569,0,2,1,0,0,[],74.47511291503906,-11.183948516845703,-8.042531967163086,-132.710693359375,16.55775260925293,143.62709045410156,0.0,0.0,0.0 +3135,9.456309,1381570,0,2,1,0,0,[],74.57135009765625,-11.615665435791016,-7.93031120300293,-132.69070434570312,16.68634033203125,143.59559631347656,0.0,0.0,0.0 +3138,9.464345,1381571,0,2,1,0,0,[],74.67533111572266,-12.047811508178711,-7.822589874267578,-132.6223907470703,16.81584930419922,143.51954650878906,0.0,0.0,0.0 +3140,9.472304,1381572,0,2,1,0,0,[],74.7876968383789,-12.478133201599121,-7.720200061798096,-132.50405883789062,16.946578979492188,143.39773559570312,0.0,0.0,0.0 +3144,9.480343,1381573,0,2,1,0,0,[],74.90909576416016,-12.904376029968262,-7.623973369598389,-132.3340301513672,17.07883071899414,143.22900390625,0.0,0.0,0.0 +3146,9.488344,1381574,0,2,1,0,0,[],75.04017639160156,-13.324286460876465,-7.5347418785095215,-132.11062622070312,17.21290397644043,143.0121612548828,0.0,0.0,0.0 +3148,9.49633,1381575,0,2,1,0,0,[],75.18159484863281,-13.735610961914062,-7.453338146209717,-131.8321533203125,17.349098205566406,142.74603271484375,0.0,0.0,0.0 +3152,9.504367,1381576,0,2,1,0,0,[],75.33399200439453,-14.136094093322754,-7.380593776702881,-131.49693298339844,17.487714767456055,142.42941284179688,0.0,0.0,0.0 +3154,9.512325,1381577,0,2,1,0,0,[],75.49801635742188,-14.523483276367188,-7.317340850830078,-131.103271484375,17.629053115844727,142.0611572265625,0.0,0.0,0.0 +3156,9.52035,1381578,0,2,1,0,0,[],75.67427825927734,-14.895607948303223,-7.264374256134033,-130.64962768554688,17.773399353027344,141.64015197753906,0.0,0.0,0.0 +3159,9.528301,1381579,0,2,1,0,0,[],75.86272430419922,-15.251615524291992,-7.221898555755615,-130.13645935058594,17.920795440673828,141.16696166992188,0.0,0.0,0.0 +3162,9.536359,1381580,0,2,1,0,0,[],76.06279754638672,-15.591703414916992,-7.1896491050720215,-129.56581115722656,18.071096420288086,140.64344787597656,0.0,0.0,0.0 +3164,9.544364,1381581,0,2,1,0,0,[],76.2739028930664,-15.916099548339844,-7.167346477508545,-128.93978881835938,18.22414779663086,140.07147216796875,0.0,0.0,0.0 +3168,9.552319,1381582,0,2,1,0,0,[],76.4954605102539,-16.22503089904785,-7.154713153839111,-128.260498046875,18.37979507446289,139.45294189453125,0.0,0.0,0.0 +3170,9.560358,1381583,0,2,1,10,10,"[2, 4]",76.72688293457031,-16.518726348876953,-7.15147066116333,-127.53003692626953,18.53788948059082,138.7897491455078,0.0,0.0,0.0 +3172,9.568328,1381584,0,2,1,0,0,[],76.96759033203125,-16.79741096496582,-7.157340049743652,-126.7505111694336,18.698274612426758,138.08377075195312,0.0,0.0,0.0 +3175,9.576354,1381585,0,2,1,10,0,[],77.21700286865234,-17.061315536499023,-7.1720428466796875,-125.92402648925781,18.86079978942871,137.33689880371094,0.0,0.0,0.0 +3179,9.584359,1381586,0,2,1,0,0,[],77.47452545166016,-17.3106632232666,-7.195301055908203,-125.05268859863281,19.025310516357422,136.55101013183594,0.0,0.0,0.0 +3181,9.592344,1381587,0,2,1,0,0,[],77.73958587646484,-17.545686721801758,-7.226836204528809,-124.13858795166016,19.191654205322266,135.72799682617188,0.0,0.0,0.0 +3183,9.600331,1381588,0,2,1,0,0,[],78.01158905029297,-17.7666072845459,-7.266369819641113,-123.18384552001953,19.359678268432617,134.8697509765625,0.0,0.0,0.0 +3186,9.608305,1381589,0,2,1,0,0,[],78.28995513916016,-17.973657608032227,-7.313622951507568,-122.19054412841797,19.52923011779785,133.97816467285156,0.0,0.0,0.0 +3189,9.616364,1381590,0,2,1,0,0,[],78.57410430908203,-18.167064666748047,-7.368317604064941,-121.16079711914062,19.700157165527344,133.0551300048828,0.0,0.0,0.0 +3191,9.624356,1381591,0,2,1,0,0,[],78.86344909667969,-18.3470516204834,-7.43017578125,-120.09671020507812,19.87230682373047,132.10250854492188,0.0,0.0,0.0 +3194,9.632314,1381592,0,2,1,0,0,[],79.15740966796875,-18.513851165771484,-7.498918056488037,-119.00038146972656,20.04552459716797,131.12220764160156,0.0,0.0,0.0 +3197,9.640327,1381593,0,2,1,0,0,[],79.45539855957031,-18.667686462402344,-7.5742669105529785,-117.87390899658203,20.21965789794922,130.11610412597656,0.0,0.0,0.0 +3199,9.64837,1381594,0,2,1,0,0,[],79.75682830810547,-18.808788299560547,-7.655943393707275,-116.71940612792969,20.394554138183594,129.0861053466797,0.0,0.0,0.0 +3203,9.656336,1381595,0,2,1,0,0,[],80.06111907958984,-18.9373836517334,-7.743669509887695,-115.53897094726562,20.57006072998047,128.03407287597656,0.0,0.0,0.0 +3205,9.664316,1381596,0,2,1,0,0,[],80.36768341064453,-19.05369758605957,-7.8371663093566895,-114.33470916748047,20.74602699279785,126.96190643310547,0.0,0.0,0.0 +3207,9.672352,1381597,0,2,1,0,0,[],80.67594146728516,-19.157958984375,-7.936155319213867,-113.10871887207031,20.92229461669922,125.87150573730469,0.0,0.0,0.0 +3210,9.680324,1381598,0,2,1,0,0,[],80.98530578613281,-19.250394821166992,-8.040358543395996,-111.86309814453125,21.09871482849121,124.76473236083984,0.0,0.0,0.0 +3213,9.688335,1381599,0,2,1,0,0,[],81.29520416259766,-19.331233978271484,-8.149497985839844,-110.59996032714844,21.275135040283203,123.64349365234375,0.0,0.0,0.0 +3215,9.696296,1381600,0,2,1,0,0,[],81.60503387451172,-19.40070343017578,-8.263293266296387,-109.3214111328125,21.451400756835938,122.50967407226562,0.0,0.0,0.0 +3217,9.704351,1381601,0,2,1,0,0,[],81.91422271728516,-19.459030151367188,-8.381468772888184,-108.02953338623047,21.62735939025879,121.36515808105469,0.0,0.0,0.0 +3220,9.712331,1381602,0,2,1,0,0,[],82.22218322753906,-19.506441116333008,-8.503743171691895,-106.72645568847656,21.8028564453125,120.21183013916016,0.0,0.0,0.0 +3223,9.720365,1381603,0,2,1,0,0,[],82.52833557128906,-19.543163299560547,-8.629840850830078,-105.41426086425781,21.97774314880371,119.05158233642578,0.0,0.0,0.0 +3225,9.728348,1381604,0,2,1,0,0,[],82.83209228515625,-19.569427490234375,-8.759480476379395,-104.09506225585938,22.1518611907959,117.88629913330078,0.0,0.0,0.0 +3229,9.736347,1381605,0,2,1,0,0,[],83.13286590576172,-19.58545684814453,-8.892386436462402,-102.77095794677734,22.325063705444336,116.7178726196289,0.0,0.0,0.0 +3231,9.744324,1381606,0,2,1,0,0,[],83.43008422851562,-19.591482162475586,-9.028278350830078,-101.44405364990234,22.4971923828125,115.54817962646484,0.0,0.0,0.0 +3233,9.752326,1381607,0,2,1,0,0,[],83.72314453125,-19.58772850036621,-9.166878700256348,-100.116455078125,22.66809844970703,114.37911987304688,0.0,0.0,0.0 +3236,9.760286,1381608,0,2,1,0,0,[],84.01148223876953,-19.574424743652344,-9.307908058166504,-98.7902603149414,22.837627410888672,113.21257781982422,0.0,0.0,0.0 +3239,9.768348,1381609,0,2,1,0,0,[],84.29450225830078,-19.551799774169922,-9.451088905334473,-97.46756744384766,23.005624771118164,112.0504379272461,0.0,0.0,0.0 +3241,9.776393,1381610,0,2,1,0,0,[],84.57162475585938,-19.520076751708984,-9.596142768859863,-96.1504898071289,23.171939849853516,110.89459228515625,0.0,0.0,0.0 +3244,9.784371,1381611,0,2,1,0,0,[],84.8422622680664,-19.4794864654541,-9.742791175842285,-94.84111785888672,23.33641815185547,109.74691772460938,0.0,0.0,0.0 +3247,9.792324,1381612,0,2,1,0,0,[],85.1058349609375,-19.430255889892578,-9.890755653381348,-93.54157257080078,23.49890899658203,108.60932159423828,0.0,0.0,0.0 +3249,9.800359,1381613,0,2,1,0,0,[],85.36175537109375,-19.37261199951172,-10.03975772857666,-92.25393676757812,23.659257888793945,107.4836654663086,0.0,0.0,0.0 +3253,9.808361,1381614,0,2,1,0,0,[],85.60944366455078,-19.306781768798828,-10.189518928527832,-90.98033142089844,23.817312240600586,106.37185668945312,0.0,0.0,0.0 +3255,9.816228,1381615,0,2,1,0,0,[],85.84831237792969,-19.232994079589844,-10.339759826660156,-89.72284698486328,23.972919464111328,105.2757797241211,0.0,0.0,0.0 +3257,9.824213,1381616,0,2,1,0,0,[],86.07777404785156,-19.15147590637207,-10.490203857421875,-88.48358917236328,24.125925064086914,104.19731140136719,0.0,0.0,0.0 +3259,9.832359,1381617,0,2,1,0,0,[],86.29725646972656,-19.06245231628418,-10.640571594238281,-87.26466369628906,24.276180267333984,103.13835144042969,0.0,0.0,0.0 +3263,9.840367,1381618,0,2,1,0,0,[],86.50616455078125,-18.966156005859375,-10.790584564208984,-86.06816864013672,24.423526763916016,102.10078430175781,0.0,0.0,0.0 +3265,9.848318,1381619,0,2,1,0,0,[],86.70391845703125,-18.862808227539062,-10.939964294433594,-84.89620971679688,24.56781578063965,101.08649444580078,0.0,0.0,0.0 +3267,9.856352,1381620,0,2,1,0,0,[],86.88993072509766,-18.752641677856445,-11.088433265686035,-83.75089263916016,24.708890914916992,100.09736633300781,0.0,0.0,0.0 +3270,9.864303,1381621,0,2,1,0,0,[],87.0636215209961,-18.635879516601562,-11.235712051391602,-82.63431549072266,24.846603393554688,99.13529968261719,0.0,0.0,0.0 +3273,9.872333,1381622,0,2,1,0,0,[],87.22441101074219,-18.512752532958984,-11.381522178649902,-81.548583984375,24.980796813964844,98.20216369628906,0.0,0.0,0.0 +3275,9.880356,1381623,0,2,1,0,0,[],87.3717041015625,-18.383487701416016,-11.525585174560547,-80.49579620361328,25.11132049560547,97.29986572265625,0.0,0.0,0.0 +3279,9.888358,1381624,0,2,1,0,0,[],87.50492858886719,-18.248310089111328,-11.667623519897461,-79.47806549072266,25.238021850585938,96.43028259277344,0.0,0.0,0.0 +3281,9.896372,1381625,0,2,1,0,0,[],87.62348937988281,-18.107450485229492,-11.807357788085938,-78.49748229980469,25.360746383666992,95.59529876708984,0.0,0.0,0.0 +3283,9.904339,1381626,0,2,1,0,0,[],87.72681427001953,-17.96113395690918,-11.944510459899902,-77.55615997314453,25.479339599609375,94.79680633544922,0.0,0.0,0.0 +3287,9.912366,1381627,0,2,1,0,0,[],87.8143081665039,-17.809587478637695,-12.078802108764648,-76.65618896484375,25.593652725219727,94.03669738769531,0.0,0.0,0.0 +3289,9.920353,1381628,0,2,1,0,0,[],87.88539123535156,-17.65304183959961,-12.209955215454102,-75.79969024658203,25.70353126525879,93.31684875488281,0.0,0.0,0.0 +3291,9.928356,1381629,0,2,1,0,0,[],87.93948364257812,-17.49172019958496,-12.337690353393555,-74.9887466430664,25.808820724487305,92.63915252685547,0.0,0.0,0.0 +3294,9.936354,1381630,0,2,1,0,0,[],87.97599792480469,-17.32585334777832,-12.461730003356934,-74.22547149658203,25.90937042236328,92.00550079345703,0.0,0.0,0.0 +3297,9.944348,1381631,0,2,1,0,0,[],87.99435424804688,-17.15566635131836,-12.581795692443848,-73.51197052001953,26.00502586364746,91.41777801513672,0.0,0.0,0.0 +3300,9.952357,1381632,0,2,1,0,0,[],87.99395751953125,-16.98138999938965,-12.69760799407959,-72.850341796875,26.09563636779785,90.87786865234375,0.0,0.0,0.0 +3302,9.960356,1381633,0,2,1,0,0,[],87.97423553466797,-16.803247451782227,-12.808889389038086,-72.24269104003906,26.181045532226562,90.38766479492188,0.0,0.0,0.0 +3306,9.968369,1381634,0,2,1,0,0,[],87.93460083007812,-16.621469497680664,-12.915362358093262,-71.69111633300781,26.261104583740234,89.94905090332031,0.0,0.0,0.0 +3308,9.976311,1381635,0,2,1,0,0,[],87.87446594238281,-16.436281204223633,-13.016745567321777,-71.19772338867188,26.335657119750977,89.56391906738281,0.0,0.0,0.0 +3310,9.984325,1381636,0,2,1,0,0,[],87.79324340820312,-16.247913360595703,-13.112763404846191,-70.76461791992188,26.404552459716797,89.2341537475586,0.0,0.0,0.0 +3314,9.992347,1381637,0,2,1,0,0,[],87.69036102294922,-16.056589126586914,-13.203136444091797,-70.3938980102539,26.467636108398438,88.96163940429688,0.0,0.0,0.0 +3316,10.000293,1381638,0,2,1,0,0,[],87.56523132324219,-15.862539291381836,-13.287586212158203,-70.0876693725586,26.524755477905273,88.74826049804688,0.0,0.0,0.0 +3318,10.008403,1381639,0,2,1,0,0,[],87.41726684570312,-15.665989875793457,-13.365833282470703,-69.84803009033203,26.575759887695312,88.59591674804688,0.0,0.0,0.0 +3321,10.01636,1381640,0,2,1,0,0,[],87.24588775634766,-15.467168807983398,-13.437601089477539,-69.67709350585938,26.620492935180664,88.50648498535156,0.0,0.0,0.0 +3324,10.024324,1381641,0,2,1,0,0,[],87.05049896240234,-15.266303062438965,-13.502610206604004,-69.57695007324219,26.65880584716797,88.48186492919922,0.0,0.0,0.0 +3326,10.03234,1381642,0,2,1,0,0,[],86.83053588867188,-15.063619613647461,-13.560582160949707,-69.5497055053711,26.690542221069336,88.52392578125,0.0,0.0,0.0 +3330,10.04037,1381643,0,2,1,0,0,[],86.58539581298828,-14.85934829711914,-13.611238479614258,-69.59747314453125,26.71554946899414,88.63456726074219,0.0,0.0,0.0 +3332,10.048324,1381644,0,2,1,0,0,[],86.31449890136719,-14.653714179992676,-13.654300689697266,-69.72234344482422,26.73367691040039,88.81568145751953,0.0,0.0,0.0 +3334,10.056378,1381645,0,2,1,0,0,[],86.01728057861328,-14.446944236755371,-13.689492225646973,-69.9264144897461,26.744770050048828,89.06912994384766,0.0,0.0,0.0 +3338,10.064324,1381646,0,2,1,0,0,[],85.69367218017578,-14.2391996383667,-13.716683387756348,-70.20999908447266,26.748775482177734,89.39500427246094,0.0,0.0,0.0 +3340,10.072347,1381647,0,2,1,0,0,[],85.34465789794922,-14.030509948730469,-13.736030578613281,-70.5699691772461,26.745826721191406,89.78993225097656,0.0,0.0,0.0 +3342,10.080335,1381648,0,2,1,0,0,[],84.97135162353516,-13.820887565612793,-13.747725486755371,-71.00279998779297,26.7360782623291,90.2500991821289,0.0,0.0,0.0 +3344,10.088359,1381649,0,2,1,0,0,[],84.57483673095703,-13.610347747802734,-13.751959800720215,-71.50496673583984,26.719684600830078,90.77173614501953,0.0,0.0,0.0 +3348,10.096324,1381650,0,2,1,0,0,[],84.1562271118164,-13.398904800415039,-13.748923301696777,-72.07293701171875,26.69680404663086,91.35104370117188,0.0,0.0,0.0 +3350,10.104322,1381651,0,2,1,10,10,"[2, 4]",83.71662139892578,-13.186573028564453,-13.738808631896973,-72.70317840576172,26.66758918762207,91.98423767089844,0.0,0.0,0.0 +3352,10.112375,1381652,0,2,1,0,0,[],83.25711822509766,-12.97336483001709,-13.721805572509766,-73.39217376708984,26.632198333740234,92.66751861572266,0.0,0.0,0.0 +3355,10.120334,1381653,0,2,1,10,0,[],82.77881622314453,-12.759296417236328,-13.69810676574707,-74.13638305664062,26.590784072875977,93.39710998535156,0.0,0.0,0.0 +3358,10.128336,1381654,0,2,1,0,0,[],82.28282165527344,-12.544381141662598,-13.667901992797852,-74.93228912353516,26.54350471496582,94.16920471191406,0.0,0.0,0.0 +3360,10.136376,1381655,0,2,1,0,0,[],81.77023315429688,-12.328633308410645,-13.63138198852539,-75.77635955810547,26.490514755249023,94.98002624511719,0.0,0.0,0.0 +3364,10.144328,1381656,0,2,1,0,0,[],81.24214935302734,-12.112067222595215,-13.588738441467285,-76.6650619506836,26.431968688964844,95.82577514648438,0.0,0.0,0.0 +3366,10.15236,1381657,0,2,1,0,0,[],80.69967651367188,-11.894696235656738,-13.540163040161133,-77.5948715209961,26.368022918701172,96.70267486572266,0.0,0.0,0.0 +3368,10.160365,1381658,0,2,1,0,0,[],80.14391326904297,-11.676534652709961,-13.485846519470215,-78.562255859375,26.298831939697266,97.60691833496094,0.0,0.0,0.0 +3371,10.168335,1381659,0,2,1,0,0,[],79.57596588134766,-11.457597732543945,-13.425979614257812,-79.5636978149414,26.224552154541016,98.53472900390625,0.0,0.0,0.0 +3374,10.176365,1381660,0,2,1,0,0,[],78.9969253540039,-11.237898826599121,-13.360753059387207,-80.59566497802734,26.145339965820312,99.48231506347656,0.0,0.0,0.0 +3376,10.184361,1381661,0,2,1,0,0,[],78.40789794921875,-11.017451286315918,-13.290358543395996,-81.65461730957031,26.06134796142578,100.44587707519531,0.0,0.0,0.0 +3380,10.192343,1381662,0,2,1,0,0,[],77.80998229980469,-10.796271324157715,-13.214986801147461,-82.73704528808594,25.972734451293945,101.42163848876953,0.0,0.0,0.0 +3382,10.200359,1381663,0,2,1,0,0,[],77.20428466796875,-10.574371337890625,-13.1348295211792,-83.83940887451172,25.879653930664062,102.40579986572266,0.0,0.0,0.0 +3384,10.208319,1381664,0,2,1,0,0,[],76.59190368652344,-10.351765632629395,-13.050076484680176,-84.95818328857422,25.78226089477539,103.39456939697266,0.0,0.0,0.0 +3386,10.216363,1381665,0,2,1,0,0,[],75.97393798828125,-10.128469467163086,-12.960920333862305,-86.08984375,25.680713653564453,104.38416290283203,0.0,0.0,0.0 +3390,10.224345,1381666,0,2,1,0,0,[],75.35149383544922,-9.904495239257812,-12.867551803588867,-87.23085021972656,25.575164794921875,105.37078857421875,0.0,0.0,0.0 +3392,10.232344,1381667,0,2,1,0,0,[],74.72566223144531,-9.679859161376953,-12.770161628723145,-88.37769317626953,25.465770721435547,106.35065460205078,0.0,0.0,0.0 +3394,10.240363,1381668,0,2,1,0,0,[],74.0975570678711,-9.454573631286621,-12.668940544128418,-89.52682495117188,25.35268783569336,107.31997680664062,0.0,0.0,0.0 +3398,10.24837,1381669,0,2,1,0,0,[],73.46826934814453,-9.228653907775879,-12.564079284667969,-90.67473602294922,25.23607063293457,108.27496337890625,0.0,0.0,0.0 +3400,10.256347,1381670,0,2,1,0,0,[],72.83890533447266,-9.002113342285156,-12.455770492553711,-91.81787872314453,25.116073608398438,109.2118148803711,0.0,0.0,0.0 +3402,10.264361,1381671,0,2,1,0,0,[],72.21056365966797,-8.774967193603516,-12.34420394897461,-92.95274353027344,24.992855072021484,110.12675476074219,0.0,0.0,0.0 +3405,10.272327,1381672,0,2,1,0,0,[],71.5843505859375,-8.547228813171387,-12.229571342468262,-94.07579040527344,24.866567611694336,111.01598358154297,0.0,0.0,0.0 +3408,10.280346,1381673,0,2,1,0,0,[],70.96135711669922,-8.3189115524292,-12.11206340789795,-95.18350219726562,24.737369537353516,111.87571716308594,0.0,0.0,0.0 +3410,10.288362,1381674,0,2,1,0,0,[],70.34269714355469,-8.090031623840332,-11.99187183380127,-96.2723388671875,24.60541343688965,112.70216369628906,0.0,0.0,0.0 +3414,10.296367,1381675,0,2,1,0,0,[],69.72946166992188,-7.860601425170898,-11.869186401367188,-97.33877563476562,24.470855712890625,113.49153137207031,0.0,0.0,0.0 +3416,10.304352,1381676,0,2,1,0,0,[],69.12274932861328,-7.630635738372803,-11.744199752807617,-98.37928771972656,24.33385467529297,114.24002838134766,0.0,0.0,0.0 +3418,10.312359,1381677,0,2,1,0,0,[],68.52367401123047,-7.400148391723633,-11.617101669311523,-99.39035034179688,24.194561004638672,114.9438705444336,0.0,0.0,0.0 +3422,10.320374,1381678,0,2,1,0,0,[],67.9333267211914,-7.169154167175293,-11.48808479309082,-100.36842346191406,24.053133010864258,115.59925842285156,0.0,0.0,0.0 +3424,10.328368,1381679,0,2,1,0,0,[],67.35281372070312,-6.937666893005371,-11.357337951660156,-101.30998992919922,23.909727096557617,116.2024154663086,0.0,0.0,0.0 +3426,10.336353,1381680,0,2,1,0,0,[],66.78323364257812,-6.705700397491455,-11.225054740905762,-102.21151733398438,23.764495849609375,116.74954223632812,0.0,0.0,0.0 +3428,10.344359,1381681,0,2,1,0,0,[],66.2256851196289,-6.473269462585449,-11.091423988342285,-103.06947326660156,23.617597579956055,117.23685455322266,0.0,0.0,0.0 +3431,10.35235,1381682,0,2,1,0,0,[],65.68126678466797,-6.240387916564941,-10.956637382507324,-103.88034057617188,23.469186782836914,117.66055297851562,0.0,0.0,0.0 +3434,10.36031,1381683,0,2,1,0,0,[],65.15109252929688,-6.0070695877075195,-10.820887565612793,-104.64057922363281,23.31941795349121,118.01686096191406,0.0,0.0,0.0 +3436,10.368368,1381684,0,2,1,0,0,[],64.6362533569336,-5.77332878112793,-10.68436336517334,-105.34667205810547,23.168447494506836,118.30197143554688,0.0,0.0,0.0 +3440,10.376384,1381685,0,2,1,0,0,[],64.13785552978516,-5.539180278778076,-10.547257423400879,-105.99508666992188,23.01643180847168,118.51211547851562,0.0,0.0,0.0 +3442,10.384328,1381686,0,2,1,0,0,[],63.65699005126953,-5.304637432098389,-10.409760475158691,-106.58229064941406,22.863523483276367,118.64348602294922,0.0,0.0,0.0 +3444,10.392359,1381687,0,2,1,0,0,[],63.19477081298828,-5.0697150230407715,-10.272063255310059,-107.1047592163086,22.709880828857422,118.69229125976562,0.0,0.0,0.0 +3448,10.400354,1381688,0,2,1,0,0,[],62.75229263305664,-4.834426403045654,-10.134357452392578,-107.55896759033203,22.5556583404541,118.6547622680664,0.0,0.0,0.0 +3450,10.408351,1381689,0,2,1,0,0,[],62.33065414428711,-4.5987868309021,-9.996832847595215,-107.94137573242188,22.401010513305664,118.52708435058594,0.0,0.0,0.0 +3452,10.416358,1381690,0,2,1,0,0,[],61.93096160888672,-4.362809181213379,-9.859682083129883,-108.24847412109375,22.246095657348633,118.30548095703125,0.0,0.0,0.0 +3455,10.424364,1381691,0,2,1,0,0,[],61.55429458618164,-4.126511096954346,-9.723097801208496,-108.476806640625,22.0910701751709,117.98627471923828,0.0,0.0,0.0 +3458,10.432323,1381692,0,2,1,0,0,[],61.201236724853516,-3.889975070953369,-9.587350845336914,-108.62549591064453,21.936195373535156,117.56914520263672,0.0,0.0,0.0 +3460,10.440365,1381693,0,2,1,0,0,[],60.871788024902344,-3.6533615589141846,-9.452800750732422,-108.69657135009766,21.78185272216797,117.05757141113281,0.0,0.0,0.0 +3464,10.448318,1381694,0,2,1,0,0,[],60.56592559814453,-3.416835069656372,-9.319809913635254,-108.69223022460938,21.62842559814453,116.45528411865234,0.0,0.0,0.0 +3466,10.456342,1381695,0,2,1,0,0,[],60.28361511230469,-3.1805591583251953,-9.188741683959961,-108.61465454101562,21.476299285888672,115.7659683227539,0.0,0.0,0.0 +3468,10.464362,1381696,0,2,1,0,0,[],60.024837493896484,-2.9446983337402344,-9.05996036529541,-108.46603393554688,21.32586097717285,114.99333953857422,0.0,0.0,0.0 +3470,10.472333,1381697,0,2,1,10,10,"[2, 4]",59.78955841064453,-2.7094168663024902,-8.933828353881836,-108.24857330322266,21.177492141723633,114.14110565185547,0.0,0.0,0.0 +3474,10.480323,1381698,0,2,1,0,0,[],59.577754974365234,-2.4748787879943848,-8.810709953308105,-107.96444702148438,21.03158187866211,113.21296691894531,0.0,0.0,0.0 +3476,10.488357,1381699,0,2,1,10,0,[],59.389400482177734,-2.241248369216919,-8.690967559814453,-107.61585998535156,20.888513565063477,112.21263885498047,0.0,0.0,0.0 +3478,10.496305,1381700,0,2,1,0,0,[],59.224464416503906,-2.0086896419525146,-8.57496452331543,-107.20499420166016,20.748672485351562,111.14381408691406,0.0,0.0,0.0 +3481,10.504344,1381701,0,2,1,0,0,[],59.08292007446289,-1.7773669958114624,-8.463065147399902,-106.73404693603516,20.612443923950195,110.01020812988281,0.0,0.0,0.0 +3484,10.512371,1381702,0,2,1,0,0,[],58.96473693847656,-1.5474443435668945,-8.355632781982422,-106.20520782470703,20.480213165283203,108.81552124023438,0.0,0.0,0.0 +3486,10.520378,1381703,0,2,1,0,0,[],58.869895935058594,-1.3190861940383911,-8.253030776977539,-105.62066650390625,20.352365493774414,107.56346893310547,0.0,0.0,0.0 +3490,10.528368,1381704,0,2,1,0,0,[],58.798362731933594,-1.0924564599990845,-8.155621528625488,-104.98261260986328,20.229286193847656,106.25774383544922,0.0,0.0,0.0 +3492,10.536335,1381705,0,2,1,0,0,[],58.75011444091797,-0.8677194118499756,-8.063769340515137,-104.29324340820312,20.111360549926758,104.90206146240234,0.0,0.0,0.0 +3494,10.544355,1381706,0,2,1,0,0,[],58.725120544433594,-0.6450392603874207,-7.977837562561035,-103.55474090576172,19.998973846435547,103.5001220703125,0.0,0.0,0.0 +3498,10.552358,1381707,0,2,1,0,0,[],58.723350524902344,-0.4245802164077759,-7.898189544677734,-102.76930236816406,19.892513275146484,102.0556411743164,0.0,0.0,0.0 +3500,10.560366,1381708,0,2,1,0,0,[],58.74478530883789,-0.2065064013004303,-7.825188159942627,-101.93912506103516,19.792360305786133,100.57231140136719,0.0,0.0,0.0 +3502,10.568367,1381709,0,2,1,0,0,[],58.78939437866211,0.009017974138259888,-7.759197235107422,-101.0663833618164,19.698902130126953,99.05384826660156,0.0,0.0,0.0 +3505,10.576354,1381710,0,2,1,0,0,[],58.857147216796875,0.22182874381542206,-7.70058012008667,-100.15328216552734,19.612524032592773,97.50395202636719,0.0,0.0,0.0 +3508,10.584318,1381711,0,2,1,0,0,[],58.94801712036133,0.4317617118358612,-7.649700164794922,-99.20201110839844,19.533611297607422,95.92633819580078,0.0,0.0,0.0 +3510,10.592346,1381712,0,2,1,0,0,[],59.061981201171875,0.6386526823043823,-7.6069207191467285,-98.21475219726562,19.462549209594727,94.32469940185547,0.0,0.0,0.0 +3513,10.600357,1381713,0,2,1,0,0,[],59.199005126953125,0.8423374891281128,-7.572605609893799,-97.19371032714844,19.399721145629883,92.70275115966797,0.0,0.0,0.0 +3516,10.608354,1381714,0,2,1,0,0,[],59.35906982421875,1.0426520109176636,-7.547117710113525,-96.14106750488281,19.345516204833984,91.06419372558594,0.0,0.0,0.0 +3519,10.616357,1381715,0,2,1,0,0,[],59.54214096069336,1.2394319772720337,-7.530820369720459,-95.05901336669922,19.300315856933594,89.4127426147461,0.0,0.0,0.0 +3521,10.624336,1381716,0,2,1,0,0,[],59.74819564819336,1.4325131177902222,-7.52407693862915,-93.94974517822266,19.264509201049805,87.75209045410156,0.0,0.0,0.0 +3525,10.632376,1381717,0,2,1,0,0,[],59.977203369140625,1.6217315196990967,-7.527251720428467,-92.8154525756836,19.23847770690918,86.08595275878906,0.0,0.0,0.0 +3527,10.640361,1381718,0,2,1,0,0,[],60.2291374206543,1.8069227933883667,-7.540706634521484,-91.6583251953125,19.22260856628418,84.41802978515625,0.0,0.0,0.0 +3529,10.648355,1381719,0,2,1,0,0,[],60.503971099853516,1.9879227876663208,-7.5648064613342285,-90.48055267333984,19.21728515625,82.75203704833984,0.0,0.0,0.0 +3532,10.656291,1381720,0,2,1,0,0,[],60.80167770385742,2.164567232131958,-7.599913597106934,-89.2843246459961,19.222896575927734,81.09166717529297,0.0,0.0,0.0 +3535,10.66434,1381721,0,2,1,0,0,[],61.122230529785156,2.3366923332214355,-7.646391868591309,-88.07183837890625,19.239824295043945,79.44063568115234,0.0,0.0,0.0 +3537,10.672354,1381722,0,2,1,0,0,[],61.465599060058594,2.504133462905884,-7.7046051025390625,-86.84528350830078,19.26845359802246,77.80264282226562,0.0,0.0,0.0 +3540,10.680327,1381723,0,2,1,0,0,[],61.831756591796875,2.666726589202881,-7.77491569519043,-85.60684967041016,19.309173583984375,76.181396484375,0.0,0.0,0.0 +3543,10.688353,1381724,0,2,1,0,0,[],62.220680236816406,2.824307441711426,-7.857687473297119,-84.35873413085938,19.36236572265625,74.58061218261719,0.0,0.0,0.0 +3545,10.696353,1381725,0,2,1,0,0,[],62.6323356628418,2.9767119884490967,-7.95328426361084,-83.10311126708984,19.428417205810547,73.00397491455078,0.0,0.0,0.0 +3549,10.704372,1381726,0,2,1,0,0,[],63.06669998168945,3.1237761974334717,-8.062068939208984,-81.84219360351562,19.507713317871094,71.45521545410156,0.0,0.0,0.0 +3551,10.712363,1381727,0,2,1,0,0,[],63.523746490478516,3.2653353214263916,-8.184405326843262,-80.57815551757812,19.600637435913086,69.93801879882812,0.0,0.0,0.0 +3553,10.720373,1381728,0,2,1,0,0,[],64.00344848632812,3.4012258052825928,-8.320655822753906,-79.31319427490234,19.707576751708984,68.45610046386719,0.0,0.0,0.0 +3555,10.728336,1381729,0,2,1,0,0,[],64.50576782226562,3.531283140182495,-8.471184730529785,-78.04949951171875,19.828914642333984,67.01316833496094,0.0,0.0,0.0 +3559,10.736351,1381730,0,2,1,0,0,[],65.03069305419922,3.6553432941436768,-8.63635540008545,-76.78926849365234,19.965038299560547,65.61292266845703,0.0,0.0,0.0 +3561,10.744355,1381731,0,2,1,0,0,[],65.57818603515625,3.7732417583465576,-8.81653118133545,-75.5346908569336,20.1163330078125,64.25907135009766,0.0,0.0,0.0 +3563,10.752342,1381732,0,2,1,0,0,[],66.14822387695312,3.884814739227295,-9.012075424194336,-74.28794860839844,20.28318214416504,62.955322265625,0.0,0.0,0.0 +3566,10.760365,1381733,0,2,1,0,0,[],66.74078369140625,3.9898979663848877,-9.223350524902344,-73.05123901367188,20.465970993041992,61.70538330078125,0.0,0.0,0.0 +3569,10.768371,1381734,0,2,1,0,0,[],67.35582733154297,4.088326930999756,-9.450721740722656,-71.8267593383789,20.66508674621582,60.51295471191406,0.0,0.0,0.0 +3571,10.776392,1381735,0,2,1,0,0,[],67.99333190917969,4.179938316345215,-9.694551467895508,-70.61668395996094,20.88091468811035,59.381744384765625,0.0,0.0,0.0 +3575,10.784358,1381736,0,2,1,0,0,[],68.65327453613281,4.264566898345947,-9.95520305633545,-69.42322540283203,21.11383819580078,58.315460205078125,0.0,0.0,0.0 +3577,10.792296,1381737,0,2,1,0,0,[],69.33561706542969,4.342048645019531,-10.233039855957031,-68.2485580444336,21.364242553710938,57.317806243896484,0.0,0.0,0.0 +3579,10.80032,1381738,0,2,1,0,0,[],70.04034423828125,4.412220001220703,-10.528425216674805,-67.09488677978516,21.63251495361328,56.39249038696289,0.0,0.0,0.0 +3583,10.808352,1381739,0,2,1,0,0,[],70.76742553710938,4.474916458129883,-10.84172248840332,-65.96438598632812,21.91904067993164,55.543216705322266,0.0,0.0,0.0 +3585,10.816357,1381740,0,2,1,0,0,[],71.51683044433594,4.529973983764648,-11.173295974731445,-64.85926055908203,22.224201202392578,54.7736930847168,0.0,0.0,0.0 +3587,10.824396,1381741,0,2,1,0,0,[],72.28853607177734,4.577227592468262,-11.523507118225098,-63.78169631958008,22.54838752746582,54.087623596191406,0.0,0.0,0.0 +3590,10.832369,1381742,0,2,1,0,0,[],73.08250427246094,4.616514205932617,-11.892722129821777,-62.73388671875,22.891979217529297,53.488712310791016,0.0,0.0,0.0 +3593,10.840353,1381743,0,2,1,0,0,[],73.89871978759766,4.647669315338135,-12.281301498413086,-61.718021392822266,23.2553653717041,52.98067092895508,0.0,0.0,0.0 +3595,10.848358,1381744,0,2,1,0,0,[],74.73715209960938,4.670528411865234,-12.689610481262207,-60.736289978027344,23.63892936706543,52.567203521728516,0.0,0.0,0.0 +3597,10.856329,1381745,0,2,1,0,0,[],75.59777069091797,4.684927463531494,-13.118011474609375,-59.79088592529297,24.043058395385742,52.25201416015625,0.0,0.0,0.0 +3601,10.86435,1381746,0,2,1,0,0,[],76.48054504394531,4.690702438354492,-13.566868782043457,-58.88399887084961,24.4681339263916,52.0388069152832,0.0,0.0,0.0 +3603,10.872361,1381747,0,2,1,0,0,[],77.38545989990234,4.687688827514648,-14.036545753479004,-58.017818450927734,24.914546966552734,51.93129348754883,0.0,0.0,0.0 +3605,10.880344,1381748,0,2,1,0,0,[],78.3124771118164,4.675722599029541,-14.52740478515625,-57.194541931152344,25.38267707824707,51.93317413330078,0.0,0.0,0.0 +3609,10.888266,1381749,0,2,1,0,0,[],79.26158142089844,4.654639720916748,-15.039809226989746,-56.41635513305664,25.87291145324707,52.04815673828125,0.0,0.0,0.0 +3611,10.896324,1381750,0,2,1,0,0,[],80.23268127441406,4.624288082122803,-15.574081420898438,-55.68537521362305,26.385595321655273,52.279754638671875,0.0,0.0,0.0 +3613,10.904356,1381751,0,2,1,0,0,[],81.22460174560547,4.584806442260742,-16.129499435424805,-55.00190734863281,26.920007705688477,52.62669372558594,0.0,0.0,0.0 +3616,10.912358,1381752,0,2,1,0,0,[],82.23499298095703,4.5366363525390625,-16.704267501831055,-54.36437225341797,27.47434425354004,53.08274459838867,0.0,0.0,0.0 +3619,10.920312,1381753,0,2,1,0,0,[],83.2614517211914,4.480233192443848,-17.29653549194336,-53.77110290527344,28.046743392944336,53.64146423339844,0.0,0.0,0.0 +3621,10.928375,1381754,0,2,1,0,0,[],84.30158996582031,4.416051387786865,-17.90445899963379,-53.22044372558594,28.635347366333008,54.296390533447266,0.0,0.0,0.0 +3625,10.93637,1381755,0,2,1,0,0,[],85.3530044555664,4.344546318054199,-18.52618980407715,-52.710723876953125,29.238292694091797,55.041080474853516,0.0,0.0,0.0 +3627,10.944371,1381756,0,2,1,12,12,"[3, 4]",86.41329193115234,4.266173362731934,-19.15987777709961,-52.24028015136719,29.853723526000977,55.86907196044922,0.0,0.0,0.0 +3629,10.952347,1381757,0,2,1,0,0,[],87.48005676269531,4.181386947631836,-19.803678512573242,-51.80745315551758,30.479778289794922,56.773921966552734,0.0,0.0,0.0 +3633,10.960362,1381758,0,2,1,12,0,[],88.55091094970703,4.09064245223999,-20.455745697021484,-51.41057586669922,31.11459732055664,57.749176025390625,0.0,0.0,0.0 +3635,10.968355,1381759,0,2,1,0,0,[],89.62344360351562,3.9943950176239014,-21.114229202270508,-51.04798889160156,31.756322860717773,58.78837966918945,0.0,0.0,0.0 +3637,10.976351,1381760,0,2,1,0,0,[],90.69525146484375,3.893099784851074,-21.777280807495117,-50.71802520751953,32.40309524536133,59.88508224487305,0.0,0.0,0.0 +3639,10.984331,1381761,0,2,1,0,0,[],91.76395416259766,3.7872116565704346,-22.443056106567383,-50.41901779174805,33.05305099487305,61.03282928466797,0.0,0.0,0.0 +3642,10.99234,1381762,0,2,1,0,0,[],92.82714080810547,3.6771857738494873,-23.10970687866211,-50.14931106567383,33.7043342590332,62.22517013549805,0.0,0.0,0.0 +3645,11.000348,1381763,0,2,1,0,0,[],93.88241577148438,3.5634772777557373,-23.775386810302734,-49.90723419189453,34.35508728027344,63.45565414428711,0.0,0.0,0.0 +3648,11.008384,1381764,0,2,1,0,0,[],94.92738342285156,3.4465410709381104,-24.43824577331543,-49.69112777709961,35.00344467163086,64.71782684326172,0.0,0.0,0.0 +3652,11.016352,1381765,0,2,1,0,0,[],95.95964050292969,3.3268322944641113,-25.096437454223633,-49.49932861328125,35.64754867553711,66.00524139404297,0.0,0.0,0.0 +3654,11.024374,1381766,0,2,1,0,0,[],96.9767837524414,3.204806089401245,-25.74811553955078,-49.330169677734375,36.28554153442383,67.31143951416016,0.0,0.0,0.0 +3656,11.032373,1381767,0,2,1,0,0,[],97.97643280029297,3.0809173583984375,-26.39143180847168,-49.18198776245117,36.915565490722656,68.62996673583984,0.0,0.0,0.0 +3660,11.040369,1381768,0,2,1,0,0,[],98.95616912841797,2.9556214809417725,-27.024538040161133,-49.053123474121094,37.5357551574707,69.95437622070312,0.0,0.0,0.0 +3662,11.048342,1381769,0,2,1,0,0,[],99.91361236572266,2.829373359680176,-27.645587921142578,-48.94190979003906,38.144256591796875,71.27821350097656,0.0,0.0,0.0 +3664,11.056335,1381770,0,2,1,0,0,[],100.84634399414062,2.7026278972625732,-28.252735137939453,-48.8466796875,38.73920822143555,72.59503173828125,0.0,0.0,0.0 +3667,11.064364,1381771,0,2,1,0,0,[],101.75198364257812,2.575840473175049,-28.84412956237793,-48.765777587890625,39.318748474121094,73.89836883544922,0.0,0.0,0.0 +3670,11.072362,1381772,0,2,1,0,0,[],102.62812805175781,2.4494659900665283,-29.417926788330078,-48.697532653808594,39.881019592285156,75.18177795410156,0.0,0.0,0.0 +3672,11.080362,1381773,0,2,1,0,0,[],103.47237396240234,2.3239595890045166,-29.972278594970703,-48.64028549194336,40.42416000366211,76.43881225585938,0.0,0.0,0.0 +3676,11.088382,1381774,0,2,1,0,0,[],104.2823257446289,2.1997761726379395,-30.505334854125977,-48.59237289428711,40.94631576538086,77.66300964355469,0.0,0.0,0.0 +3678,11.096364,1381775,0,2,1,0,0,[],105.05558013916016,2.077371120452881,-31.0152530670166,-48.552127838134766,41.445621490478516,78.8479232788086,0.0,0.0,0.0 +3680,11.10436,1381776,0,2,1,0,0,[],105.78974914550781,1.957199215888977,-31.500181198120117,-48.51789093017578,41.92021560668945,79.98709869384766,0.0,0.0,0.0 +3682,11.112364,1381777,0,2,1,0,0,[],106.482421875,1.8397157192230225,-31.958274841308594,-48.48799133300781,42.368247985839844,81.07408905029297,0.0,0.0,0.0 +3686,11.12035,1381778,0,2,1,0,0,[],107.13121795654297,1.7253755331039429,-32.3876838684082,-48.46077346801758,42.78784942626953,82.10243225097656,0.0,0.0,0.0 +3688,11.128276,1381779,0,2,1,0,0,[],107.73371887207031,1.6146339178085327,-32.786563873291016,-48.4345703125,43.17716598510742,83.06568908691406,0.0,0.0,0.0 +3690,11.136386,1381780,0,2,1,0,0,[],108.28753662109375,1.5079458951950073,-33.15306854248047,-48.407718658447266,43.53433609008789,83.9573974609375,0.0,0.0,0.0 +3694,11.144347,1381781,0,2,1,0,0,[],108.79026794433594,1.4057663679122925,-33.48534393310547,-48.3785514831543,43.85750198364258,84.77110290527344,0.0,0.0,0.0 +3696,11.152287,1381782,0,2,1,0,0,[],109.23975372314453,1.308494210243225,-33.781700134277344,-48.34553527832031,44.144962310791016,85.50084686279297,0.0,0.0,0.0 +3698,11.160383,1381783,0,2,1,0,0,[],109.63668060302734,1.2158204317092896,-34.04230880737305,-48.308650970458984,44.397056579589844,86.14665985107422,0.0,0.0,0.0 +3701,11.168358,1381784,0,2,1,0,0,[],109.98371124267578,1.1269536018371582,-34.268619537353516,-48.2689323425293,44.61551284790039,86.71269989013672,0.0,0.0,0.0 +3704,11.176345,1381785,0,2,1,0,0,[],110.28353118896484,1.041093111038208,-34.46210861206055,-48.227439880371094,44.80208206176758,87.20319366455078,0.0,0.0,0.0 +3706,11.184351,1381786,0,2,1,0,0,[],110.53883361816406,0.9574383497238159,-34.6242561340332,-48.185218811035156,44.95851135253906,87.62236785888672,0.0,0.0,0.0 +3710,11.192339,1381787,0,2,1,10,10,"[2, 4]",110.7523193359375,0.8751887679100037,-34.756534576416016,-48.14332580566406,45.0865592956543,87.97444152832031,0.0,0.0,0.0 +3712,11.20037,1381788,0,2,1,0,0,[],110.92666625976562,0.7935436964035034,-34.86042404174805,-48.102813720703125,45.1879768371582,88.26365661621094,0.0,0.0,0.0 +3714,11.208359,1381789,0,2,1,10,0,[],111.0645751953125,0.7117024660110474,-34.93739700317383,-48.06473159790039,45.2645149230957,88.49422454833984,0.0,0.0,0.0 +3717,11.216339,1381790,0,2,1,0,0,[],111.16873931884766,0.628864586353302,-34.98893356323242,-48.0301399230957,45.31792068481445,88.6703872680664,0.0,0.0,0.0 +3720,11.224358,1381791,0,2,1,0,0,[],111.2418441772461,0.54422926902771,-35.016510009765625,-48.000083923339844,45.349952697753906,88.7963638305664,0.0,0.0,0.0 +3722,11.232351,1381792,0,2,1,0,0,[],111.28658294677734,0.45699605345726013,-35.02159881591797,-47.975624084472656,45.362361907958984,88.87638854980469,0.0,0.0,0.0 +3724,11.24036,1381793,0,2,1,0,0,[],111.30564880371094,0.3663642406463623,-35.00567626953125,-47.95780563354492,45.35689926147461,88.91468048095703,0.0,0.0,0.0 +3727,11.248337,1381794,0,2,1,0,0,[],111.30174255371094,0.27153319120407104,-34.97022247314453,-47.94768524169922,45.3353157043457,88.91547393798828,0.0,0.0,0.0 +3730,11.256344,1381795,0,2,1,0,0,[],111.27754211425781,0.1717023253440857,-34.91671371459961,-47.946319580078125,45.29936599731445,88.88298797607422,0.0,0.0,0.0 +3732,11.264331,1381796,0,2,1,0,0,[],111.23574829101562,0.06607100367546082,-34.84662628173828,-47.95475387573242,45.250797271728516,88.82146453857422,0.0,0.0,0.0 +3736,11.272359,1381797,0,2,1,0,0,[],111.17904663085938,-0.04616139084100723,-34.76143264770508,-47.97404861450195,45.191368103027344,88.73511505126953,0.0,0.0,0.0 +3738,11.280359,1381798,0,2,1,0,0,[],111.1101303100586,-0.1657954752445221,-34.6626091003418,-48.005252838134766,45.122825622558594,88.628173828125,0.0,0.0,0.0 +3740,11.288358,1381799,0,2,1,0,0,[],111.03170013427734,-0.2936318516731262,-34.551639556884766,-48.04941940307617,45.04692459106445,88.50486755371094,0.0,0.0,0.0 +3744,11.296343,1381800,0,2,1,0,0,[],110.94644165039062,-0.43047118186950684,-34.42999267578125,-48.10760498046875,44.96541213989258,88.36943054199219,0.0,0.0,0.0 +3746,11.304349,1381801,0,2,1,0,0,[],110.85704803466797,-0.5771140456199646,-34.29914474487305,-48.18085861206055,44.88004684448242,88.22608184814453,0.0,0.0,0.0 +3748,11.312384,1381802,0,2,1,0,0,[],110.76620483398438,-0.7343611121177673,-34.160579681396484,-48.270233154296875,44.792579650878906,88.07904815673828,0.0,0.0,0.0 +3751,11.320352,1381803,0,2,1,0,0,[],110.6766128540039,-0.9030129313468933,-34.01576614379883,-48.37678146362305,44.70475769042969,87.93256378173828,0.0,0.0,0.0 +3754,11.328352,1381804,0,2,1,0,0,[],110.59095764160156,-1.0838701725006104,-33.86618423461914,-48.50156021118164,44.61833572387695,87.79084777832031,0.0,0.0,0.0 +3756,11.336423,1381805,0,2,1,0,0,[],110.51193237304688,-1.2777334451675415,-33.71330642700195,-48.64562225341797,44.53506851196289,87.65813446044922,0.0,0.0,0.0 +3760,11.344366,1381806,0,2,1,0,0,[],110.4422378540039,-1.4854034185409546,-33.558616638183594,-48.81001663208008,44.456703186035156,87.53865051269531,0.0,0.0,0.0 +3762,11.352354,1381807,0,2,1,0,0,[],110.38455200195312,-1.707680583000183,-33.40358352661133,-48.99579620361328,44.3849983215332,87.4366226196289,0.0,0.0,0.0 +3764,11.360381,1381808,0,2,1,0,0,[],110.3415756225586,-1.9453656673431396,-33.24968719482422,-49.204017639160156,44.32169723510742,87.35627746582031,0.0,0.0,0.0 +3766,11.368361,1381809,0,2,1,0,0,[],110.31600189208984,-2.1992592811584473,-33.09840393066406,-49.435733795166016,44.268558502197266,87.30184173583984,0.0,0.0,0.0 +3770,11.376353,1381810,0,2,1,0,0,[],110.31051635742188,-2.4701619148254395,-32.951210021972656,-49.691993713378906,44.22732925415039,87.27754211425781,0.0,0.0,0.0 +3772,11.38434,1381811,0,2,1,0,0,[],110.32781219482422,-2.7588744163513184,-32.80957794189453,-49.973854064941406,44.19976806640625,87.28760528564453,0.0,0.0,0.0 +3774,11.39234,1381812,0,2,1,0,0,[],110.3705825805664,-3.066197395324707,-32.67498779296875,-50.28236770629883,44.1876220703125,87.33626556396484,0.0,0.0,0.0 +3777,11.400349,1381813,0,2,1,0,0,[],110.4415283203125,-3.3929312229156494,-32.548919677734375,-50.618587493896484,44.19264602661133,87.42774200439453,0.0,0.0,0.0 +3780,11.408361,1381814,0,2,1,0,0,[],110.54332733154297,-3.7398767471313477,-32.43284225463867,-50.98356246948242,44.21658706665039,87.56626892089844,0.0,0.0,0.0 +3782,11.416346,1381815,0,2,1,0,0,[],110.67842102050781,-4.107728958129883,-32.328121185302734,-51.378108978271484,44.261051177978516,87.75560760498047,0.0,0.0,0.0 +3786,11.424419,1381816,0,2,1,0,0,[],110.84618377685547,-4.495945453643799,-32.234798431396484,-51.8001823425293,44.32585144042969,87.99407196044922,0.0,0.0,0.0 +3788,11.43236,1381817,0,2,1,0,0,[],111.04402923583984,-4.9031829833984375,-32.15205764770508,-52.24589920043945,44.4096565246582,88.2764663696289,0.0,0.0,0.0 +3790,11.440368,1381818,0,2,1,0,0,[],111.26933288574219,-5.328083038330078,-32.079063415527344,-52.711341857910156,44.511104583740234,88.59752655029297,0.0,0.0,0.0 +3794,11.448357,1381819,0,2,1,0,0,[],111.51945495605469,-5.769288063049316,-32.014984130859375,-53.19259262084961,44.628841400146484,88.95197296142578,0.0,0.0,0.0 +3796,11.456361,1381820,0,2,1,10,10,"[2, 4]",111.79177856445312,-6.225440502166748,-31.958988189697266,-53.68573760986328,44.76150894165039,89.33454895019531,0.0,0.0,0.0 +3798,11.464349,1381821,0,2,1,0,0,[],112.08367919921875,-6.6951823234558105,-31.910240173339844,-54.186851501464844,44.907752990722656,89.73998260498047,0.0,0.0,0.0 +3801,11.472359,1381822,0,2,1,10,0,[],112.39252471923828,-7.177155494689941,-31.86791229248047,-54.69202423095703,45.06621170043945,90.16300964355469,0.0,0.0,0.0 +3804,11.480354,1381823,0,2,1,0,0,[],112.71568298339844,-7.6700029373168945,-31.831167221069336,-55.19733428955078,45.23553466796875,90.5983657836914,0.0,0.0,0.0 +3806,11.488354,1381824,0,2,1,0,0,[],113.05052947998047,-8.17236614227295,-31.799177169799805,-55.6988639831543,45.414363861083984,91.04078674316406,0.0,0.0,0.0 +3808,11.496364,1381825,0,2,1,0,0,[],113.39444732666016,-8.68288803100586,-31.771106719970703,-56.19269561767578,45.60133743286133,91.48500061035156,0.0,0.0,0.0 +3811,11.50432,1381826,0,2,1,0,0,[],113.74479675292969,-9.200209617614746,-31.746124267578125,-56.6749153137207,45.79510498046875,91.92573547363281,0.0,0.0,0.0 +3814,11.512346,1381827,0,2,1,0,0,[],114.09895324707031,-9.72297477722168,-31.72339630126953,-57.1416015625,45.99430847167969,92.35774230957031,0.0,0.0,0.0 +3816,11.520342,1381828,0,2,1,0,0,[],114.45429992675781,-10.249824523925781,-31.70209312438965,-57.58884048461914,46.19758987426758,92.77574157714844,0.0,0.0,0.0 +3820,11.528362,1381829,0,2,1,0,0,[],114.80818939208984,-10.779400825500488,-31.681379318237305,-58.01271438598633,46.40359115600586,93.17446899414062,0.0,0.0,0.0 +3822,11.536358,1381830,0,2,1,0,0,[],115.15801239013672,-11.310347557067871,-31.660425186157227,-58.409297943115234,46.61095428466797,93.54866027832031,0.0,0.0,0.0 +3824,11.544391,1381831,0,2,1,0,0,[],115.50113677978516,-11.84130573272705,-31.638395309448242,-58.77468490600586,46.818328857421875,93.8930435180664,0.0,0.0,0.0 +3827,11.552367,1381832,0,2,1,0,0,[],115.8349380493164,-12.370917320251465,-31.614459991455078,-59.104949951171875,47.024356842041016,94.20236206054688,0.0,0.0,0.0 +3830,11.560355,1381833,0,2,1,0,0,[],116.15677642822266,-12.89782428741455,-31.587785720825195,-59.39617919921875,47.22767639160156,94.4713363647461,0.0,0.0,0.0 +3832,11.568351,1381834,0,2,1,0,0,[],116.46403503417969,-13.420670509338379,-31.557538986206055,-59.64445495605469,47.42693328857422,94.69471740722656,0.0,0.0,0.0 +3836,11.576403,1381835,0,2,1,0,0,[],116.75408935546875,-13.93809700012207,-31.52288818359375,-59.845855712890625,47.62077331542969,94.86721801757812,0.0,0.0,0.0 +3838,11.584351,1381836,0,2,1,0,0,[],117.02430725097656,-14.448745727539062,-31.483001708984375,-59.9964714050293,47.807838439941406,94.98358917236328,0.0,0.0,0.0 +3840,11.592356,1381837,0,2,1,0,0,[],117.27206420898438,-14.95125961303711,-31.43704605102539,-60.092376708984375,47.98676681518555,95.03855895996094,0.0,0.0,0.0 +3844,11.600349,1381838,0,2,1,0,0,[],117.4947280883789,-15.444280624389648,-31.38418960571289,-60.12965774536133,48.156211853027344,95.02686309814453,0.0,0.0,0.0 +3846,11.608319,1381839,0,2,1,0,0,[],117.6896743774414,-15.926450729370117,-31.323598861694336,-60.104400634765625,48.3148078918457,94.94322204589844,0.0,0.0,0.0 +3848,11.616358,1381840,0,2,1,0,0,[],117.85427856445312,-16.396413803100586,-31.25444221496582,-60.01268005371094,48.461204528808594,94.78238677978516,0.0,0.0,0.0 +3850,11.624385,1381841,0,2,1,0,0,[],117.98590850830078,-16.852807998657227,-31.175888061523438,-59.850582122802734,48.59403991699219,94.5390853881836,0.0,0.0,0.0 +3854,11.63236,1381842,0,2,1,0,0,[],118.08193969726562,-17.294281005859375,-31.087100982666016,-59.614192962646484,48.71196365356445,94.20804595947266,0.0,0.0,0.0 +3856,11.640352,1381843,0,2,1,0,0,[],118.14005279541016,-17.719615936279297,-30.987350463867188,-59.300086975097656,48.81376266479492,93.78465270996094,0.0,0.0,0.0 +3858,11.648356,1381844,0,2,1,0,0,[],118.16007232666016,-18.128637313842773,-30.87660026550293,-58.90842056274414,48.89933395385742,93.26895141601562,0.0,0.0,0.0 +3861,11.656359,1381845,0,2,1,0,0,[],118.14276885986328,-18.521625518798828,-30.755126953125,-58.44089126586914,48.969032287597656,92.6629867553711,0.0,0.0,0.0 +3864,11.66436,1381846,0,2,1,0,0,[],118.08892822265625,-18.89885902404785,-30.62320327758789,-57.89921188354492,49.023223876953125,91.96883392333984,0.0,0.0,0.0 +3866,11.672372,1381847,0,2,1,0,0,[],117.99932861328125,-19.260616302490234,-30.481103897094727,-57.28509521484375,49.062278747558594,91.18856811523438,0.0,0.0,0.0 +3870,11.680362,1381848,0,2,1,10,10,"[2, 4]",117.87474060058594,-19.607177734375,-30.329103469848633,-56.600250244140625,49.08655548095703,90.32424926757812,0.0,0.0,0.0 +3872,11.688367,1381849,0,2,1,0,0,[],117.7159423828125,-19.938819885253906,-30.167478561401367,-55.84638595581055,49.0964241027832,89.37794494628906,0.0,0.0,0.0 +3874,11.696354,1381850,0,2,1,10,0,[],117.52371978759766,-20.255823135375977,-29.996501922607422,-55.02521514892578,49.092247009277344,88.35173797607422,0.0,0.0,0.0 +3877,11.704347,1381851,0,2,1,0,0,[],117.29884338378906,-20.558469772338867,-29.816448211669922,-54.138450622558594,49.07439041137695,87.24768829345703,0.0,0.0,0.0 +3880,11.712372,1381852,0,2,1,0,0,[],117.04209899902344,-20.847034454345703,-29.627592086791992,-53.18779754638672,49.043212890625,86.06787109375,0.0,0.0,0.0 +3882,11.720365,1381853,0,2,1,0,0,[],116.7542495727539,-21.121797561645508,-29.430208206176758,-52.17496871948242,48.999088287353516,84.81434631347656,0.0,0.0,0.0 +3886,11.728341,1381854,0,2,1,0,0,[],116.43608856201172,-21.383041381835938,-29.224571228027344,-51.10167694091797,48.94237518310547,83.48918151855469,0.0,0.0,0.0 +3888,11.736391,1381855,0,2,1,0,0,[],116.08837890625,-21.63104248046875,-29.010957717895508,-49.969635009765625,48.87343978881836,82.0944595336914,0.0,0.0,0.0 +3890,11.744375,1381856,0,2,1,0,0,[],115.7119140625,-21.866079330444336,-28.78963851928711,-48.780548095703125,48.79264831542969,80.63224029541016,0.0,0.0,0.0 +3892,11.752357,1381857,0,2,1,0,0,[],115.30745697021484,-22.08843231201172,-28.56089210510254,-47.53612518310547,48.70036697387695,79.10459899902344,0.0,0.0,0.0 +3896,11.760386,1381858,0,2,1,0,0,[],114.87579345703125,-22.298381805419922,-28.32499122619629,-46.23808670043945,48.596954345703125,77.51359558105469,0.0,0.0,0.0 +3898,11.768354,1381859,0,2,1,0,0,[],114.4177017211914,-22.496206283569336,-28.082210540771484,-44.88813400268555,48.48278045654297,75.8613052368164,0.0,0.0,0.0 +3900,11.776389,1381860,0,2,1,0,0,[],113.9339599609375,-22.68218231201172,-27.832822799682617,-43.487979888916016,48.358211517333984,74.1498031616211,0.0,0.0,0.0 +3904,11.78436,1381861,0,2,1,0,0,[],113.42533874511719,-22.85659408569336,-27.577106475830078,-42.03934097290039,48.22360610961914,72.38114166259766,0.0,0.0,0.0 +3906,11.792318,1381862,0,2,1,0,0,[],112.89262390136719,-23.019716262817383,-27.31533432006836,-40.54391860961914,48.0793342590332,70.55740356445312,0.0,0.0,0.0 +3908,11.800377,1381863,0,2,1,0,0,[],112.33658599853516,-23.171831130981445,-27.047780990600586,-39.0034294128418,47.92576217651367,68.68065643310547,0.0,0.0,0.0 +3911,11.808356,1381864,0,2,1,0,0,[],111.75800323486328,-23.313215255737305,-26.774721145629883,-37.419586181640625,47.763248443603516,66.75296783447266,0.0,0.0,0.0 +3914,11.816359,1381865,0,2,1,0,0,[],111.15766143798828,-23.444150924682617,-26.496429443359375,-35.794090270996094,47.5921630859375,64.77640533447266,0.0,0.0,0.0 +3916,11.824383,1381866,0,2,1,0,0,[],110.53633117675781,-23.56491470336914,-26.213180541992188,-34.128662109375,47.412864685058594,62.7530403137207,0.0,0.0,0.0 +3920,11.832358,1381867,0,2,1,0,0,[],109.89479064941406,-23.6757869720459,-25.925249099731445,-32.425010681152344,47.22572708129883,60.684940338134766,0.0,0.0,0.0 +3922,11.840352,1381868,0,2,1,0,0,[],109.23381805419922,-23.77704620361328,-25.632909774780273,-30.68484115600586,47.03110885620117,58.57417678833008,0.0,0.0,0.0 +3924,11.848361,1381869,0,2,1,0,0,[],108.55419921875,-23.868972778320312,-25.336437225341797,-28.909868240356445,46.829376220703125,56.42281723022461,0.0,0.0,0.0 +3927,11.856345,1381870,0,2,1,0,0,[],107.85669708251953,-23.951845169067383,-25.036104202270508,-27.1018009185791,46.62089157104492,54.23292922973633,0.0,0.0,0.0 +3930,11.864321,1381871,0,2,1,0,0,[],107.14209747314453,-24.025943756103516,-24.732189178466797,-25.262351989746094,46.40602493286133,52.006587982177734,0.0,0.0,0.0 +3932,11.872351,1381872,0,2,1,0,0,[],106.41117858886719,-24.09154510498047,-24.424964904785156,-23.393230438232422,46.18513870239258,49.745853424072266,0.0,0.0,0.0 +3934,11.880357,1381873,0,2,1,0,0,[],105.66471099853516,-24.1489315032959,-24.114704132080078,-21.49614906311035,45.958595275878906,47.45280456542969,0.0,0.0,0.0 +3937,11.888347,1381874,0,2,1,0,0,[],104.90348815917969,-24.198381423950195,-23.80168342590332,-19.57281494140625,45.72676086425781,45.1295051574707,0.0,0.0,0.0 +3940,11.896351,1381875,0,2,1,0,0,[],104.1282730102539,-24.240171432495117,-23.486177444458008,-17.624942779541016,45.49000549316406,42.77802276611328,0.0,0.0,0.0 +3942,11.904361,1381876,0,2,1,0,0,[],103.33984375,-24.27458381652832,-23.1684627532959,-15.654240608215332,45.24868392944336,40.40043258666992,0.0,0.0,0.0 +3946,11.912424,1381877,0,2,1,0,0,[],102.53898620605469,-24.301897048950195,-22.84880828857422,-13.662419319152832,45.003170013427734,37.99879837036133,0.0,0.0,0.0 +3948,11.920352,1381878,0,2,1,0,0,[],101.72647094726562,-24.322389602661133,-22.527494430541992,-11.651190757751465,44.753822326660156,35.575191497802734,0.0,0.0,0.0 +3950,11.928397,1381879,0,2,1,0,0,[],100.90308380126953,-24.336341857910156,-22.20479393005371,-9.622264862060547,44.50101089477539,33.13167953491211,0.0,0.0,0.0 +3954,11.936383,1381880,0,2,1,0,0,[],100.06959533691406,-24.344032287597656,-21.880979537963867,-7.577352046966553,44.24509811401367,30.67033576965332,0.0,0.0,0.0 +3956,11.944352,1381881,0,2,1,0,0,[],99.2267837524414,-24.345739364624023,-21.55632781982422,-5.518163204193115,43.986446380615234,28.193225860595703,0.0,0.0,0.0 +3958,11.952386,1381882,0,2,1,0,0,[],98.37542724609375,-24.34174346923828,-21.23111343383789,-3.4464094638824463,43.725425720214844,25.702421188354492,0.0,0.0,0.0 +3961,11.960354,1381883,0,2,1,0,0,[],97.51630401611328,-24.33232307434082,-20.905609130859375,-1.3638010025024414,43.462398529052734,23.199987411499023,0.0,0.0,0.0 +3964,11.968367,1381884,0,2,1,0,0,[],96.65019226074219,-24.317758560180664,-20.580093383789062,0.7279513478279114,43.19772720336914,20.687997817993164,0.0,0.0,0.0 +3966,11.976373,1381885,0,2,1,0,0,[],95.77787017822266,-24.29832649230957,-20.254837036132812,2.827136754989624,42.93177795410156,18.16851806640625,0.0,0.0,0.0 +3970,11.984393,1381886,0,2,1,0,0,[],94.90011596679688,-24.274311065673828,-19.930116653442383,4.932044982910156,42.6649169921875,15.643620491027832,0.0,0.0,0.0 +3972,11.99235,1381887,0,2,1,0,0,[],94.01770782470703,-24.24598503112793,-19.606204986572266,7.0409650802612305,42.39750671386719,13.115372657775879,0.0,0.0,0.0 +3974,12.000361,1381888,0,2,1,0,0,[],93.13141632080078,-24.213634490966797,-19.28337860107422,9.152186393737793,42.129913330078125,10.585844039916992,0.0,0.0,0.0 +3976,12.008387,1381889,0,2,1,0,0,[],92.24202728271484,-24.177532196044922,-18.961912155151367,11.263998031616211,41.86250305175781,8.057104110717773,0.0,0.0,0.0 +3980,12.016387,1381890,0,2,1,0,0,[],91.3503189086914,-24.137962341308594,-18.642080307006836,13.374689102172852,41.59564208984375,5.531221389770508,0.0,0.0,0.0 +3982,12.024355,1381891,0,2,1,0,0,[],90.45706176757812,-24.09520149230957,-18.324155807495117,15.482549667358398,41.329689025878906,3.010265827178955,0.0,0.0,0.0 +3984,12.03236,1381892,0,2,1,0,0,[],89.56303405761719,-24.049528121948242,-18.00841522216797,17.58586883544922,41.06501388549805,0.49630603194236755,0.0,0.0,0.0 +3987,12.040383,1381893,0,2,1,0,0,[],88.66902160644531,-24.001224517822266,-17.695131301879883,19.68293571472168,40.801979064941406,-2.0085885524749756,0.0,0.0,0.0 +3990,12.048356,1381894,0,2,1,0,0,[],87.77579498291016,-23.9505672454834,-17.38458251953125,21.77203941345215,40.54094696044922,-4.50234842300415,0.0,0.0,0.0 +3992,12.056352,1381895,0,2,1,0,0,[],86.8841323852539,-23.897836685180664,-17.077037811279297,23.851470947265625,40.282291412353516,-6.98290491104126,0.0,0.0,0.0 +3996,12.064386,1381896,0,2,1,0,0,[],85.99481201171875,-23.843313217163086,-16.772777557373047,25.91951560974121,40.0263671875,-9.448188781738281,0.0,0.0,0.0 +3998,12.072358,1381897,0,2,1,0,0,[],85.1086196899414,-23.787273406982422,-16.47207260131836,27.974468231201172,39.77354431152344,-11.896129608154297,0.0,0.0,0.0 +4000,12.080361,1381898,0,2,1,0,0,[],84.226318359375,-23.729997634887695,-16.17519760131836,30.01461410522461,39.52418899536133,-14.324660301208496,0.0,0.0,0.0 +4004,12.08835,1381899,0,2,1,0,0,[],83.34869384765625,-23.67176628112793,-15.882429122924805,32.03824234008789,39.27865982055664,-16.73171043395996,0.0,0.0,0.0 +4006,12.096367,1381900,0,2,1,0,0,[],82.47653198242188,-23.612855911254883,-15.594040870666504,34.043643951416016,39.03732681274414,-19.115209579467773,0.0,0.0,0.0 +4008,12.104379,1381901,0,2,1,0,0,[],81.61058807373047,-23.55354881286621,-15.310307502746582,36.029109954833984,38.80055236816406,-21.47309112548828,0.0,0.0,0.0 +4011,12.112384,1381902,0,2,1,0,0,[],80.75166320800781,-23.494123458862305,-15.03150463104248,37.99292755126953,38.56870651245117,-23.80328369140625,0.0,0.0,0.0 +4014,12.12036,1381903,0,2,1,0,0,[],79.90052032470703,-23.434856414794922,-14.757905006408691,39.93338394165039,38.34214401245117,-26.10371971130371,0.0,0.0,0.0 +4016,12.128374,1381904,0,2,1,0,0,[],79.05794525146484,-23.37602996826172,-14.489784240722656,41.84877014160156,38.121238708496094,-28.372329711914062,0.0,0.0,0.0 +4018,12.136389,1381905,0,2,1,0,0,[],78.2247085571289,-23.317922592163086,-14.2274169921875,43.73738098144531,37.90635299682617,-30.60704231262207,0.0,0.0,0.0 +4021,12.144361,1381906,0,2,1,0,0,[],77.40159606933594,-23.260812759399414,-13.971078872680664,45.597496032714844,37.69784927368164,-32.80579376220703,0.0,0.0,0.0 +4024,12.152357,1381907,0,2,1,0,0,[],76.5893783569336,-23.204978942871094,-13.72104263305664,47.42741012573242,37.49609375,-34.96650695800781,0.0,0.0,0.0 +4026,12.160367,1381908,0,2,1,0,0,[],75.7888412475586,-23.15070343017578,-13.477582931518555,49.22541427612305,37.30145263671875,-37.08711624145508,0.0,0.0,0.0 +4030,12.16837,1381909,0,2,1,0,0,[],75.00074768066406,-23.098262786865234,-13.240976333618164,50.98979187011719,37.114288330078125,-39.165557861328125,0.0,0.0,0.0 +4032,12.176383,1381910,0,2,1,0,0,[],74.22589111328125,-23.047937393188477,-13.011496543884277,52.71883773803711,36.934967041015625,-41.19975280761719,0.0,0.0,0.0 +4034,12.184388,1381911,0,2,1,0,0,[],73.46504211425781,-23.0000057220459,-12.789417266845703,54.41083908081055,36.76385498046875,-43.18764114379883,0.0,0.0,0.0 +4037,12.192362,1381912,0,2,1,0,0,[],72.71897888183594,-22.95474624633789,-12.575014114379883,56.0640869140625,36.601314544677734,-45.12714767456055,0.0,0.0,0.0 +4040,12.200354,1381913,0,2,1,0,0,[],71.98847961425781,-22.91244125366211,-12.368561744689941,57.6768684387207,36.44770812988281,-47.016204833984375,0.0,0.0,0.0 +4042,12.208364,1381914,0,2,1,0,0,[],71.2743148803711,-22.87336540222168,-12.170334815979004,59.24747085571289,36.303409576416016,-48.85274124145508,0.0,0.0,0.0 +4046,12.216361,1381915,0,2,1,0,0,[],70.57727813720703,-22.83780288696289,-11.980607032775879,60.77418899536133,36.16877365112305,-50.63469314575195,0.0,0.0,0.0 +4048,12.224326,1381916,0,2,1,0,0,[],69.89813232421875,-22.8060302734375,-11.799654960632324,62.255306243896484,36.04417037963867,-52.3599853515625,0.0,0.0,0.0 +4050,12.232373,1381917,0,2,1,0,0,[],69.23766326904297,-22.7783260345459,-11.627750396728516,63.689117431640625,35.92996597290039,-54.026554107666016,0.0,0.0,0.0 +4054,12.240361,1381918,0,2,1,0,0,[],68.59664154052734,-22.75497055053711,-11.465170860290527,65.07391357421875,35.82651901245117,-55.63232421875,0.0,0.0,0.0 +4056,12.248376,1381919,0,2,1,0,0,[],67.9758529663086,-22.736244201660156,-11.312188148498535,66.40797424316406,35.73419952392578,-57.17523193359375,0.0,0.0,0.0 +4058,12.256379,1381920,0,2,1,0,0,[],67.37606811523438,-22.722423553466797,-11.169079780578613,67.68959045410156,35.65336990356445,-58.65320587158203,0.0,0.0,0.0 +4060,12.264413,1381921,0,2,1,0,0,[],66.7980728149414,-22.713788986206055,-11.036118507385254,68.91706085205078,35.58440017700195,-60.064178466796875,0.0,0.0,0.0 +4064,12.27236,1381922,0,2,1,0,0,[],66.24263763427734,-22.710620880126953,-10.913579940795898,70.08866882324219,35.52764892578125,-61.40607452392578,0.0,0.0,0.0 +4066,12.280345,1381923,0,2,1,0,0,[],65.71054077148438,-22.71319580078125,-10.801737785339355,71.20270538330078,35.483482360839844,-62.67683410644531,0.0,0.0,0.0 +4068,12.288368,1381924,0,2,1,0,0,[],65.20256805419922,-22.7217960357666,-10.70086669921875,72.25746154785156,35.45226287841797,-63.87438201904297,0.0,0.0,0.0 +4071,12.296355,1381925,0,2,1,0,0,[],64.719482421875,-22.7367000579834,-10.611242294311523,73.251220703125,35.434364318847656,-64.99665069580078,0.0,0.0,0.0 +4074,12.304368,1381926,0,2,1,0,0,[],64.26207733154297,-22.7581844329834,-10.5331392288208,74.1822738647461,35.43014144897461,-66.04156494140625,0.0,0.0,0.0 +4076,12.312395,1381927,0,2,1,0,0,[],63.831119537353516,-22.786531448364258,-10.466830253601074,75.04891204833984,35.439964294433594,-67.00706481933594,0.0,0.0,0.0 +4080,12.320361,1381928,0,2,1,0,0,[],63.427391052246094,-22.822019577026367,-10.412592887878418,75.84942626953125,35.464195251464844,-67.89107513427734,0.0,0.0,0.0 +4082,12.328358,1381929,0,2,1,0,0,[],63.051666259765625,-22.864927291870117,-10.370698928833008,76.58209991455078,35.503204345703125,-68.69153594970703,0.0,0.0,0.0 +4084,12.336387,1381930,0,2,1,0,0,[],62.70472717285156,-22.91553497314453,-10.341424942016602,77.24523162841797,35.557350158691406,-69.40636444091797,0.0,0.0,0.0 +4087,12.344361,1381931,0,2,1,0,0,[],62.38726043701172,-22.973976135253906,-10.324951171875,77.8370132446289,35.626773834228516,-70.03351593017578,0.0,0.0,0.0 +4090,12.3523,1381932,0,2,1,0,0,[],62.098609924316406,-23.038188934326172,-10.320054054260254,78.3543472290039,35.70827865600586,-70.57115173339844,0.0,0.0,0.0 +4092,12.360392,1381933,0,2,1,0,0,[],61.83707809448242,-23.104421615600586,-10.324423789978027,78.7931137084961,35.79606628417969,-71.01762390136719,0.0,0.0,0.0 +4095,12.368366,1381934,0,2,1,0,0,[],61.6009521484375,-23.16887855529785,-10.335723876953125,79.149169921875,35.88428497314453,-71.37127685546875,0.0,0.0,0.0 +4098,12.376369,1381935,0,2,1,0,0,[],61.38850402832031,-23.22776222229004,-10.351614952087402,79.41836547851562,35.96707534790039,-71.63046264648438,0.0,0.0,0.0 +4100,12.384376,1381936,0,2,1,0,0,[],61.19801712036133,-23.27727508544922,-10.36976146697998,79.59657287597656,36.03858184814453,-71.79351806640625,0.0,0.0,0.0 +4102,12.392389,1381937,0,2,1,0,0,[],61.02777099609375,-23.313621520996094,-10.387825012207031,79.67963409423828,36.09294509887695,-71.85879516601562,0.0,0.0,0.0 +4106,12.400393,1381938,0,2,1,0,0,[],60.876041412353516,-23.333005905151367,-10.40346908569336,79.66340637207031,36.124305725097656,-71.82464599609375,0.0,0.0,0.0 +4108,12.408361,1381939,0,2,1,0,0,[],60.74110794067383,-23.331626892089844,-10.41435432434082,79.54376220703125,36.12681579589844,-71.68941497802734,0.0,0.0,0.0 +4110,12.416369,1381940,0,2,1,0,0,[],60.62125015258789,-23.305692672729492,-10.418145179748535,79.31654357910156,36.094608306884766,-71.45144653320312,0.0,0.0,0.0 +4114,12.424384,1381941,0,2,1,0,0,[],60.514747619628906,-23.251401901245117,-10.41250228881836,78.97760772705078,36.02183151245117,-71.10909271240234,0.0,0.0,0.0 +4116,12.43238,1381942,0,2,1,0,0,[],60.419883728027344,-23.164962768554688,-10.395090103149414,78.5228271484375,35.90262985229492,-70.66069793701172,0.0,0.0,0.0 +4118,12.440395,1381943,0,2,1,0,0,[],60.334930419921875,-23.042573928833008,-10.363570213317871,77.94803619384766,35.73114013671875,-70.1046142578125,0.0,0.0,0.0 +4121,12.448375,1381944,0,2,1,0,0,[],60.2581787109375,-22.880474090576172,-10.315624237060547,77.2491683959961,35.50156021118164,-69.43922424316406,0.0,0.0,0.0 +4124,12.456381,1381945,0,2,1,0,0,[],60.18846130371094,-22.6769962310791,-10.250082015991211,76.42583465576172,35.21126174926758,-68.66544342041016,0.0,0.0,0.0 +4126,12.464403,1381946,0,2,1,0,0,[],60.12546157836914,-22.43378257751465,-10.167591094970703,75.48347473144531,34.862632751464844,-67.78814697265625,0.0,0.0,0.0 +4130,12.472433,1381947,0,2,1,0,0,[],60.06894302368164,-22.15275764465332,-10.06895637512207,74.42807006835938,34.45849609375,-66.81258392333984,0.0,0.0,0.0 +4132,12.480363,1381948,0,2,1,0,0,[],60.01866912841797,-21.835857391357422,-9.95498275756836,73.26556396484375,34.001678466796875,-65.74397277832031,0.0,0.0,0.0 +4134,12.488383,1381949,0,2,1,0,0,[],59.974395751953125,-21.485008239746094,-9.826475143432617,72.0019302368164,33.4950065612793,-64.5875473022461,0.0,0.0,0.0 +4138,12.496365,1381950,0,2,1,12,12,"[3, 4]",59.93589401245117,-21.102140426635742,-9.684239387512207,70.64311981201172,32.941307067871094,-63.34855651855469,0.0,0.0,0.0 +4140,12.504369,1381951,0,2,1,0,0,[],59.902915954589844,-20.689186096191406,-9.52907943725586,69.19510650634766,32.343406677246094,-62.032222747802734,0.0,0.0,0.0 +4142,12.512395,1381952,0,2,1,12,0,[],59.8752326965332,-20.24807357788086,-9.361802101135254,67.66384887695312,31.704130172729492,-60.64377975463867,0.0,0.0,0.0 +4144,12.520386,1381953,0,2,1,0,0,[],59.852596282958984,-19.780733108520508,-9.183212280273438,66.05530548095703,31.026304244995117,-59.18846893310547,0.0,0.0,0.0 +4147,12.528369,1381954,0,2,1,0,0,[],59.83477783203125,-19.28909683227539,-8.994114875793457,64.37543487548828,30.312753677368164,-57.6715202331543,0.0,0.0,0.0 +4150,12.536422,1381955,0,2,1,0,0,[],59.821537017822266,-18.77509117126465,-8.795315742492676,62.63020706176758,29.56630516052246,-56.098167419433594,0.0,0.0,0.0 +4152,12.544347,1381956,0,2,1,0,0,[],59.81262969970703,-18.24064826965332,-8.587618827819824,60.82558059692383,28.78978729248047,-54.4736442565918,0.0,0.0,0.0 +4156,12.552366,1381957,0,2,1,0,0,[],59.807823181152344,-17.687700271606445,-8.371829986572266,58.96752166748047,27.98602294921875,-52.803184509277344,0.0,0.0,0.0 +4158,12.560357,1381958,0,2,1,0,0,[],59.806880950927734,-17.118173599243164,-8.148755073547363,57.06198501586914,27.157838821411133,-51.09202575683594,0.0,0.0,0.0 +4160,12.568382,1381959,0,2,1,0,0,[],59.8095588684082,-16.534000396728516,-7.919198513031006,55.114933013916016,26.308061599731445,-49.345394134521484,0.0,0.0,0.0 +4164,12.576388,1381960,0,2,1,0,0,[],59.81562423706055,-15.937108993530273,-7.683966159820557,53.1323356628418,25.439517974853516,-47.56853485107422,0.0,0.0,0.0 +4166,12.584379,1381961,0,2,1,0,0,[],59.82483673095703,-15.329431533813477,-7.443862438201904,51.120147705078125,24.55503273010254,-45.76667022705078,0.0,0.0,0.0 +4168,12.592378,1381962,0,2,1,0,0,[],59.83695602416992,-14.712897300720215,-7.199693202972412,49.08433151245117,23.657432556152344,-43.945045471191406,0.0,0.0,0.0 +4171,12.600393,1381963,0,2,1,0,0,[],59.851749420166016,-14.089436531066895,-6.952263832092285,47.030853271484375,22.74954605102539,-42.10888671875,0.0,0.0,0.0 +4174,12.608363,1381964,0,2,1,0,0,[],59.86897277832031,-13.460979461669922,-6.702378749847412,44.96567153930664,21.83419418334961,-40.263431549072266,0.0,0.0,0.0 +4176,12.616372,1381965,0,2,1,0,0,[],59.88839340209961,-12.829455375671387,-6.450843811035156,42.89474868774414,20.914207458496094,-38.413909912109375,0.0,0.0,0.0 +4180,12.624393,1381966,0,2,1,0,0,[],59.909767150878906,-12.196795463562012,-6.198464393615723,40.82404708862305,19.992408752441406,-36.5655632019043,0.0,0.0,0.0 +4182,12.632386,1381967,0,2,1,0,0,[],59.932861328125,-11.564929008483887,-5.946045398712158,38.75952911376953,19.071626663208008,-34.72361755371094,0.0,0.0,0.0 +4184,12.640377,1381968,0,2,1,0,0,[],59.957435607910156,-10.935785293579102,-5.69439172744751,36.7071533203125,18.154687881469727,-32.893314361572266,0.0,0.0,0.0 +4186,12.648397,1381969,0,2,1,0,0,[],59.983253479003906,-10.311296463012695,-5.444309234619141,34.672889709472656,17.244415283203125,-31.079879760742188,0.0,0.0,0.0 +4190,12.656414,1381970,0,2,1,0,0,[],60.010074615478516,-9.693391799926758,-5.196603298187256,32.66269302368164,16.343637466430664,-29.288555145263672,0.0,0.0,0.0 +4192,12.664395,1381971,0,2,1,0,0,[],60.037662506103516,-9.084000587463379,-4.952078342437744,30.682525634765625,15.455179214477539,-27.524568557739258,0.0,0.0,0.0 +4194,12.672357,1381972,0,2,1,0,0,[],60.06577682495117,-8.485053062438965,-4.711540222167969,28.738353729248047,14.581868171691895,-25.79315948486328,0.0,0.0,0.0 +4198,12.680331,1381973,0,2,1,0,0,[],60.094181060791016,-7.898480415344238,-4.475793361663818,26.836135864257812,13.726529121398926,-24.099557876586914,0.0,0.0,0.0 +4200,12.688366,1381974,0,2,1,0,0,[],60.12263870239258,-7.326211929321289,-4.2456440925598145,24.981834411621094,12.891987800598145,-22.448999404907227,0.0,0.0,0.0 +4202,12.696412,1381975,0,2,1,0,0,[],60.150909423828125,-6.770177841186523,-4.021897315979004,23.181413650512695,12.081071853637695,-20.846717834472656,0.0,0.0,0.0 +4205,12.704367,1381976,0,2,1,0,0,[],60.17875289916992,-6.232308387756348,-3.8053576946258545,21.440832138061523,11.296606063842773,-19.297948837280273,0.0,0.0,0.0 +4208,12.712372,1381977,0,2,1,0,0,[],60.205936431884766,-5.714532852172852,-3.5968310832977295,19.766054153442383,10.541418075561523,-17.80792236328125,0.0,0.0,0.0 +4210,12.720398,1381978,0,2,1,0,0,[],60.232215881347656,-5.218782424926758,-3.397122383117676,18.163040161132812,9.81833267211914,-16.38187599182129,0.0,0.0,0.0 +4214,12.728377,1381979,0,2,1,0,0,[],60.25735855102539,-4.7469868659973145,-3.2070369720458984,16.637752532958984,9.130175590515137,-15.025043487548828,0.0,0.0,0.0 +4216,12.736391,1381980,0,2,1,0,0,[],60.281166076660156,-4.300666332244873,-3.0272107124328613,15.19487476348877,8.479175567626953,-13.741530418395996,0.0,0.0,0.0 +4218,12.744424,1381981,0,2,1,0,0,[],60.30363082885742,-3.8795688152313232,-2.8575470447540283,13.833537101745605,7.864964962005615,-12.530550956726074,0.0,0.0,0.0 +4221,12.75239,1381982,0,2,1,0,0,[],60.32478332519531,-3.4829604625701904,-2.6977503299713135,12.55136775970459,7.286474227905273,-11.389996528625488,0.0,0.0,0.0 +4224,12.760366,1381983,0,2,1,0,0,[],60.34467315673828,-3.110107183456421,-2.5475246906280518,11.345995903015137,6.742632865905762,-10.317756652832031,0.0,0.0,0.0 +4226,12.768401,1381984,0,2,1,0,0,[],60.36333465576172,-2.7602758407592773,-2.4065747261047363,10.215049743652344,6.232370853424072,-9.311722755432129,0.0,0.0,0.0 +4228,12.776463,1381985,0,2,1,0,0,[],60.38080596923828,-2.432732582092285,-2.2746047973632812,9.156157493591309,5.754618167877197,-8.369783401489258,0.0,0.0,0.0 +4231,12.784388,1381986,0,2,1,0,0,[],60.39712905883789,-2.126743793487549,-2.1513192653656006,8.166946411132812,5.308304786682129,-7.489830493927002,0.0,0.0,0.0 +4234,12.792368,1381987,0,2,1,0,0,[],60.4123420715332,-1.8415758609771729,-2.0364227294921875,7.245046615600586,4.892360687255859,-6.669753551483154,0.0,0.0,0.0 +4236,12.800387,1381988,0,2,1,0,0,[],60.42647933959961,-1.5764952898025513,-1.9296196699142456,6.38808536529541,4.505715370178223,-5.907443046569824,0.0,0.0,0.0 +4240,12.808402,1381989,0,2,1,0,0,[],60.43959045410156,-1.3307682275772095,-1.8306142091751099,5.593691349029541,4.147299289703369,-5.200788974761963,0.0,0.0,0.0 +4242,12.816422,1381990,0,2,1,0,0,[],60.45170211791992,-1.1036611795425415,-1.7391109466552734,4.859492301940918,3.816042423248291,-4.54768180847168,0.0,0.0,0.0 +4244,12.824369,1381991,0,2,1,0,0,[],60.46286392211914,-0.8944405317306519,-1.6548141241073608,4.183116912841797,3.5108745098114014,-3.9460113048553467,0.0,0.0,0.0 +4248,12.832365,1381992,0,2,1,0,0,[],60.47311019897461,-0.7023726105690002,-1.5774284601211548,3.5621936321258545,3.2307252883911133,-3.3936684131622314,0.0,0.0,0.0 +4250,12.840377,1381993,0,2,1,0,0,[],60.482479095458984,-0.5267238616943359,-1.5066580772399902,2.9943506717681885,2.974524974822998,-2.888543128967285,0.0,0.0,0.0 +4252,12.848389,1381994,0,2,1,0,0,[],60.49101257324219,-0.3667605519294739,-1.4422075748443604,2.4772160053253174,2.741203546524048,-2.428525686264038,0.0,0.0,0.0 +4255,12.8564,1381995,0,2,1,0,0,[],60.49874496459961,-0.22174911201000214,-1.3837811946868896,2.008418560028076,2.529690742492676,-2.0115063190460205,0.0,0.0,0.0 +4258,12.864396,1381996,0,2,1,0,0,[],60.50572204589844,-0.09095591306686401,-1.3310835361480713,1.5855859518051147,2.338916540145874,-1.6353751420974731,0.0,0.0,0.0 +4260,12.872386,1381997,0,2,1,0,0,[],60.51198196411133,0.02635267563164234,-1.2838189601898193,1.2063469886779785,2.1678109169006348,-1.298022747039795,0.0,0.0,0.0 +4264,12.880421,1381998,0,2,1,0,0,[],60.51755905151367,0.13091027736663818,-1.2416918277740479,0.8683297634124756,2.015303611755371,-0.9973392486572266,0.0,0.0,0.0 +4266,12.88835,1381999,0,2,1,0,0,[],60.52249526977539,0.22345052659511566,-1.2044066190719604,0.5691626667976379,1.8803250789642334,-0.7312148809432983,0.0,0.0,0.0 +4268,12.896384,1382000,0,2,1,0,0,[],60.52682876586914,0.30470705032348633,-1.1716676950454712,0.3064739406108856,1.7618048191070557,-0.4975399076938629,0.0,0.0,0.0 +4270,12.904335,1382001,0,2,1,0,0,[],60.530601501464844,0.3754134774208069,-1.1431795358657837,0.07789193838834763,1.65867280960083,-0.29420459270477295,0.0,0.0,0.0 +4274,12.912383,1382002,0,2,1,0,0,[],60.53384780883789,0.43630340695381165,-1.1186463832855225,-0.11895503103733063,1.5698591470718384,-0.1190991923213005,0.0,0.0,0.0 +4276,12.920372,1382003,0,2,1,0,0,[],60.53661346435547,0.4881105124950409,-1.0977729558944702,-0.2864386737346649,1.4942935705184937,0.02988602966070175,0.0,0.0,0.0 +4278,12.928386,1382004,0,2,1,0,0,[],60.5389289855957,0.5315683484077454,-1.080263376235962,-0.42693066596984863,1.4309061765670776,0.15486082434654236,0.0,0.0,0.0 +4281,12.936408,1382005,0,2,1,0,0,[],60.54084396362305,0.5674106478691101,-1.0658222436904907,-0.5428027510643005,1.378626823425293,0.25793492794036865,0.0,0.0,0.0 +4284,12.944343,1382006,0,2,1,0,0,[],60.542388916015625,0.5963709354400635,-1.0541539192199707,-0.636426568031311,1.3363854885101318,0.3412180542945862,0.0,0.0,0.0 +4286,12.952404,1382007,0,2,1,0,0,[],60.54360580444336,0.6191828846931458,-1.044962763786316,-0.7101737856864929,1.3031121492385864,0.4068199694156647,0.0,0.0,0.0 +4290,12.960413,1382008,0,2,1,0,0,[],60.544532775878906,0.6365801692008972,-1.03795325756073,-0.7664161920547485,1.2777366638183594,0.45685043931007385,0.0,0.0,0.0 +4292,12.968382,1382009,0,2,1,0,0,[],60.54521179199219,0.6492962837219238,-1.032829761505127,-0.8075253367424011,1.2591888904571533,0.49341917037963867,0.0,0.0,0.0 +4294,12.976398,1382010,0,2,1,0,0,[],60.545677185058594,0.6580650210380554,-1.029296875,-0.8358730673789978,1.24639892578125,0.5186359286308289,0.0,0.0,0.0 +4298,12.984369,1382011,0,2,1,0,0,[],60.54597473144531,0.663619875907898,-1.027058720588684,-0.8538309931755066,1.238296627998352,0.534610390663147,0.0,0.0,0.0 +4300,12.992399,1382012,0,2,1,0,0,[],60.546138763427734,0.6666945219039917,-1.0258198976516724,-0.8637708425521851,1.2338119745254517,0.5434523820877075,0.0,0.0,0.0 +4302,13.000361,1382013,0,2,1,0,0,[],60.546207427978516,0.6680225729942322,-1.0252848863601685,-0.868064284324646,1.231874942779541,0.5472716093063354,0.0,0.0,0.0 +4305,13.008395,1382014,0,2,1,0,0,[],60.546226501464844,0.6683377027511597,-1.0251579284667969,-0.8690829873085022,1.2314152717590332,0.5481777787208557,0.0,0.0,0.0 +4308,13.016379,1382015,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4310,13.024402,1382016,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4312,13.032385,1382017,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4315,13.040377,1382018,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4318,13.048391,1382019,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4320,13.05635,1382020,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4324,13.064415,1382021,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4326,13.072385,1382022,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4328,13.080368,1382023,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4331,13.088369,1382024,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4334,13.096383,1382025,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4336,13.104423,1382026,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4340,13.112411,1382027,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4342,13.120385,1382028,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4344,13.128368,1382029,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4348,13.136403,1382030,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4350,13.144374,1382031,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4352,13.152405,1382032,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4354,13.160391,1382033,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4357,13.168359,1382034,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4360,13.176395,1382035,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4362,13.184373,1382036,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4365,13.192385,1382037,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4368,13.200398,1382038,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4370,13.208413,1382039,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4374,13.216416,1382040,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4376,13.224358,1382041,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4378,13.232426,1382042,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4382,13.240358,1382043,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4384,13.248377,1382044,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4386,13.256395,1382045,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4389,13.264388,1382046,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4392,13.272349,1382047,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4394,13.280399,1382048,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4396,13.288404,1382049,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4400,13.296399,1382050,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4402,13.304384,1382051,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4404,13.312388,1382052,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4408,13.320357,1382053,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4410,13.328383,1382054,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4412,13.336368,1382055,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4415,13.344381,1382056,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4418,13.352395,1382057,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4420,13.360412,1382058,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4424,13.368423,1382059,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4426,13.376391,1382060,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4428,13.384399,1382061,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4432,13.392364,1382062,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4434,13.400395,1382063,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4436,13.408367,1382064,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4438,13.416382,1382065,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4441,13.424419,1382066,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4444,13.432395,1382067,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4446,13.440382,1382068,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4450,13.448402,1382069,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4452,13.456363,1382070,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4454,13.464379,1382071,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4458,13.472376,1382072,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4460,13.480373,1382073,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4462,13.488369,1382074,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4465,13.496385,1382075,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4468,13.504385,1382076,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4470,13.512418,1382077,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4474,13.520414,1382078,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4476,13.528387,1382079,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4478,13.536394,1382080,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4480,13.544406,1382081,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4484,13.552403,1382082,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4486,13.560414,1382083,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4488,13.568422,1382084,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4492,13.576406,1382085,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4495,13.584417,1382086,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4497,13.592358,1382087,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4501,13.600396,1382088,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4503,13.608381,1382089,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4505,13.61641,1382090,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4509,13.624423,1382091,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4511,13.632403,1382092,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4513,13.640406,1382093,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4516,13.648388,1382094,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4519,13.656382,1382095,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4521,13.664383,1382096,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4523,13.672412,1382097,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4526,13.680386,1382098,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4529,13.68839,1382099,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4531,13.696424,1382100,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4535,13.704415,1382101,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4537,13.712389,1382102,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4539,13.720413,1382103,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4543,13.728407,1382104,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4545,13.736373,1382105,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4547,13.74439,1382106,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4550,13.752384,1382107,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4553,13.760395,1382108,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4555,13.768389,1382109,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4559,13.776405,1382110,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4561,13.784376,1382111,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4563,13.792384,1382112,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4565,13.800406,1382113,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4569,13.808409,1382114,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4571,13.816375,1382115,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4573,13.824386,1382116,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4576,13.83242,1382117,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4579,13.840389,1382118,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4581,13.848391,1382119,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4585,13.856418,1382120,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4587,13.86442,1382121,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4589,13.872418,1382122,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4593,13.880402,1382123,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4595,13.888376,1382124,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4597,13.896425,1382125,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4600,13.904421,1382126,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4603,13.912373,1382127,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4605,13.920376,1382128,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4607,13.928417,1382129,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4611,13.936437,1382130,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4613,13.944383,1382131,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4615,13.95244,1382132,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4619,13.960374,1382133,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4621,13.968391,1382134,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4623,13.976419,1382135,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4626,13.984392,1382136,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4629,13.992386,1382137,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4631,14.000384,1382138,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4635,14.008398,1382139,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4637,14.016391,1382140,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4639,14.024392,1382141,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4643,14.032399,1382142,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4645,14.040381,1382143,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4647,14.048364,1382144,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4649,14.056391,1382145,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4652,14.064389,1382146,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4655,14.072392,1382147,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4657,14.080419,1382148,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4660,14.08836,1382149,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4663,14.096393,1382150,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4665,14.104427,1382151,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4669,14.112379,1382152,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4671,14.120426,1382153,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4673,14.128419,1382154,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4676,14.136421,1382155,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4679,14.14442,1382156,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4681,14.15238,1382157,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4685,14.160428,1382158,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4687,14.168394,1382159,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4689,14.176396,1382160,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4691,14.184373,1382161,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4695,14.192387,1382162,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4697,14.200401,1382163,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4699,14.208389,1382164,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4703,14.216395,1382165,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4705,14.22441,1382166,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4707,14.23239,1382167,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4710,14.240405,1382168,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4713,14.248416,1382169,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4715,14.256421,1382170,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4719,14.264394,1382171,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4721,14.272383,1382172,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4723,14.280432,1382173,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4726,14.288389,1382174,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 +4729,14.296418,1382175,0,2,1,0,0,[],60.546226501464844,0.6683444380760193,-1.0251551866531372,-0.86910480260849,1.2314053773880005,0.5481972098350525,0.0,0.0,0.0 diff --git a/analysis/2026042802-1/2026042802-1_j519_status_feedback_all.csv b/analysis/2026042802-1/2026042802-1_j519_status_feedback_all.csv new file mode 100644 index 0000000..059e6f6 --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_j519_status_feedback_all.csv @@ -0,0 +1,1790 @@ +frame_number,time_relative_s,sequence,status,accepts_command,received_command,system_ready,robot_in_motion,read_io_value,timestamp,pose_x_mm,pose_y_mm,pose_z_mm,pose_w_deg,pose_p_deg,pose_r_deg,j1_deg,j2_deg,j3_deg,j4_deg,j5_deg,j6_deg +1,0.0,1380380,7,True,True,True,False,0,624362492,273.267333984375,483.85614013671875,467.73809814453125,-57.317848205566406,-89.61824798583984,-62.15401077270508,60.546241760253906,0.6682876944541931,-1.0251282453536987,-0.8690953850746155,1.2314053773880005,0.5476495623588562 +5,0.007993,1380381,7,True,True,True,False,0,624362500,273.2668151855469,483.8565673828125,467.7380065917969,-57.32086181640625,-89.61824035644531,-62.15093231201172,60.54630661010742,0.6682990789413452,-1.025141716003418,-0.8690953850746155,1.2314053773880005,0.5476329922676086 +7,0.01605,1380382,7,True,True,True,False,0,624362508,273.2672424316406,483.85638427734375,467.73809814453125,-57.31855392456055,-89.61822509765625,-62.15328598022461,60.546260833740234,0.6683047413825989,-1.0251350402832031,-0.869085967540741,1.2314218282699585,0.5476164221763611 +9,0.024039,1380383,7,True,True,True,False,0,624362516,273.2672424316406,483.8564147949219,467.7380065917969,-57.31906509399414,-89.61824798583984,-62.152774810791016,60.546260833740234,0.6683104038238525,-1.025141716003418,-0.869085967540741,1.2314136028289795,0.5476329922676086 +13,0.032026,1380384,7,True,True,True,False,0,624362524,273.26708984375,483.8558654785156,467.73858642578125,-57.30956268310547,-89.61820983886719,-62.162288665771484,60.54624557495117,0.6682422757148743,-1.0250744819641113,-0.869085967540741,1.2314218282699585,0.5476329922676086 +15,0.040024,1380385,7,True,True,True,False,0,624362532,273.267333984375,483.85601806640625,467.7383117675781,-57.313812255859375,-89.61822509765625,-62.15805435180664,60.546234130859375,0.668276309967041,-1.0251080989837646,-0.869085967540741,1.2314218282699585,0.5476329922676086 +17,0.04803,1380386,7,True,True,True,False,0,624362540,273.2672424316406,483.85638427734375,467.7379150390625,-57.32376480102539,-89.61820983886719,-62.148075103759766,60.546260833740234,0.6683047413825989,-1.0251551866531372,-0.869085967540741,1.2314218282699585,0.5475832223892212 +20,0.055984,1380387,7,True,True,True,False,0,624362548,273.267333984375,483.8564147949219,467.7379150390625,-57.32079315185547,-89.61822509765625,-62.15105438232422,60.5462532043457,0.6683160662651062,-1.0251551866531372,-0.8690953850746155,1.2314300537109375,0.5476164221763611 +23,0.064005,1380388,7,True,True,True,False,0,624362556,273.2668151855469,483.8565673828125,467.7380676269531,-57.31824493408203,-89.61824035644531,-62.15354919433594,60.54630661010742,0.6682990789413452,-1.0251350402832031,-0.869085967540741,1.2314136028289795,0.5476329922676086 +25,0.072021,1380389,7,True,True,True,False,0,624362564,273.2674560546875,483.8562316894531,467.7377014160156,-57.32377624511719,-89.61827850341797,-62.14809799194336,60.546234130859375,0.6682990789413452,-1.0251753330230713,-0.8690953850746155,1.2314053773880005,0.5476495623588562 +29,0.080024,1380390,7,True,True,True,False,0,624362572,273.2673645019531,483.8563537597656,467.7375183105469,-57.32568359375,-89.61827850341797,-62.14617919921875,60.54624557495117,0.6683047413825989,-1.0252022743225098,-0.869085967540741,1.2314218282699585,0.5476329922676086 +31,0.087958,1380391,7,True,True,True,False,0,624362580,273.26727294921875,483.8564453125,467.7376708984375,-57.3244743347168,-89.61824798583984,-62.14736557006836,60.546260833740234,0.6683104038238525,-1.0251820087432861,-0.869085967540741,1.2314218282699585,0.5476164221763611 +33,0.095956,1380392,7,True,True,True,False,0,624362588,273.2672119140625,483.8563232421875,467.7381896972656,-57.31550598144531,-89.61823272705078,-62.156333923339844,60.546260833740234,0.6682990789413452,-1.0251215696334839,-0.869085967540741,1.2314218282699585,0.5476329922676086 +35,0.104022,1380393,7,True,True,True,False,0,624362596,273.2668762207031,483.8566589355469,467.73797607421875,-57.31814193725586,-89.61822509765625,-62.153648376464844,60.54630661010742,0.6683160662651062,-1.0251483917236328,-0.869085967540741,1.2314382791519165,0.5476164221763611 +39,0.112004,1380394,7,True,True,True,False,0,624362604,273.267333984375,483.85614013671875,467.7381896972656,-57.31550598144531,-89.61823272705078,-62.15635299682617,60.546241760253906,0.6682876944541931,-1.0251215696334839,-0.869085967540741,1.2314218282699585,0.5476329922676086 +41,0.11997,1380395,7,True,True,True,False,0,624362612,273.267333984375,483.8559875488281,467.73834228515625,-57.31712341308594,-89.61824035644531,-62.15474319458008,60.546234130859375,0.6682707071304321,-1.0251014232635498,-0.869085967540741,1.2313889265060425,0.5476329922676086 +43,0.128017,1380396,7,True,True,True,False,0,624362620,273.2668151855469,483.8565673828125,467.7381286621094,-57.31636047363281,-89.61824035644531,-62.155433654785156,60.54630661010742,0.6682990789413452,-1.0251282453536987,-0.869085967540741,1.2314218282699585,0.5476329922676086 +47,0.136012,1380397,7,True,True,True,False,0,624362628,273.26727294921875,483.8564453125,467.73779296875,-57.32218551635742,-89.61824798583984,-62.149654388427734,60.546260833740234,0.6683104038238525,-1.0251686573028564,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +49,0.143996,1380398,7,True,True,True,False,0,624362636,273.266845703125,483.85662841796875,467.73773193359375,-57.32151794433594,-89.61827087402344,-62.15027618408203,60.54630661010742,0.6683047413825989,-1.0251753330230713,-0.8690766096115112,1.2314218282699585,0.5476329922676086 +51,0.15203,1380399,7,True,True,True,False,0,624362644,273.26727294921875,483.8564453125,467.7378234863281,-57.31782913208008,-89.6182632446289,-62.15401077270508,60.546260833740234,0.6683104038238525,-1.0251686573028564,-0.8690671920776367,1.2314382791519165,0.5476329922676086 +54,0.160007,1380400,7,True,True,True,False,0,624362652,273.2672119140625,483.8560791015625,467.73828125,-57.311824798583984,-89.61823272705078,-62.160030364990234,60.54624557495117,0.668276309967041,-1.0251147747039795,-0.8690766096115112,1.2314382791519165,0.5476329922676086 +57,0.167992,1380401,7,True,True,True,False,0,624362660,273.26715087890625,483.8560791015625,467.73828125,-57.315452575683594,-89.6181869506836,-62.156394958496094,60.5462532043457,0.6682707071304321,-1.0251147747039795,-0.86910480260849,1.2314382791519165,0.5476164221763611 +59,0.17604,1380402,7,True,True,True,False,0,624362668,273.2672119140625,483.8563232421875,467.73809814453125,-57.315860748291016,-89.61824798583984,-62.15597915649414,60.546260833740234,0.6682990789413452,-1.0251350402832031,-0.869085967540741,1.2314218282699585,0.5476495623588562 +63,0.184,1380403,7,True,True,True,False,0,624362676,273.2668762207031,483.8566589355469,467.7379455566406,-57.317649841308594,-89.61824035644531,-62.15414047241211,60.54630661010742,0.6683160662651062,-1.0251551866531372,-0.869085967540741,1.2314382791519165,0.5476329922676086 +65,0.191967,1380404,7,True,True,True,False,0,624362684,273.26727294921875,483.8564453125,467.7378234863281,-57.32372283935547,-89.61824035644531,-62.14811706542969,60.546260833740234,0.6683160662651062,-1.025161862373352,-0.8690953850746155,1.2314136028289795,0.5476164221763611 +67,0.200025,1380405,7,True,True,True,False,0,624362692,273.2673645019531,483.8560485839844,467.7381896972656,-57.31760787963867,-89.61820983886719,-62.154258728027344,60.546234130859375,0.668276309967041,-1.0251215696334839,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +70,0.208012,1380406,7,True,True,True,False,0,624362700,273.2667236328125,483.85638427734375,467.7380065917969,-57.321449279785156,-89.61824035644531,-62.15034484863281,60.54630661010742,0.6682707071304321,-1.025141716003418,-0.869085967540741,1.2314053773880005,0.5476164221763611 +73,0.216,1380407,7,True,True,True,False,0,624362708,273.26666259765625,483.8562927246094,467.7378845214844,-57.32273864746094,-89.61824798583984,-62.149051666259766,60.54630661010742,0.66825932264328,-1.0251551866531372,-0.8691142201423645,1.2314053773880005,0.5476495623588562 +75,0.224003,1380408,7,True,True,True,False,0,624362716,273.26708984375,483.8561096191406,467.7378234863281,-57.32102966308594,-89.6182632446289,-62.15081024169922,60.546260833740234,0.6682650446891785,-1.025161862373352,-0.8690953850746155,1.2314136028289795,0.5476495623588562 +77,0.232024,1380409,7,True,True,True,False,0,624362724,273.2673034667969,483.8560791015625,467.7379150390625,-57.32048416137695,-89.61824035644531,-62.15137481689453,60.546241760253906,0.668276309967041,-1.0251551866531372,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +80,0.239995,1380410,7,True,True,True,False,0,624362732,273.2674255371094,483.8562927246094,467.73773193359375,-57.32438659667969,-89.61824035644531,-62.14747619628906,60.546241760253906,0.6683047413825989,-1.0251753330230713,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +83,0.247995,1380411,7,True,True,True,False,0,624362740,273.2671203613281,483.8561706542969,467.7378234863281,-57.32145309448242,-89.61823272705078,-62.150386810302734,60.546260833740234,0.6682707071304321,-1.0251686573028564,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +85,0.255983,1380412,7,True,True,True,False,0,624362748,273.2671203613281,483.85614013671875,467.7381591796875,-57.31369400024414,-89.61823272705078,-62.158145904541016,60.546260833740234,0.6682707071304321,-1.0251282453536987,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +89,0.264045,1380413,7,True,True,True,False,0,624362756,273.2671203613281,483.85614013671875,467.73834228515625,-57.311737060546875,-89.61821746826172,-62.16010284423828,60.546260833740234,0.668276309967041,-1.0251080989837646,-0.869085967540741,1.2314382791519165,0.5476329922676086 +91,0.272017,1380414,7,True,True,True,False,0,624362764,273.26715087890625,483.856201171875,467.73828125,-57.3146858215332,-89.61819458007812,-62.15715408325195,60.546260833740234,0.6682820320129395,-1.0251147747039795,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +93,0.280043,1380415,7,True,True,True,False,0,624362772,273.2674255371094,483.8562927246094,467.73809814453125,-57.31454086303711,-89.61824035644531,-62.157318115234375,60.546241760253906,0.6683047413825989,-1.0251350402832031,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +97,0.288003,1380416,7,True,True,True,False,0,624362780,273.2672119140625,483.8563232421875,467.7380065917969,-57.318782806396484,-89.61824035644531,-62.15305709838867,60.546260833740234,0.6682990789413452,-1.025141716003418,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +99,0.296,1380417,7,True,True,True,False,0,624362788,273.26678466796875,483.85650634765625,467.7377624511719,-57.32350540161133,-89.6182632446289,-62.14828872680664,60.54630661010742,0.6682876944541931,-1.0251686573028564,-0.869085967540741,1.2314053773880005,0.5476329922676086 +101,0.304032,1380418,7,True,True,True,False,0,624362796,273.2671203613281,483.85614013671875,467.7379150390625,-57.32498550415039,-89.61821746826172,-62.146854400634766,60.546260833740234,0.6682707071304321,-1.0251483917236328,-0.869085967540741,1.2314053773880005,0.5475832223892212 +104,0.312,1380419,7,True,True,True,False,0,624362804,273.2674560546875,483.8562316894531,467.7377014160156,-57.32511520385742,-89.6182632446289,-62.146751403808594,60.546234130859375,0.6682990789413452,-1.0251753330230713,-0.8690953850746155,1.2314053773880005,0.5476329922676086 +107,0.320019,1380420,7,True,True,True,False,0,624362812,273.2674255371094,483.8562927246094,467.7375183105469,-57.3243293762207,-89.61829376220703,-62.14752960205078,60.546241760253906,0.6683047413825989,-1.0252022743225098,-0.869085967540741,1.2314218282699585,0.5476495623588562 +109,0.328011,1380421,7,True,True,True,False,0,624362820,273.26715087890625,483.8562316894531,467.7376708984375,-57.32312774658203,-89.6182632446289,-62.148712158203125,60.546260833740234,0.6682820320129395,-1.0251820087432861,-0.869085967540741,1.2314218282699585,0.5476329922676086 +113,0.336002,1380422,7,True,True,True,False,0,624362828,273.2667541503906,483.8564453125,467.7378234863281,-57.31922912597656,-89.61827087402344,-62.152565002441406,60.54630661010742,0.6682820320129395,-1.025161862373352,-0.869085967540741,1.2314218282699585,0.5476495623588562 +115,0.344007,1380423,7,True,True,True,False,0,624362836,273.2672119140625,483.8563232421875,467.7378234863281,-57.32069778442383,-89.61824035644531,-62.15114974975586,60.546260833740234,0.6682933568954468,-1.0251686573028564,-0.869085967540741,1.2314382791519165,0.5476164221763611 +117,0.35201,1380424,7,True,True,True,False,0,624362844,273.26739501953125,483.85626220703125,467.737548828125,-57.3248291015625,-89.61827087402344,-62.147029876708984,60.546241760253906,0.6682990789413452,-1.0251954793930054,-0.869085967540741,1.2314218282699585,0.5476329922676086 +119,0.360021,1380425,7,True,True,True,False,0,624362852,273.2674865722656,483.85626220703125,467.7375183105469,-57.3243293762207,-89.61829376220703,-62.14753723144531,60.546234130859375,0.6683047413825989,-1.0252022743225098,-0.869085967540741,1.2314218282699585,0.5476495623588562 +123,0.367998,1380426,7,True,True,True,False,0,624362860,273.2672119140625,483.856201171875,467.7381896972656,-57.31398391723633,-89.61824798583984,-62.15786361694336,60.5462532043457,0.6682820320129395,-1.0251215696334839,-0.8690671920776367,1.2314218282699585,0.5476329922676086 +125,0.375956,1380427,7,True,True,True,False,0,624362868,273.26678466796875,483.8565368652344,467.7381286621094,-57.31636047363281,-89.61824035644531,-62.155433654785156,60.54630661010742,0.6682933568954468,-1.0251282453536987,-0.869085967540741,1.2314218282699585,0.5476329922676086 +127,0.383962,1380428,7,True,True,True,False,0,624362876,273.2672424316406,483.85638427734375,467.7380065917969,-57.318782806396484,-89.61824035644531,-62.15305709838867,60.546260833740234,0.6683047413825989,-1.025141716003418,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +130,0.39201,1380429,7,True,True,True,False,0,624362884,273.2672424316406,483.8564147949219,467.73797607421875,-57.3219108581543,-89.61824798583984,-62.14992904663086,60.546260833740234,0.6683104038238525,-1.025141716003418,-0.8690953850746155,1.2313971519470215,0.5476329922676086 +133,0.400016,1380430,7,True,True,True,False,0,624362892,273.2674560546875,483.8562316894531,467.7377624511719,-57.32666015625,-89.61824798583984,-62.145206451416016,60.546234130859375,0.6682990789413452,-1.0251686573028564,-0.8690953850746155,1.2313971519470215,0.5476164221763611 +135,0.408021,1380431,7,True,True,True,False,0,624362900,273.26727294921875,483.8564453125,467.7376708984375,-57.329444885253906,-89.61820220947266,-62.14239501953125,60.546260833740234,0.6683104038238525,-1.0251820087432861,-0.8691142201423645,1.2314218282699585,0.5475832223892212 +139,0.416032,1380432,7,True,True,True,False,0,624362908,273.2672424316406,483.85638427734375,467.7380676269531,-57.31928253173828,-89.61824798583984,-62.152557373046875,60.546260833740234,0.6683047413825989,-1.0251350402832031,-0.869085967540741,1.2314053773880005,0.5476329922676086 +141,0.424011,1380433,7,True,True,True,False,0,624362916,273.2668151855469,483.8565673828125,467.73809814453125,-57.31918716430664,-89.61824035644531,-62.15260314941406,60.54630661010742,0.6682990789413452,-1.0251282453536987,-0.8690953850746155,1.2314053773880005,0.5476329922676086 +143,0.43203,1380434,7,True,True,True,False,0,624362924,273.2674560546875,483.856201171875,467.7381286621094,-57.31531524658203,-89.61823272705078,-62.156551361083984,60.546234130859375,0.6682990789413452,-1.0251282453536987,-0.869085967540741,1.2314300537109375,0.5476329922676086 +147,0.440005,1380435,7,True,True,True,False,0,624362932,273.26715087890625,483.85595703125,467.7384948730469,-57.31529235839844,-89.61817169189453,-62.156558990478516,60.54624557495117,0.6682536602020264,-1.0250879526138306,-0.869085967540741,1.2314218282699585,0.5475832223892212 +149,0.448026,1380436,7,True,True,True,False,0,624362940,273.2672119140625,483.8562927246094,467.7381286621094,-57.31711959838867,-89.61823272705078,-62.154720306396484,60.546260833740234,0.6682933568954468,-1.0251282453536987,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +151,0.456027,1380437,7,True,True,True,False,0,624362948,273.2673645019531,483.8563232421875,467.738037109375,-57.31639862060547,-89.61824035644531,-62.155452728271484,60.54624557495117,0.6683047413825989,-1.025141716003418,-0.8690953850746155,1.2314300537109375,0.5476495623588562 +154,0.464016,1380438,7,True,True,True,False,0,624362956,273.26727294921875,483.8564453125,467.7376403808594,-57.322662353515625,-89.61824798583984,-62.14917755126953,60.546260833740234,0.6683160662651062,-1.0251888036727905,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +157,0.472036,1380439,7,True,True,True,False,0,624362964,273.2672119140625,483.8563232421875,467.7381286621094,-57.31769943237305,-89.61822509765625,-62.15414047241211,60.546260833740234,0.6682990789413452,-1.0251282453536987,-0.869085967540741,1.2314218282699585,0.5476164221763611 +159,0.480005,1380440,7,True,True,True,False,0,624362972,273.267333984375,483.8559875488281,467.7382507324219,-57.32084655761719,-89.61821746826172,-62.15102005004883,60.546234130859375,0.6682707071304321,-1.0251080989837646,-0.86910480260849,1.2313889265060425,0.5476164221763611 +161,0.488046,1380441,7,True,True,True,False,0,624362980,273.26666259765625,483.85626220703125,467.73846435546875,-57.31333923339844,-89.61822509765625,-62.158451080322266,60.54630661010742,0.66825932264328,-1.0250879526138306,-0.869085967540741,1.2314053773880005,0.5476329922676086 +164,0.496004,1380442,7,True,True,True,False,0,624362988,273.267333984375,483.85626220703125,467.7381286621094,-57.31789016723633,-89.61822509765625,-62.15397262573242,60.54624557495117,0.6682990789413452,-1.0251282453536987,-0.86910480260849,1.2314218282699585,0.5476329922676086 +167,0.504019,1380443,7,True,True,True,False,0,624362996,273.2669372558594,483.8568115234375,467.7378234863281,-57.32344055175781,-89.61822509765625,-62.14834976196289,60.54630661010742,0.668333113193512,-1.025161862373352,-0.86910480260849,1.2314218282699585,0.5476164221763611 +169,0.512044,1380444,7,True,True,True,False,0,624363004,273.26727294921875,483.8564453125,467.7379150390625,-57.31913757324219,-89.61825561523438,-62.15270233154297,60.546260833740234,0.6683160662651062,-1.0251551866531372,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +173,0.520012,1380445,7,True,True,True,False,0,624363012,273.2674865722656,483.85626220703125,467.7380065917969,-57.318885803222656,-89.61824798583984,-62.15298080444336,60.546234130859375,0.6683104038238525,-1.025141716003418,-0.8690671920776367,1.2314136028289795,0.5476164221763611 +175,0.528011,1380446,7,True,True,True,False,0,624363020,273.26678466796875,483.85650634765625,467.7383117675781,-57.313629150390625,-89.61823272705078,-62.158164978027344,60.54630661010742,0.6682933568954468,-1.0251080989837646,-0.8690671920776367,1.2314218282699585,0.5476164221763611 +177,0.536031,1380447,7,True,True,True,False,0,624363028,273.2667541503906,483.8564453125,467.73828125,-57.310752868652344,-89.61825561523438,-62.161041259765625,60.54630661010742,0.6682820320129395,-1.0251147747039795,-0.8690671920776367,1.2314300537109375,0.5476495623588562 +180,0.544005,1380448,7,True,True,True,False,0,624363036,273.266845703125,483.85662841796875,467.7380065917969,-57.3193359375,-89.61825561523438,-62.1524543762207,60.54630661010742,0.6683104038238525,-1.025141716003418,-0.8690766096115112,1.2314053773880005,0.5476329922676086 +183,0.551998,1380449,7,True,True,True,False,0,624363044,273.266845703125,483.85662841796875,467.7379455566406,-57.31735610961914,-89.6182632446289,-62.15443420410156,60.54630661010742,0.6683104038238525,-1.0251483917236328,-0.8690671920776367,1.2314218282699585,0.5476329922676086 +185,0.560039,1380450,7,True,True,True,False,0,624363052,273.2673034667969,483.8563537597656,467.7381896972656,-57.31605911254883,-89.61825561523438,-62.15578842163086,60.5462532043457,0.6683047413825989,-1.0251215696334839,-0.8690671920776367,1.2314053773880005,0.5476329922676086 +189,0.568069,1380451,7,True,True,True,False,0,624363060,273.2666931152344,483.8563537597656,467.73846435546875,-57.315574645996094,-89.61817932128906,-62.156219482421875,60.54630661010742,0.6682707071304321,-1.0250879526138306,-0.8690766096115112,1.2314136028289795,0.5475832223892212 +191,0.575992,1380452,7,True,True,True,False,0,624363068,273.266845703125,483.85662841796875,467.7381591796875,-57.31814956665039,-89.6182632446289,-62.15364456176758,60.54630661010742,0.6683047413825989,-1.0251215696334839,-0.8690671920776367,1.2313889265060425,0.5476329922676086 +193,0.584042,1380453,7,True,True,True,False,0,624363076,273.2672119140625,483.8562927246094,467.73822021484375,-57.31643295288086,-89.61825561523438,-62.1554069519043,60.546260833740234,0.6682990789413452,-1.0251147747039795,-0.869085967540741,1.2313971519470215,0.5476495623588562 +197,0.592008,1380454,7,True,True,True,False,0,624363084,273.266845703125,483.85662841796875,467.73809814453125,-57.319480895996094,-89.61824798583984,-62.15230941772461,60.54630661010742,0.6683104038238525,-1.0251282453536987,-0.869085967540741,1.2313971519470215,0.5476329922676086 +199,0.600022,1380455,7,True,True,True,False,0,624363092,273.2673645019531,483.8563537597656,467.7380676269531,-57.31824493408203,-89.61824035644531,-62.15361022949219,60.54624557495117,0.6683104038238525,-1.0251350402832031,-0.869085967540741,1.2314136028289795,0.5476329922676086 +201,0.608013,1380456,7,True,True,True,False,0,624363100,273.266845703125,483.85662841796875,467.7377014160156,-57.324180603027344,-89.61827087402344,-62.147613525390625,60.54630661010742,0.6683047413825989,-1.0251753330230713,-0.8690671920776367,1.2314053773880005,0.5476164221763611 +204,0.616039,1380457,7,True,True,True,False,0,624363108,273.2668151855469,483.8565673828125,467.73760986328125,-57.322628021240234,-89.6182861328125,-62.14916229248047,60.54630661010742,0.6682933568954468,-1.0251888036727905,-0.869085967540741,1.2314218282699585,0.5476495623588562 +208,0.624007,1380458,7,True,True,True,False,0,624363116,273.2667541503906,483.8564453125,467.7378234863281,-57.323997497558594,-89.61824798583984,-62.147796630859375,60.54630661010742,0.6682820320129395,-1.025161862373352,-0.869085967540741,1.2314053773880005,0.5476164221763611 +210,0.632003,1380459,7,True,True,True,False,0,624363124,273.267333984375,483.85638427734375,467.7380065917969,-57.31802749633789,-89.61824035644531,-62.1538200378418,60.5462532043457,0.6683104038238525,-1.025141716003418,-0.869085967540741,1.2314218282699585,0.5476329922676086 +212,0.640026,1380460,7,True,True,True,False,0,624363132,273.26739501953125,483.85638427734375,467.7378845214844,-57.32133102416992,-89.61827850341797,-62.15052032470703,60.54624557495117,0.6683160662651062,-1.0251551866531372,-0.8690671920776367,1.2313971519470215,0.5476329922676086 +215,0.648011,1380461,7,True,True,True,False,0,624363140,273.2666931152344,483.8563537597656,467.73846435546875,-57.31257629394531,-89.61823272705078,-62.159217834472656,60.54630661010742,0.6682707071304321,-1.0250879526138306,-0.8690766096115112,1.2314053773880005,0.5476329922676086 +218,0.656023,1380462,7,True,True,True,False,0,624363148,273.2667541503906,483.8564758300781,467.73809814453125,-57.31930923461914,-89.61824798583984,-62.15248107910156,60.54630661010742,0.6682876944541931,-1.0251282453536987,-0.8690671920776367,1.2313971519470215,0.5476164221763611 +220,0.664026,1380463,7,True,True,True,False,0,624363156,273.26678466796875,483.8565368652344,467.73809814453125,-57.319480895996094,-89.61824798583984,-62.15230941772461,60.54630661010742,0.6682933568954468,-1.0251282453536987,-0.869085967540741,1.2313971519470215,0.5476329922676086 +224,0.672026,1380464,7,True,True,True,False,0,624363164,273.2673034667969,483.8565368652344,467.73773193359375,-57.323768615722656,-89.6183090209961,-62.14806365966797,60.546260833740234,0.6683217287063599,-1.0251686573028564,-0.8690671920776367,1.2313807010650635,0.5476495623588562 +226,0.680008,1380465,7,True,True,True,False,0,624363172,273.26751708984375,483.85687255859375,467.7374267578125,-57.32804489135742,-89.61830139160156,-62.143795013427734,60.546260833740234,0.6683728098869324,-1.0252022743225098,-0.8690766096115112,1.2313971519470215,0.5476329922676086 +228,0.688033,1380466,7,True,True,True,False,0,624363180,273.26702880859375,483.85693359375,467.7375793457031,-57.327396392822266,-89.6182632446289,-62.14439392089844,60.54630661010742,0.6683501601219177,-1.0251888036727905,-0.869085967540741,1.2314053773880005,0.5476164221763611 +232,0.696028,1380467,7,True,True,True,False,0,624363188,273.2668151855469,483.8565673828125,467.73809814453125,-57.31813430786133,-89.6182632446289,-62.153656005859375,60.54630661010742,0.6682990789413452,-1.0251282453536987,-0.869085967540741,1.2313971519470215,0.5476495623588562 +234,0.704017,1380468,7,True,True,True,False,0,624363196,273.26715087890625,483.856201171875,467.7382507324219,-57.31705093383789,-89.61821746826172,-62.1547966003418,60.546260833740234,0.6682820320129395,-1.0251147747039795,-0.869085967540741,1.2314136028289795,0.5476164221763611 +236,0.712048,1380469,7,True,True,True,False,0,624363204,273.2673645019531,483.8563232421875,467.7380065917969,-57.321449279785156,-89.61824035644531,-62.15040588378906,60.54624557495117,0.6683047413825989,-1.025141716003418,-0.869085967540741,1.2314053773880005,0.5476164221763611 +239,0.720011,1380470,7,True,True,True,False,0,624363212,273.2674865722656,483.8564147949219,467.7377624511719,-57.32666015625,-89.61824798583984,-62.14520263671875,60.546241760253906,0.6683217287063599,-1.0251686573028564,-0.8690953850746155,1.2313971519470215,0.5476164221763611 +242,0.728029,1380471,7,True,True,True,False,0,624363220,273.2669982910156,483.85687255859375,467.7375793457031,-57.32738494873047,-89.61829376220703,-62.1444091796875,60.54630661010742,0.6683444380760193,-1.0251888036727905,-0.8690766096115112,1.2313889265060425,0.5476329922676086 +244,0.736018,1380472,7,True,True,True,False,0,624363228,273.2668151855469,483.8565979003906,467.7377624511719,-57.325313568115234,-89.6182632446289,-62.14647674560547,60.54630661010742,0.6682990789413452,-1.0251686573028564,-0.8690953850746155,1.2313971519470215,0.5476329922676086 +246,0.744003,1380473,7,True,True,True,False,0,624363236,273.2667541503906,483.8564758300781,467.7381286621094,-57.31978988647461,-89.6182632446289,-62.15200424194336,60.54630661010742,0.6682820320129395,-1.0251215696334839,-0.869057834148407,1.2313807010650635,0.5476164221763611 +250,0.752045,1380474,7,True,True,True,False,0,624363244,273.26708984375,483.85614013671875,467.73822021484375,-57.322139739990234,-89.6182632446289,-62.14970016479492,60.546260833740234,0.668276309967041,-1.0251080989837646,-0.869085967540741,1.231355905532837,0.5476329922676086 +252,0.760009,1380475,7,True,True,True,False,0,624363252,273.267578125,483.8564453125,467.73779296875,-57.32835388183594,-89.61830139160156,-62.14351272583008,60.546234130859375,0.668333113193512,-1.0251551866531372,-0.8690766096115112,1.231347680091858,0.5476329922676086 +254,0.768023,1380476,7,True,True,True,False,0,624363260,273.26708984375,483.8570556640625,467.737548828125,-57.33233642578125,-89.6183090209961,-62.13945770263672,60.54630661010742,0.6683671474456787,-1.0251820087432861,-0.8690671920776367,1.231347680091858,0.5476164221763611 +258,0.776056,1380477,7,True,True,True,False,0,624363268,273.266845703125,483.8566589355469,467.7379150390625,-57.32455062866211,-89.6183090209961,-62.14724349975586,60.54630661010742,0.6683104038238525,-1.025141716003418,-0.8690671920776367,1.231347680091858,0.5476495623588562 +260,0.784014,1380478,7,True,True,True,False,0,624363276,273.2672424316406,483.85638427734375,467.73773193359375,-57.329959869384766,-89.61829376220703,-62.14188003540039,60.546260833740234,0.6683047413825989,-1.025161862373352,-0.869085967540741,1.231347680091858,0.5476329922676086 +262,0.792039,1380479,7,True,True,True,False,0,624363284,273.2672424316406,483.85638427734375,467.73797607421875,-57.326576232910156,-89.61827850341797,-62.145263671875,60.546260833740234,0.6683047413825989,-1.0251350402832031,-0.869085967540741,1.231347680091858,0.5476329922676086 +265,0.800007,1380480,7,True,True,True,False,0,624363292,273.26751708984375,483.8565979003906,467.7376403808594,-57.32865905761719,-89.6183090209961,-62.143192291259766,60.54624557495117,0.6683444380760193,-1.0251753330230713,-0.869057834148407,1.231364130973816,0.5476164221763611 +268,0.808012,1380481,7,True,True,True,False,0,624363300,273.26690673828125,483.8567199707031,467.7379455566406,-57.3227653503418,-89.6182861328125,-62.149024963378906,60.54630661010742,0.6683217287063599,-1.025141716003418,-0.8690671920776367,1.2313724756240845,0.5476329922676086 +270,0.816034,1380482,7,True,True,True,False,0,624363308,273.267333984375,483.8565673828125,467.73773193359375,-57.3260498046875,-89.61831665039062,-62.145790100097656,60.546260833740234,0.668333113193512,-1.025161862373352,-0.8690671920776367,1.231355905532837,0.5476495623588562 +274,0.82402,1380483,7,True,True,True,False,0,624363316,273.26678466796875,483.8565368652344,467.7381896972656,-57.322227478027344,-89.61827087402344,-62.149566650390625,60.54630661010742,0.6682990789413452,-1.0251147747039795,-0.8690766096115112,1.231355905532837,0.5476329922676086 +276,0.83202,1380484,7,True,True,True,False,0,624363324,273.2671203613281,483.856201171875,467.7383117675781,-57.323238372802734,-89.61820983886719,-62.14860153198242,60.546260833740234,0.6682820320129395,-1.0251014232635498,-0.869085967540741,1.2313724756240845,0.5475832223892212 +278,0.840019,1380485,7,True,True,True,False,0,624363332,273.26739501953125,483.85638427734375,467.7380065917969,-57.31830596923828,-89.61825561523438,-62.15354919433594,60.54624557495117,0.6683160662651062,-1.025141716003418,-0.8690766096115112,1.2314136028289795,0.5476329922676086 +282,0.848023,1380486,7,True,True,True,False,0,624363340,273.2669372558594,483.8568115234375,467.73779296875,-57.32426834106445,-89.61829376220703,-62.14752197265625,60.54630661010742,0.668333113193512,-1.025161862373352,-0.8690671920776367,1.2313807010650635,0.5476329922676086 +284,0.856017,1380487,7,True,True,True,False,0,624363348,273.2673034667969,483.8565368652344,467.73773193359375,-57.323768615722656,-89.6183090209961,-62.14806365966797,60.546260833740234,0.6683217287063599,-1.0251686573028564,-0.8690671920776367,1.2313807010650635,0.5476495623588562 +286,0.86401,1380488,7,True,True,True,False,0,624363356,273.266845703125,483.8566589355469,467.73797607421875,-57.32306671142578,-89.6182632446289,-62.148719787597656,60.54630661010742,0.6683104038238525,-1.025141716003418,-0.8690671920776367,1.2313807010650635,0.5476164221763611 +288,0.872035,1380489,7,True,True,True,False,0,624363364,273.2673645019531,483.8564453125,467.7375793457031,-57.3278694152832,-89.61827087402344,-62.143978118896484,60.5462532043457,0.6683160662651062,-1.0251888036727905,-0.8690953850746155,1.2313971519470215,0.5476329922676086 +291,0.88003,1380490,7,True,True,True,False,0,624363372,273.2674560546875,483.8564758300781,467.7377624511719,-57.325313568115234,-89.6182632446289,-62.14653778076172,60.54624557495117,0.6683273911476135,-1.0251686573028564,-0.8690953850746155,1.2313971519470215,0.5476329922676086 +294,0.888019,1380491,7,True,True,True,False,0,624363380,273.26708984375,483.8570556640625,467.7374572753906,-57.32956314086914,-89.6182861328125,-62.14223098754883,60.54630661010742,0.6683671474456787,-1.0252022743225098,-0.8690953850746155,1.2313971519470215,0.5476329922676086 +296,0.896042,1380492,7,True,True,True,False,0,624363388,273.26690673828125,483.8567199707031,467.7380065917969,-57.32220458984375,-89.61823272705078,-62.14958572387695,60.54630661010742,0.6683217287063599,-1.025141716003418,-0.8690953850746155,1.2314053773880005,0.5476164221763611 +300,0.904009,1380493,7,True,True,True,False,0,624363396,273.26678466796875,483.85650634765625,467.7383117675781,-57.315914154052734,-89.61820220947266,-62.15587615966797,60.54630661010742,0.6682933568954468,-1.0251080989837646,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +302,0.912013,1380494,7,True,True,True,False,0,624363404,273.2667541503906,483.8564453125,467.73834228515625,-57.315792083740234,-89.61822509765625,-62.15599822998047,60.54630661010742,0.6682820320129395,-1.0251014232635498,-0.8690953850746155,1.2314053773880005,0.5476329922676086 +304,0.92006,1380495,7,True,True,True,False,0,624363412,273.2674255371094,483.8564147949219,467.7379150390625,-57.32048416137695,-89.61824035644531,-62.1513671875,60.54624557495117,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +308,0.928028,1380496,7,True,True,True,False,0,624363420,273.2669982910156,483.85687255859375,467.73779296875,-57.32456970214844,-89.61824035644531,-62.14722442626953,60.54630661010742,0.6683444380760193,-1.0251686573028564,-0.8690953850746155,1.2314136028289795,0.5476164221763611 +310,0.936027,1380497,7,True,True,True,False,0,624363428,273.26690673828125,483.8567199707031,467.7379150390625,-57.320491790771484,-89.61824035644531,-62.15130615234375,60.54630661010742,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +312,0.944024,1380498,7,True,True,True,False,0,624363436,273.26690673828125,483.8567199707031,467.7378845214844,-57.3215217590332,-89.61824035644531,-62.1502685546875,60.54630661010742,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.2314136028289795,0.5476329922676086 +315,0.952027,1380499,7,True,True,True,False,0,624363444,273.2668151855469,483.8565673828125,467.7381286621094,-57.315773010253906,-89.61824035644531,-62.15602111816406,60.54630661010742,0.6682990789413452,-1.0251282453536987,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +318,0.960002,1380500,7,True,True,True,False,0,624363452,273.2674865722656,483.8565368652344,467.7378234863281,-57.321449279785156,-89.61823272705078,-62.15040588378906,60.54624557495117,0.668333113193512,-1.0251686573028564,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +320,0.96803,1380501,7,True,True,True,False,0,624363460,273.26690673828125,483.85675048828125,467.7375183105469,-57.33047103881836,-89.61822509765625,-62.14132308959961,60.54630661010742,0.6683217287063599,-1.0252022743225098,-0.8690953850746155,1.2314218282699585,0.5475832223892212 +324,0.976073,1380502,7,True,True,True,False,0,624363468,273.2669677734375,483.8568420410156,467.7375793457031,-57.32292938232422,-89.6182632446289,-62.148860931396484,60.54630661010742,0.668333113193512,-1.0251954793930054,-0.86910480260849,1.2314382791519165,0.5476495623588562 +326,0.984012,1380503,7,True,True,True,False,0,624363476,273.2669677734375,483.8568420410156,467.7378234863281,-57.31935119628906,-89.61824798583984,-62.15243911743164,60.54630661010742,0.668333113193512,-1.0251686573028564,-0.869085967540741,1.2314382791519165,0.5476329922676086 +328,0.992041,1380504,7,True,True,True,False,0,624363484,273.2672119140625,483.8563232421875,467.73822021484375,-57.31706237792969,-89.6181869506836,-62.15477752685547,60.546260833740234,0.6682990789413452,-1.0251215696334839,-0.8691142201423645,1.2314382791519165,0.5476164221763611 +330,1.000029,1380505,7,True,True,True,False,0,624363492,273.26739501953125,483.85638427734375,467.73809814453125,-57.31635284423828,-89.61824035644531,-62.15550231933594,60.54624557495117,0.6683160662651062,-1.0251350402832031,-0.86910480260849,1.2314300537109375,0.5476495623588562 +334,1.008031,1380506,7,True,True,True,False,0,624363500,273.2673645019531,483.8563537597656,467.7379455566406,-57.31734848022461,-89.6182632446289,-62.154502868652344,60.54624557495117,0.6683104038238525,-1.0251483917236328,-0.8690671920776367,1.2314218282699585,0.5476329922676086 +336,1.016031,1380507,7,True,True,True,False,0,624363508,273.266845703125,483.85662841796875,467.7379455566406,-57.317474365234375,-89.61824035644531,-62.154319763183594,60.54630661010742,0.6683104038238525,-1.0251551866531372,-0.8690671920776367,1.2314382791519165,0.5476164221763611 +338,1.02406,1380508,7,True,True,True,False,0,624363516,273.2668151855469,483.8565673828125,467.738037109375,-57.315185546875,-89.61824035644531,-62.15660858154297,60.54630661010742,0.6682990789413452,-1.025141716003418,-0.8690766096115112,1.2314382791519165,0.5476329922676086 +341,1.032013,1380509,7,True,True,True,False,0,624363524,273.2671813964844,483.85626220703125,467.73822021484375,-57.31446838378906,-89.61823272705078,-62.157371520996094,60.546260833740234,0.6682876944541931,-1.0251215696334839,-0.869085967540741,1.2314300537109375,0.5476329922676086 +344,1.040027,1380510,7,True,True,True,False,0,624363532,273.26727294921875,483.856201171875,467.7381286621094,-57.31455612182617,-89.61824035644531,-62.15729904174805,60.54624557495117,0.6682876944541931,-1.0251282453536987,-0.8690766096115112,1.2314300537109375,0.5476329922676086 +346,1.048027,1380511,7,True,True,True,False,0,624363540,273.2673034667969,483.85650634765625,467.7378234863281,-57.32344055175781,-89.61822509765625,-62.14839553833008,60.546260833740234,0.6683217287063599,-1.025161862373352,-0.86910480260849,1.2314218282699585,0.5476164221763611 +349,1.056029,1380512,7,True,True,True,False,0,624363548,273.2669982910156,483.85687255859375,467.73773193359375,-57.32304000854492,-89.61824798583984,-62.14875411987305,60.54630661010742,0.6683444380760193,-1.0251753330230713,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +352,1.064024,1380513,7,True,True,True,False,0,624363556,273.2669372558594,483.8567810058594,467.7378234863281,-57.32145309448242,-89.61823272705078,-62.15033721923828,60.54630661010742,0.6683273911476135,-1.0251686573028564,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +354,1.072035,1380514,7,True,True,True,False,0,624363564,273.2668151855469,483.8565979003906,467.73773193359375,-57.32439041137695,-89.61824035644531,-62.14740753173828,60.54630661010742,0.6682990789413452,-1.0251753330230713,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +358,1.080018,1380515,7,True,True,True,False,0,624363572,273.2673645019531,483.8563537597656,467.7375183105469,-57.32674026489258,-89.61824798583984,-62.145111083984375,60.54624557495117,0.6683047413825989,-1.0252022743225098,-0.8690953850746155,1.2314300537109375,0.5476164221763611 +360,1.088037,1380516,7,True,True,True,False,0,624363580,273.267333984375,483.8565368652344,467.7374572753906,-57.325218200683594,-89.6182632446289,-62.14662170410156,60.546260833740234,0.6683273911476135,-1.0252090692520142,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +362,1.096032,1380517,7,True,True,True,False,0,624363588,273.26690673828125,483.8567199707031,467.73785400390625,-57.31895446777344,-89.61825561523438,-62.152835845947266,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2314300537109375,0.5476495623588562 +365,1.104035,1380518,7,True,True,True,False,0,624363596,273.26702880859375,483.8569641113281,467.7375183105469,-57.32674026489258,-89.61824798583984,-62.145050048828125,60.54630661010742,0.6683557629585266,-1.0252022743225098,-0.8690953850746155,1.2314300537109375,0.5476164221763611 +368,1.112023,1380519,7,True,True,True,False,0,624363604,273.266845703125,483.85662841796875,467.73809814453125,-57.320648193359375,-89.61820220947266,-62.15114212036133,60.54630661010742,0.6683047413825989,-1.0251350402832031,-0.8690953850746155,1.2314218282699585,0.5475997924804688 +370,1.120028,1380520,7,True,True,True,False,0,624363612,273.2674255371094,483.8564453125,467.73785400390625,-57.317909240722656,-89.61824798583984,-62.1539421081543,60.54624557495117,0.6683273911476135,-1.025161862373352,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +372,1.128037,1380521,7,True,True,True,False,0,624363620,273.266845703125,483.85662841796875,467.73822021484375,-57.314186096191406,-89.61821746826172,-62.1576042175293,60.54630661010742,0.6683047413825989,-1.0251215696334839,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +375,1.136032,1380522,7,True,True,True,False,0,624363628,273.2669677734375,483.8568420410156,467.7379150390625,-57.32183074951172,-89.61823272705078,-62.14996337890625,60.54630661010742,0.6683387756347656,-1.0251551866531372,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +378,1.144016,1380523,7,True,True,True,False,0,624363636,273.26690673828125,483.8567199707031,467.73785400390625,-57.31849670410156,-89.61824035644531,-62.15329360961914,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.869085967540741,1.2314382791519165,0.5476329922676086 +380,1.15203,1380524,7,True,True,True,False,0,624363644,273.2673645019531,483.85662841796875,467.7376708984375,-57.32160568237305,-89.61827850341797,-62.15023422241211,60.546260833740234,0.6683387756347656,-1.0251820087432861,-0.8690671920776367,1.2314218282699585,0.5476329922676086 +384,1.160046,1380525,7,True,True,True,False,0,624363652,273.2674255371094,483.8564453125,467.73779296875,-57.32200622558594,-89.61824798583984,-62.149845123291016,60.54624557495117,0.6683217287063599,-1.0251686573028564,-0.8690766096115112,1.2314218282699585,0.5476164221763611 +386,1.168022,1380526,7,True,True,True,False,0,624363660,273.2673034667969,483.85650634765625,467.7378234863281,-57.32476043701172,-89.61824035644531,-62.14707946777344,60.546260833740234,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2314053773880005,0.5476164221763611 +388,1.176048,1380527,7,True,True,True,False,0,624363668,273.266845703125,483.85662841796875,467.73809814453125,-57.31433868408203,-89.61827087402344,-62.15745544433594,60.54630661010742,0.6683047413825989,-1.0251350402832031,-0.8690671920776367,1.2314218282699585,0.5476495623588562 +392,1.184015,1380528,7,True,True,True,False,0,624363676,273.26702880859375,483.85693359375,467.73760986328125,-57.322628021240234,-89.6182861328125,-62.14916229248047,60.54630661010742,0.6683501601219177,-1.0251888036727905,-0.869085967540741,1.2314218282699585,0.5476495623588562 +394,1.192021,1380529,7,True,True,True,False,0,624363684,273.2669677734375,483.8568420410156,467.73779296875,-57.32142639160156,-89.61825561523438,-62.15036392211914,60.54630661010742,0.668333113193512,-1.0251686573028564,-0.869085967540741,1.2314218282699585,0.5476329922676086 +396,1.200034,1380530,7,True,True,True,False,0,624363692,273.26739501953125,483.8562316894531,467.7381286621094,-57.31742477416992,-89.61820983886719,-62.15443420410156,60.546241760253906,0.6682990789413452,-1.0251282453536987,-0.8690953850746155,1.2314300537109375,0.5476164221763611 +399,1.208012,1380531,7,True,True,True,False,0,624363700,273.266845703125,483.85662841796875,467.7381896972656,-57.31550598144531,-89.61823272705078,-62.156288146972656,60.54630661010742,0.6683047413825989,-1.0251215696334839,-0.869085967540741,1.2314218282699585,0.5476329922676086 +402,1.216039,1380532,7,True,True,True,False,0,624363708,273.266845703125,483.85662841796875,467.7379455566406,-57.322021484375,-89.61823272705078,-62.1497688293457,60.54630661010742,0.6683104038238525,-1.0251483917236328,-0.8690953850746155,1.2314136028289795,0.5476164221763611 +404,1.224041,1380533,7,True,True,True,False,0,624363716,273.26702880859375,483.8569641113281,467.7375183105469,-57.327022552490234,-89.6182632446289,-62.144771575927734,60.54630661010742,0.6683557629585266,-1.0252022743225098,-0.869085967540741,1.2314218282699585,0.5476164221763611 +408,1.231988,1380534,7,True,True,True,False,0,624363724,273.26690673828125,483.85675048828125,467.73779296875,-57.32142639160156,-89.61825561523438,-62.15036392211914,60.54630661010742,0.6683217287063599,-1.0251686573028564,-0.869085967540741,1.2314218282699585,0.5476329922676086 +410,1.240025,1380535,7,True,True,True,False,0,624363732,273.26739501953125,483.85638427734375,467.7379150390625,-57.32124328613281,-89.61827087402344,-62.150611877441406,60.54624557495117,0.6683160662651062,-1.0251483917236328,-0.8690766096115112,1.2313971519470215,0.5476329922676086 +412,1.248027,1380536,7,True,True,True,False,0,624363740,273.2669677734375,483.8568420410156,467.7377624511719,-57.32560729980469,-89.61824035644531,-62.146183013916016,60.54630661010742,0.6683387756347656,-1.0251686573028564,-0.8690953850746155,1.2314053773880005,0.5476164221763611 +414,1.256044,1380537,7,True,True,True,False,0,624363748,273.26690673828125,483.8567199707031,467.73797607421875,-57.32219314575195,-89.6182632446289,-62.149600982666016,60.54630661010742,0.6683217287063599,-1.025141716003418,-0.869085967540741,1.2313889265060425,0.5476329922676086 +418,1.263989,1380538,7,True,True,True,False,0,624363756,273.26739501953125,483.85638427734375,467.7380065917969,-57.317996978759766,-89.61827087402344,-62.15385437011719,60.54624557495117,0.6683160662651062,-1.025141716003418,-0.8690766096115112,1.2314053773880005,0.5476495623588562 +420,1.272027,1380539,7,True,True,True,False,0,624363764,273.26739501953125,483.8566589355469,467.73760986328125,-57.327293395996094,-89.6182861328125,-62.14454650878906,60.546260833740234,0.6683444380760193,-1.0251820087432861,-0.869085967540741,1.2313889265060425,0.5476329922676086 +422,1.280051,1380540,7,True,True,True,False,0,624363772,273.2675476074219,483.85626220703125,467.7379455566406,-57.32471466064453,-89.61820220947266,-62.147159576416016,60.546226501464844,0.6683104038238525,-1.0251483917236328,-0.8690953850746155,1.2314136028289795,0.5475832223892212 +425,1.28804,1380541,7,True,True,True,False,0,624363780,273.2674255371094,483.8564147949219,467.73779296875,-57.323116302490234,-89.61827087402344,-62.148738861083984,60.54624557495117,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2313971519470215,0.5476495623588562 +428,1.296034,1380542,7,True,True,True,False,0,624363788,273.26690673828125,483.8567199707031,467.7378845214844,-57.32256317138672,-89.61824798583984,-62.14923095703125,60.54630661010742,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.2314053773880005,0.5476329922676086 +430,1.304038,1380543,7,True,True,True,False,0,624363796,273.2667541503906,483.8564453125,467.7383728027344,-57.313716888427734,-89.61821746826172,-62.158077239990234,60.54630661010742,0.6682820320129395,-1.0251014232635498,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +434,1.31204,1380544,7,True,True,True,False,0,624363804,273.26678466796875,483.8565368652344,467.73828125,-57.31572341918945,-89.61820220947266,-62.15606689453125,60.54630661010742,0.6682990789413452,-1.0251147747039795,-0.8690953850746155,1.2314300537109375,0.5476164221763611 +436,1.320035,1380545,7,True,True,True,False,0,624363812,273.2674865722656,483.856689453125,467.73779296875,-57.323524475097656,-89.61824035644531,-62.14832305908203,60.5462532043457,0.6683557629585266,-1.0251686573028564,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +438,1.328057,1380546,7,True,True,True,False,0,624363820,273.26708984375,483.8570556640625,467.737548828125,-57.323204040527344,-89.61827087402344,-62.148590087890625,60.54630661010742,0.6683671474456787,-1.0251954793930054,-0.8690953850746155,1.2314300537109375,0.5476495623588562 +442,1.336017,1380547,7,True,True,True,False,0,624363828,273.2673034667969,483.85650634765625,467.7378234863281,-57.31999206542969,-89.61825561523438,-62.15184783935547,60.546260833740234,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +444,1.344038,1380548,7,True,True,True,False,0,624363836,273.2673645019531,483.8563537597656,467.7380065917969,-57.321449279785156,-89.61824035644531,-62.15040588378906,60.54624557495117,0.6683104038238525,-1.025141716003418,-0.869085967540741,1.2314053773880005,0.5476164221763611 +446,1.352052,1380549,7,True,True,True,False,0,624363844,273.26739501953125,483.8566589355469,467.7376708984375,-57.322547912597656,-89.61827087402344,-62.1492919921875,60.546260833740234,0.6683444380760193,-1.0251820087432861,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +449,1.360036,1380550,7,True,True,True,False,0,624363852,273.2675476074219,483.85638427734375,467.7378234863281,-57.32265090942383,-89.6182632446289,-62.14921569824219,60.546234130859375,0.6683273911476135,-1.025161862373352,-0.869085967540741,1.2314053773880005,0.5476329922676086 +452,1.368014,1380551,7,True,True,True,False,0,624363860,273.2668151855469,483.8565673828125,467.7381896972656,-57.32121658325195,-89.61820220947266,-62.150577545166016,60.54630661010742,0.6682990789413452,-1.0251215696334839,-0.8691142201423645,1.2314053773880005,0.5476164221763611 +454,1.376043,1380552,7,True,True,True,False,0,624363868,273.2668151855469,483.8565673828125,467.73809814453125,-57.3186149597168,-89.61824035644531,-62.15317916870117,60.54630661010742,0.6682990789413452,-1.0251282453536987,-0.86910480260849,1.2314053773880005,0.5476495623588562 +456,1.384042,1380553,7,True,True,True,False,0,624363876,273.2673645019531,483.8563537597656,467.73809814453125,-57.31739044189453,-89.61824035644531,-62.15446090698242,60.54624557495117,0.6683104038238525,-1.0251350402832031,-0.86910480260849,1.2314218282699585,0.5476495623588562 +460,1.392072,1380554,7,True,True,True,False,0,624363884,273.26727294921875,483.8564453125,467.7379455566406,-57.321468353271484,-89.61820220947266,-62.15037155151367,60.546260833740234,0.6683160662651062,-1.0251483917236328,-0.8691142201423645,1.2314300537109375,0.5476164221763611 +462,1.400036,1380555,7,True,True,True,False,0,624363892,273.2674255371094,483.8564147949219,467.7378845214844,-57.31745910644531,-89.61824035644531,-62.154396057128906,60.54624557495117,0.6683217287063599,-1.025161862373352,-0.869085967540741,1.2314465045928955,0.5476329922676086 +464,1.408028,1380556,7,True,True,True,False,0,624363900,273.2674255371094,483.8564453125,467.73779296875,-57.31989669799805,-89.61827087402344,-62.151954650878906,60.54624557495117,0.6683217287063599,-1.0251686573028564,-0.8690671920776367,1.2314218282699585,0.5476329922676086 +468,1.416044,1380557,7,True,True,True,False,0,624363908,273.26739501953125,483.8566589355469,467.7376403808594,-57.322662353515625,-89.61824798583984,-62.14917755126953,60.546260833740234,0.6683444380760193,-1.0251888036727905,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +470,1.424013,1380558,7,True,True,True,False,0,624363916,273.266845703125,483.85662841796875,467.7379150390625,-57.32183074951172,-89.61823272705078,-62.14996337890625,60.54630661010742,0.6683104038238525,-1.0251551866531372,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +472,1.432051,1380559,7,True,True,True,False,0,624363924,273.2668151855469,483.8565979003906,467.73773193359375,-57.32066345214844,-89.6182632446289,-62.1511344909668,60.54630661010742,0.6682990789413452,-1.0251753330230713,-0.8690953850746155,1.2314300537109375,0.5476495623588562 +475,1.440036,1380560,7,True,True,True,False,0,624363932,273.2674560546875,483.8563232421875,467.7376708984375,-57.32312774658203,-89.6182632446289,-62.14873123168945,60.546241760253906,0.6683104038238525,-1.0251820087432861,-0.869085967540741,1.2314218282699585,0.5476329922676086 +478,1.448029,1380561,7,True,True,True,False,0,624363940,273.26739501953125,483.856689453125,467.7371826171875,-57.32978057861328,-89.6182632446289,-62.142059326171875,60.546260833740234,0.6683444380760193,-1.0252426862716675,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +480,1.456053,1380562,7,True,True,True,False,0,624363948,273.266845703125,483.85662841796875,467.7378845214844,-57.317935943603516,-89.61822509765625,-62.15385437011719,60.54630661010742,0.6683104038238525,-1.025161862373352,-0.86910480260849,1.231454849243164,0.5476329922676086 +484,1.464052,1380563,7,True,True,True,False,0,624363956,273.26690673828125,483.85675048828125,467.737548828125,-57.32466506958008,-89.61824035644531,-62.147125244140625,60.54630661010742,0.6683217287063599,-1.0252022743225098,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +486,1.472026,1380564,7,True,True,True,False,0,624363964,273.26678466796875,483.8565368652344,467.7381286621094,-57.31591033935547,-89.6181869506836,-62.1558837890625,60.54630661010742,0.6682933568954468,-1.0251350402832031,-0.86910480260849,1.231454849243164,0.5476164221763611 +488,1.48005,1380565,7,True,True,True,False,0,624363972,273.2673034667969,483.8562316894531,467.7379455566406,-57.317378997802734,-89.61823272705078,-62.154483795166016,60.54624557495117,0.6682933568954468,-1.0251551866531372,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +492,1.488033,1380566,7,True,True,True,False,0,624363980,273.26751708984375,483.85662841796875,467.7373352050781,-57.32826614379883,-89.61825561523438,-62.14358901977539,60.54624557495117,0.6683444380760193,-1.0252225399017334,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +494,1.496031,1380567,7,True,True,True,False,0,624363988,273.26708984375,483.8570556640625,467.7373046875,-57.32673645019531,-89.61827087402344,-62.145057678222656,60.54630661010742,0.6683671474456787,-1.0252292156219482,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +496,1.504027,1380568,7,True,True,True,False,0,624363996,273.2673034667969,483.85650634765625,467.7379455566406,-57.31706237792969,-89.61824798583984,-62.15477752685547,60.546260833740234,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +498,1.512052,1380569,7,True,True,True,False,0,624364004,273.26690673828125,483.8567199707031,467.738037109375,-57.316707611083984,-89.61822509765625,-62.15508270263672,60.54630661010742,0.6683217287063599,-1.025141716003418,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +501,1.520025,1380570,7,True,True,True,False,0,624364012,273.2676086425781,483.8564758300781,467.73785400390625,-57.31791687011719,-89.61824798583984,-62.15394973754883,60.546234130859375,0.6683444380760193,-1.025161862373352,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +504,1.528038,1380571,7,True,True,True,False,0,624364020,273.2674255371094,483.8565673828125,467.7378845214844,-57.324398040771484,-89.61821746826172,-62.1474494934082,60.5462532043457,0.6683387756347656,-1.0251551866531372,-0.8691142201423645,1.2314136028289795,0.5476164221763611 +506,1.536046,1380572,7,True,True,True,False,0,624364028,273.266845703125,483.85662841796875,467.7381591796875,-57.314002990722656,-89.61821746826172,-62.15778732299805,60.54630661010742,0.6683104038238525,-1.0251282453536987,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +509,1.544048,1380573,7,True,True,True,False,0,624364036,273.267333984375,483.8565673828125,467.7380065917969,-57.31547164916992,-89.61822509765625,-62.156368255615234,60.546260833740234,0.668333113193512,-1.0251483917236328,-0.8690953850746155,1.231454849243164,0.5476329922676086 +512,1.552018,1380574,7,True,True,True,False,0,624364044,273.266845703125,483.85662841796875,467.73809814453125,-57.315128326416016,-89.61823272705078,-62.15666198730469,60.54630661010742,0.6683047413825989,-1.0251350402832031,-0.869085967540741,1.2314382791519165,0.5476329922676086 +514,1.560056,1380575,7,True,True,True,False,0,624364052,273.2675476074219,483.85626220703125,467.7380676269531,-57.31521224975586,-89.61820983886719,-62.15666961669922,60.546226501464844,0.6683104038238525,-1.025141716003418,-0.869085967540741,1.231454849243164,0.5476164221763611 +518,1.568046,1380576,7,True,True,True,False,0,624364060,273.2668762207031,483.8566589355469,467.7379455566406,-57.31613540649414,-89.61825561523438,-62.15566635131836,60.54630661010742,0.6683160662651062,-1.0251551866531372,-0.8690671920776367,1.2314382791519165,0.5476329922676086 +520,1.57602,1380577,7,True,True,True,False,0,624364068,273.2673645019531,483.8564453125,467.7379455566406,-57.31633377075195,-89.61822509765625,-62.155521392822266,60.5462532043457,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.231454849243164,0.5476329922676086 +522,1.584054,1380578,7,True,True,True,False,0,624364076,273.2673645019531,483.85662841796875,467.7377624511719,-57.32230758666992,-89.61823272705078,-62.149532318115234,60.546260833740234,0.6683387756347656,-1.0251753330230713,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +525,1.592035,1380579,7,True,True,True,False,0,624364084,273.26727294921875,483.8564453125,467.7380676269531,-57.31224060058594,-89.61823272705078,-62.15959930419922,60.546260833740234,0.6683160662651062,-1.025141716003418,-0.8690953850746155,1.231463074684143,0.5476495623588562 +528,1.600029,1380580,7,True,True,True,False,0,624364092,273.26739501953125,483.8562316894531,467.7381896972656,-57.315067291259766,-89.6181869506836,-62.15679931640625,60.546241760253906,0.6682990789413452,-1.0251282453536987,-0.86910480260849,1.231454849243164,0.5476164221763611 +530,1.60805,1380581,7,True,True,True,False,0,624364100,273.26739501953125,483.85650634765625,467.7378234863281,-57.318756103515625,-89.61817169189453,-62.15309143066406,60.5462532043457,0.6683273911476135,-1.0251753330230713,-0.8690953850746155,1.23148775100708,0.5475832223892212 +534,1.616062,1380582,7,True,True,True,False,0,624364108,273.26690673828125,483.8567199707031,467.7379455566406,-57.31063461303711,-89.61822509765625,-62.161155700683594,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.231495976448059,0.5476495623588562 +536,1.62405,1380583,7,True,True,True,False,0,624364116,273.26702880859375,483.85693359375,467.7377624511719,-57.31422805786133,-89.61824035644531,-62.157562255859375,60.54630661010742,0.6683501601219177,-1.0251820087432861,-0.8690953850746155,1.23148775100708,0.5476495623588562 +538,1.632029,1380584,7,True,True,True,False,0,624364124,273.2672119140625,483.8562927246094,467.7381591796875,-57.315040588378906,-89.61821746826172,-62.15679931640625,60.546260833740234,0.6682933568954468,-1.0251282453536987,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +540,1.640055,1380585,7,True,True,True,False,0,624364132,273.2674255371094,483.8563232421875,467.738037109375,-57.311607360839844,-89.61821746826172,-62.16026306152344,60.546241760253906,0.6683104038238525,-1.0251483917236328,-0.869085967540741,1.231479525566101,0.5476329922676086 +544,1.648042,1380586,7,True,True,True,False,0,624364140,273.2673034667969,483.8563537597656,467.7381591796875,-57.31562042236328,-89.61821746826172,-62.15622329711914,60.5462532043457,0.6683047413825989,-1.0251282453536987,-0.869085967540741,1.2314382791519165,0.5476164221763611 +546,1.656036,1380587,7,True,True,True,False,0,624364148,273.2674255371094,483.8564147949219,467.73809814453125,-57.31047058105469,-89.61820220947266,-62.161380767822266,60.54624557495117,0.6683217287063599,-1.025141716003418,-0.8690953850746155,1.23148775100708,0.5476329922676086 +548,1.664056,1380588,7,True,True,True,False,0,624364156,273.267333984375,483.8565673828125,467.7379455566406,-57.31256866455078,-89.61820220947266,-62.159271240234375,60.546260833740234,0.668333113193512,-1.025161862373352,-0.869085967540741,1.231495976448059,0.5476164221763611 +552,1.672035,1380589,7,True,True,True,False,0,624364164,273.26727294921875,483.8564453125,467.738037109375,-57.310279846191406,-89.61819458007812,-62.16156005859375,60.546260833740234,0.6683160662651062,-1.0251483917236328,-0.8690953850746155,1.231495976448059,0.5476329922676086 +554,1.680039,1380590,7,True,True,True,False,0,624364172,273.2674560546875,483.8563537597656,467.738037109375,-57.30952835083008,-89.61820983886719,-62.16233825683594,60.546241760253906,0.6683160662651062,-1.0251483917236328,-0.869085967540741,1.231495976448059,0.5476329922676086 +556,1.68802,1380591,7,True,True,True,False,0,624364180,273.2669982910156,483.85687255859375,467.73785400390625,-57.31218719482422,-89.61819458007812,-62.159610748291016,60.54630661010742,0.6683444380760193,-1.0251753330230713,-0.869085967540741,1.231512427330017,0.5476164221763611 +559,1.696043,1380592,7,True,True,True,False,0,624364188,273.26690673828125,483.8567199707031,467.7380065917969,-57.30812072753906,-89.61817169189453,-62.16366958618164,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2315372228622437,0.5476164221763611 +562,1.704035,1380593,7,True,True,True,False,0,624364196,273.2672119140625,483.8563232421875,467.73828125,-57.3015022277832,-89.6181869506836,-62.17034149169922,60.546260833740234,0.6682990789413452,-1.0251282453536987,-0.869085967540741,1.231528878211975,0.5476495623588562 +564,1.712046,1380594,7,True,True,True,False,0,624364204,273.2674255371094,483.8567199707031,467.7375183105469,-57.3135986328125,-89.61821746826172,-62.15824508666992,60.546260833740234,0.6683501601219177,-1.025215744972229,-0.8690953850746155,1.2315372228622437,0.5476329922676086 +568,1.720048,1380595,7,True,True,True,False,0,624364212,273.267578125,483.8565673828125,467.7374572753906,-57.31679153442383,-89.61824035644531,-62.15507125854492,60.546241760253906,0.6683444380760193,-1.0252225399017334,-0.869085967540741,1.231512427330017,0.5476329922676086 +570,1.728049,1380596,7,True,True,True,False,0,624364220,273.267333984375,483.8565673828125,467.7379455566406,-57.30897521972656,-89.61817169189453,-62.162864685058594,60.546260833740234,0.6683273911476135,-1.0251686573028564,-0.8690953850746155,1.2315372228622437,0.5476164221763611 +572,1.736065,1380597,7,True,True,True,False,0,624364228,273.26690673828125,483.8567199707031,467.7380676269531,-57.305931091308594,-89.61817932128906,-62.165863037109375,60.54630661010742,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.2315372228622437,0.5476329922676086 +575,1.744036,1380598,7,True,True,True,False,0,624364236,273.26739501953125,483.8566589355469,467.73779296875,-57.315757751464844,-89.61822509765625,-62.15608215332031,60.546260833740234,0.6683444380760193,-1.0251753330230713,-0.8690953850746155,1.231479525566101,0.5476329922676086 +578,1.752001,1380599,7,True,True,True,False,0,624364244,273.2674255371094,483.8567199707031,467.7376708984375,-57.3177375793457,-89.61824035644531,-62.15410232543945,60.546260833740234,0.6683501601219177,-1.0251888036727905,-0.869085967540741,1.231471300125122,0.5476329922676086 +580,1.760038,1380600,7,True,True,True,False,0,624364252,273.26751708984375,483.8565673828125,467.73779296875,-57.317657470703125,-89.61823272705078,-62.154197692871094,60.54624557495117,0.6683444380760193,-1.0251753330230713,-0.8690766096115112,1.231463074684143,0.5476164221763611 +582,1.768055,1380601,7,True,True,True,False,0,624364260,273.2673034667969,483.85650634765625,467.7377014160156,-57.31776809692383,-89.61820983886719,-62.15407180786133,60.546260833740234,0.6683217287063599,-1.0251888036727905,-0.8690953850746155,1.23148775100708,0.5476164221763611 +585,1.77607,1380602,7,True,True,True,False,0,624364268,273.2673034667969,483.85650634765625,467.73760986328125,-57.314510345458984,-89.61824035644531,-62.15732955932617,60.546260833740234,0.6683217287063599,-1.0252022743225098,-0.8690766096115112,1.231504201889038,0.5476329922676086 +588,1.784034,1380603,7,True,True,True,False,0,624364276,273.2673034667969,483.85650634765625,467.737548828125,-57.31613540649414,-89.61824035644531,-62.155704498291016,60.546260833740234,0.6683217287063599,-1.0252090692520142,-0.869085967540741,1.231504201889038,0.5476329922676086 +590,1.792072,1380604,7,True,True,True,False,0,624364284,273.2668762207031,483.8566589355469,467.7378234863281,-57.30857467651367,-89.61820220947266,-62.16322708129883,60.54630661010742,0.6683104038238525,-1.0251820087432861,-0.869085967540741,1.2315372228622437,0.5476329922676086 +594,1.800036,1380605,7,True,True,True,False,0,624364292,273.2674255371094,483.8564453125,467.73797607421875,-57.30913162231445,-89.61820220947266,-62.1627197265625,60.54624557495117,0.6683273911476135,-1.025161862373352,-0.869085967540741,1.231512427330017,0.5476329922676086 +596,1.808054,1380606,7,True,True,True,False,0,624364300,273.2674255371094,483.8564147949219,467.7381591796875,-57.31138610839844,-89.6181411743164,-62.16047286987305,60.54624557495117,0.6683217287063599,-1.025141716003418,-0.8690953850746155,1.231512427330017,0.5475832223892212 +598,1.816079,1380607,7,True,True,True,False,0,624364308,273.26739501953125,483.8566589355469,467.7378845214844,-57.3071403503418,-89.61820220947266,-62.16469955444336,60.546260833740234,0.6683444380760193,-1.0251753330230713,-0.8690953850746155,1.2315372228622437,0.5476495623588562 +602,1.824048,1380608,7,True,True,True,False,0,624364316,273.2673645019531,483.85662841796875,467.7375183105469,-57.3135986328125,-89.61821746826172,-62.15824508666992,60.546260833740234,0.6683387756347656,-1.025215744972229,-0.8690953850746155,1.2315372228622437,0.5476329922676086 +604,1.832055,1380609,7,True,True,True,False,0,624364324,273.26739501953125,483.8566589355469,467.73773193359375,-57.314640045166016,-89.61819458007812,-62.15719985961914,60.546260833740234,0.6683444380760193,-1.0251888036727905,-0.8690953850746155,1.231512427330017,0.5476164221763611 +606,1.84007,1380610,7,True,True,True,False,0,624364332,273.2676086425781,483.8564758300781,467.7379150390625,-57.31150436401367,-89.61819458007812,-62.160362243652344,60.546234130859375,0.668333113193512,-1.0251686573028564,-0.86910480260849,1.231512427330017,0.5476329922676086 +609,1.848039,1380611,7,True,True,True,False,0,624364340,273.2674255371094,483.8567199707031,467.73773193359375,-57.31253433227539,-89.61822509765625,-62.159305572509766,60.546260833740234,0.6683501601219177,-1.0251888036727905,-0.869085967540741,1.231512427330017,0.5476329922676086 +612,1.856039,1380612,7,True,True,True,False,0,624364348,273.2674255371094,483.8564147949219,467.73797607421875,-57.313209533691406,-89.61820983886719,-62.15864181518555,60.54624557495117,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.231479525566101,0.5476329922676086 +614,1.864063,1380613,7,True,True,True,False,0,624364356,273.2669677734375,483.8568420410156,467.7378234863281,-57.31454849243164,-89.61822509765625,-62.15724182128906,60.54630661010742,0.6683387756347656,-1.0251753330230713,-0.8690766096115112,1.23148775100708,0.5476164221763611 +618,1.87205,1380614,7,True,True,True,False,0,624364364,273.26690673828125,483.8567199707031,467.7379150390625,-57.3135986328125,-89.61820220947266,-62.1581916809082,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.869085967540741,1.23148775100708,0.5476164221763611 +620,1.880038,1380615,7,True,True,True,False,0,624364372,273.2676086425781,483.85650634765625,467.7377624511719,-57.3148078918457,-89.61823272705078,-62.15705871582031,60.546234130859375,0.6683444380760193,-1.0251820087432861,-0.869085967540741,1.23148775100708,0.5476329922676086 +622,1.888057,1380616,7,True,True,True,False,0,624364380,273.2672424316406,483.8564147949219,467.7379455566406,-57.31631851196289,-89.61822509765625,-62.155521392822266,60.546260833740234,0.6683104038238525,-1.0251551866531372,-0.8690953850746155,1.231454849243164,0.5476329922676086 +624,1.896061,1380617,7,True,True,True,False,0,624364388,273.26727294921875,483.8564147949219,467.738037109375,-57.311912536621094,-89.6181640625,-62.15992736816406,60.546260833740234,0.6683104038238525,-1.0251551866531372,-0.8691142201423645,1.231512427330017,0.5476164221763611 +628,1.904037,1380618,7,True,True,True,False,0,624364396,273.2674865722656,483.8565368652344,467.7378845214844,-57.313865661621094,-89.61821746826172,-62.15798568725586,60.54624557495117,0.668333113193512,-1.0251686573028564,-0.8690953850746155,1.23148775100708,0.5476329922676086 +630,1.91203,1380619,7,True,True,True,False,0,624364404,273.26739501953125,483.8566589355469,467.7377624511719,-57.321746826171875,-89.61820220947266,-62.15009307861328,60.546260833740234,0.6683444380760193,-1.0251753330230713,-0.8691142201423645,1.231454849243164,0.5476164221763611 +632,1.920057,1380620,7,True,True,True,False,0,624364412,273.2674865722656,483.85638427734375,467.73809814453125,-57.310760498046875,-89.61820983886719,-62.161109924316406,60.546241760253906,0.6683217287063599,-1.025141716003418,-0.869085967540741,1.231479525566101,0.5476329922676086 +635,1.928041,1380621,7,True,True,True,False,0,624364420,273.2675476074219,483.85650634765625,467.7379150390625,-57.315853118896484,-89.61820983886719,-62.156005859375,60.546241760253906,0.6683387756347656,-1.025161862373352,-0.86910480260849,1.231471300125122,0.5476329922676086 +638,1.936048,1380622,7,True,True,True,False,0,624364428,273.2673034667969,483.85650634765625,467.7380065917969,-57.31370162963867,-89.6181869506836,-62.158145904541016,60.546260833740234,0.6683217287063599,-1.0251551866531372,-0.8691142201423645,1.23148775100708,0.5476329922676086 +640,1.944065,1380623,7,True,True,True,False,0,624364436,273.2674255371094,483.8567199707031,467.7377014160156,-57.319297790527344,-89.61819458007812,-62.15254211425781,60.546260833740234,0.6683557629585266,-1.0251888036727905,-0.8691142201423645,1.23148775100708,0.5476164221763611 +644,1.952066,1380624,7,True,True,True,False,0,624364444,273.2674865722656,483.8565673828125,467.73785400390625,-57.31236267089844,-89.61819458007812,-62.159488677978516,60.54624557495117,0.6683387756347656,-1.0251753330230713,-0.86910480260849,1.231512427330017,0.5476329922676086 +646,1.960052,1380625,7,True,True,True,False,0,624364452,273.2674560546875,483.8564758300781,467.7379150390625,-57.311893463134766,-89.61821746826172,-62.15996170043945,60.54624557495117,0.6683273911476135,-1.0251686573028564,-0.8690671920776367,1.231495976448059,0.5476164221763611 +648,1.968048,1380626,7,True,True,True,False,0,624364460,273.2674865722656,483.85638427734375,467.7380065917969,-57.310367584228516,-89.61820983886719,-62.16149139404297,60.546241760253906,0.6683217287063599,-1.0251551866531372,-0.869085967540741,1.231495976448059,0.5476329922676086 +652,1.976048,1380627,7,True,True,True,False,0,624364468,273.2673034667969,483.85650634765625,467.7380065917969,-57.31351089477539,-89.61819458007812,-62.158329010009766,60.546260833740234,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.23148775100708,0.5476164221763611 +654,1.984032,1380628,7,True,True,True,False,0,624364476,273.2674255371094,483.8564147949219,467.7379150390625,-57.315860748291016,-89.61820983886719,-62.1559944152832,60.54624557495117,0.6683217287063599,-1.025161862373352,-0.86910480260849,1.231471300125122,0.5476329922676086 +656,1.992037,1380629,7,True,True,True,False,0,624364484,273.266845703125,483.85662841796875,467.7382507324219,-57.303592681884766,-89.61817169189453,-62.16819763183594,60.54630661010742,0.6683047413825989,-1.0251350402832031,-0.8691142201423645,1.2315372228622437,0.5476495623588562 +659,2.000025,1380630,7,True,True,True,False,0,624364492,273.26751708984375,483.8565979003906,467.7379150390625,-57.31073760986328,-89.61820220947266,-62.16111373901367,60.54624557495117,0.6683444380760193,-1.0251686573028564,-0.8690953850746155,1.231512427330017,0.5476329922676086 +663,2.008042,1380631,7,True,True,True,False,0,624364500,273.26751708984375,483.8565979003906,467.7379455566406,-57.30762481689453,-89.6181869506836,-62.16423034667969,60.54624557495117,0.6683444380760193,-1.0251686573028564,-0.8690953850746155,1.2315372228622437,0.5476329922676086 +665,2.016041,1380632,7,True,True,True,False,0,624364508,273.2674865722656,483.8568115234375,467.7377624511719,-57.3148307800293,-89.61819458007812,-62.15700912475586,60.546260833740234,0.6683671474456787,-1.0251820087432861,-0.8690953850746155,1.231504201889038,0.5476164221763611 +667,2.024053,1380633,7,True,True,True,False,0,624364516,273.267333984375,483.8564147949219,467.7376708984375,-57.32164764404297,-89.61821746826172,-62.15019989013672,60.5462532043457,0.6683104038238525,-1.0251888036727905,-0.86910480260849,1.231463074684143,0.5476164221763611 +670,2.032053,1380634,7,True,True,True,False,0,624364524,273.267333984375,483.85626220703125,467.7381896972656,-57.30861282348633,-89.6181869506836,-62.163238525390625,60.54624557495117,0.6682990789413452,-1.0251350402832031,-0.8690953850746155,1.231495976448059,0.5476329922676086 +673,2.040042,1380635,7,True,True,True,False,0,624364532,273.267578125,483.8564453125,467.7378845214844,-57.316593170166016,-89.61824035644531,-62.155277252197266,60.546234130859375,0.668333113193512,-1.025161862373352,-0.86910480260849,1.231454849243164,0.5476495623588562 +675,2.048071,1380636,7,True,True,True,False,0,624364540,273.2674255371094,483.8564453125,467.738037109375,-57.31315612792969,-89.61817169189453,-62.15869903564453,60.54624557495117,0.6683273911476135,-1.0251483917236328,-0.8691142201423645,1.231495976448059,0.5476164221763611 +679,2.056046,1380637,7,True,True,True,False,0,624364548,273.26708984375,483.8570556640625,467.73773193359375,-57.31975555419922,-89.61820983886719,-62.15203857421875,60.54630661010742,0.6683671474456787,-1.0251820087432861,-0.86910480260849,1.231471300125122,0.5476164221763611 +681,2.064048,1380638,7,True,True,True,False,0,624364556,273.2673034667969,483.85650634765625,467.7381286621094,-57.30961227416992,-89.61819458007812,-62.162227630615234,60.546260833740234,0.6683217287063599,-1.025141716003418,-0.8691142201423645,1.231495976448059,0.5476495623588562 +683,2.072057,1380639,7,True,True,True,False,0,624364564,273.2668762207031,483.8566589355469,467.73797607421875,-57.31578063964844,-89.61820220947266,-62.15602111816406,60.54630661010742,0.6683160662651062,-1.0251551866531372,-0.8691142201423645,1.231471300125122,0.5476329922676086 +686,2.080006,1380640,7,True,True,True,False,0,624364572,273.2676696777344,483.85662841796875,467.7372741699219,-57.32401657104492,-89.61824798583984,-62.147850036621094,60.546234130859375,0.6683557629585266,-1.0252360105514526,-0.869085967540741,1.231479525566101,0.5476164221763611 +689,2.088004,1380641,7,True,True,True,False,0,624364580,273.26739501953125,483.8563537597656,467.7376708984375,-57.32133865356445,-89.61824035644531,-62.150516510009766,60.54624557495117,0.6683104038238525,-1.0251888036727905,-0.86910480260849,1.231454849243164,0.5476329922676086 +691,2.096062,1380642,7,True,True,True,False,0,624364588,273.267578125,483.8565673828125,467.7371826171875,-57.329959869384766,-89.6182632446289,-62.141902923583984,60.546241760253906,0.6683444380760193,-1.0252426862716675,-0.8691142201423645,1.2314465045928955,0.5476329922676086 +695,2.104122,1380643,7,True,True,True,False,0,624364596,273.2674865722656,483.8565673828125,467.7373352050781,-57.322296142578125,-89.61824035644531,-62.149559020996094,60.54624557495117,0.668333113193512,-1.0252292156219482,-0.86910480260849,1.23148775100708,0.5476329922676086 +697,2.112039,1380644,7,True,True,True,False,0,624364604,273.2673645019531,483.85662841796875,467.7372741699219,-57.325538635253906,-89.61823272705078,-62.14630126953125,60.546260833740234,0.668333113193512,-1.0252360105514526,-0.86910480260849,1.231479525566101,0.5476164221763611 +699,2.120055,1380645,7,True,True,True,False,0,624364612,273.2675476074219,483.8564147949219,467.7373352050781,-57.32468032836914,-89.61823272705078,-62.14719009399414,60.546234130859375,0.6683217287063599,-1.0252292156219482,-0.86910480260849,1.231479525566101,0.5476164221763611 +703,2.128049,1380646,7,True,True,True,False,0,624364620,273.2676696777344,483.8564453125,467.7377624511719,-57.31934356689453,-89.61824035644531,-62.152530670166016,60.546226501464844,0.6683387756347656,-1.0251753330230713,-0.86910480260849,1.2314465045928955,0.5476495623588562 +705,2.136043,1380647,7,True,True,True,False,0,624364628,273.2674560546875,483.8567810058594,467.7375793457031,-57.32096481323242,-89.61824035644531,-62.150875091552734,60.546260833740234,0.6683614253997803,-1.0252022743225098,-0.86910480260849,1.231471300125122,0.5476329922676086 +707,2.144068,1380648,7,True,True,True,False,0,624364636,273.26739501953125,483.8566589355469,467.73748779296875,-57.32228088378906,-89.61824798583984,-62.149559020996094,60.546260833740234,0.6683444380760193,-1.0252090692520142,-0.8691142201423645,1.231463074684143,0.5476495623588562 +709,2.152012,1380649,7,True,True,True,False,0,624364644,273.26702880859375,483.85693359375,467.7375793457031,-57.323238372802734,-89.61824035644531,-62.148555755615234,60.54630661010742,0.6683501601219177,-1.0251954793930054,-0.86910480260849,1.2314465045928955,0.5476329922676086 +713,2.16005,1380650,7,True,True,True,False,0,624364652,273.2676086425781,483.85650634765625,467.7378845214844,-57.314903259277344,-89.61821746826172,-62.15696334838867,60.546234130859375,0.6683444380760193,-1.0251686573028564,-0.8690953850746155,1.231479525566101,0.5476329922676086 +715,2.168034,1380651,7,True,True,True,False,0,624364660,273.26739501953125,483.85638427734375,467.7381591796875,-57.3099250793457,-89.61820983886719,-62.16192626953125,60.54624557495117,0.6683160662651062,-1.0251350402832031,-0.869085967540741,1.231479525566101,0.5476329922676086 +717,2.17606,1380652,7,True,True,True,False,0,624364668,273.2674560546875,483.8567810058594,467.7375793457031,-57.32066345214844,-89.61825561523438,-62.15117645263672,60.546260833740234,0.6683614253997803,-1.0251954793930054,-0.869085967540741,1.231454849243164,0.5476329922676086 +720,2.184024,1380653,7,True,True,True,False,0,624364676,273.26739501953125,483.8566589355469,467.7377014160156,-57.32135772705078,-89.61824035644531,-62.150482177734375,60.546260833740234,0.6683444380760193,-1.0251820087432861,-0.869085967540741,1.2314465045928955,0.5476164221763611 +723,2.192051,1380654,7,True,True,True,False,0,624364684,273.26702880859375,483.85693359375,467.7375793457031,-57.32160568237305,-89.61824035644531,-62.15018844604492,60.54630661010742,0.6683501601219177,-1.0251954793930054,-0.8691142201423645,1.231454849243164,0.5476495623588562 +725,2.200068,1380655,7,True,True,True,False,0,624364692,273.267578125,483.8565368652344,467.73773193359375,-57.31764221191406,-89.61823272705078,-62.15421676635742,60.546241760253906,0.6683444380760193,-1.0251820087432861,-0.8690953850746155,1.231471300125122,0.5476329922676086 +729,2.208049,1380656,7,True,True,True,False,0,624364700,273.2674865722656,483.85638427734375,467.73797607421875,-57.315773010253906,-89.61820220947266,-62.156089782714844,60.546241760253906,0.6683217287063599,-1.0251551866531372,-0.8691142201423645,1.231471300125122,0.5476329922676086 +731,2.216011,1380657,7,True,True,True,False,0,624364708,273.26690673828125,483.85675048828125,467.73797607421875,-57.31711959838867,-89.6181869506836,-62.1546745300293,60.54630661010742,0.6683273911476135,-1.0251551866531372,-0.8691142201423645,1.231471300125122,0.5476164221763611 +733,2.224069,1380658,7,True,True,True,False,0,624364716,273.2673645019531,483.8565979003906,467.73779296875,-57.3244743347168,-89.61822509765625,-62.14736557006836,60.546260833740234,0.668333113193512,-1.0251686573028564,-0.8691235780715942,1.2314218282699585,0.5476329922676086 +736,2.232055,1380659,7,True,True,True,False,0,624364724,273.2669982910156,483.8569030761719,467.7378234863281,-57.31998825073242,-89.61825561523438,-62.15180587768555,60.54630661010742,0.6683501601219177,-1.025161862373352,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +739,2.240046,1380660,7,True,True,True,False,0,624364732,273.26751708984375,483.8564758300781,467.7378234863281,-57.32133102416992,-89.61824035644531,-62.15052795410156,60.546241760253906,0.668333113193512,-1.025161862373352,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +741,2.248067,1380661,7,True,True,True,False,0,624364740,273.2673034667969,483.85650634765625,467.7378845214844,-57.318519592285156,-89.61821746826172,-62.1533203125,60.546260833740234,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.231454849243164,0.5476164221763611 +744,2.256043,1380662,7,True,True,True,False,0,624364748,273.26739501953125,483.85638427734375,467.7380676269531,-57.31509780883789,-89.61823272705078,-62.15675735473633,60.54624557495117,0.6683160662651062,-1.025141716003418,-0.86910480260849,1.2314465045928955,0.5476495623588562 +747,2.264045,1380663,7,True,True,True,False,0,624364756,273.26690673828125,483.8567199707031,467.73809814453125,-57.31349182128906,-89.61819458007812,-62.158302307128906,60.54630661010742,0.6683217287063599,-1.025141716003418,-0.8691235780715942,1.231471300125122,0.5476495623588562 +749,2.272049,1380664,7,True,True,True,False,0,624364764,273.2669677734375,483.8568420410156,467.7379150390625,-57.313751220703125,-89.61824035644531,-62.158042907714844,60.54630661010742,0.6683387756347656,-1.025161862373352,-0.8690953850746155,1.231471300125122,0.5476495623588562 +751,2.280061,1380665,7,True,True,True,False,0,624364772,273.267822265625,483.8567199707031,467.7373962402344,-57.327301025390625,-89.61827087402344,-62.14457321166992,60.546226501464844,0.668378472328186,-1.0252090692520142,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +755,2.288052,1380666,7,True,True,True,False,0,624364780,273.2674255371094,483.8564453125,467.73785400390625,-57.321372985839844,-89.61821746826172,-62.150482177734375,60.54624557495117,0.6683273911476135,-1.025161862373352,-0.86910480260849,1.2314382791519165,0.5476164221763611 +757,2.296042,1380667,7,True,True,True,False,0,624364788,273.2669372558594,483.8568115234375,467.7378234863281,-57.32389831542969,-89.61824035644531,-62.147891998291016,60.54630661010742,0.668333113193512,-1.025161862373352,-0.8691142201423645,1.2314136028289795,0.5476329922676086 +759,2.304073,1380668,7,True,True,True,False,0,624364796,273.2673034667969,483.85650634765625,467.73785400390625,-57.32001876831055,-89.61823272705078,-62.15182113647461,60.546260833740234,0.6683273911476135,-1.025161862373352,-0.86910480260849,1.2314382791519165,0.5476329922676086 +763,2.312054,1380669,7,True,True,True,False,0,624364804,273.26690673828125,483.85675048828125,467.7378234863281,-57.32118225097656,-89.61821746826172,-62.150611877441406,60.54630661010742,0.6683217287063599,-1.0251686573028564,-0.86910480260849,1.2314465045928955,0.5476164221763611 +765,2.320047,1380670,7,True,True,True,False,0,624364812,273.2677001953125,483.8566589355469,467.7374572753906,-57.32823181152344,-89.61821746826172,-62.14364242553711,60.546234130859375,0.6683614253997803,-1.0252090692520142,-0.8690953850746155,1.2314465045928955,0.5475832223892212 +767,2.328069,1380671,7,True,True,True,False,0,624364820,273.2673645019531,483.85662841796875,467.7378234863281,-57.31830596923828,-89.61824035644531,-62.153533935546875,60.546260833740234,0.6683387756347656,-1.0251686573028564,-0.869085967540741,1.2314465045928955,0.5476329922676086 +770,2.336051,1380672,7,True,True,True,False,0,624364828,273.2671203613281,483.8570861816406,467.7375183105469,-57.324092864990234,-89.61824035644531,-62.147701263427734,60.54630661010742,0.6683728098869324,-1.0252022743225098,-0.86910480260849,1.2314465045928955,0.5476329922676086 +773,2.344057,1380673,7,True,True,True,False,0,624364836,273.26690673828125,483.8567199707031,467.7379455566406,-57.31783676147461,-89.61824035644531,-62.15396499633789,60.54630661010742,0.6683217287063599,-1.0251551866531372,-0.86910480260849,1.2314382791519165,0.5476495623588562 +775,2.352074,1380674,7,True,True,True,False,0,624364844,273.2672424316406,483.85638427734375,467.73809814453125,-57.314659118652344,-89.6181869506836,-62.15718078613281,60.546260833740234,0.6683047413825989,-1.025141716003418,-0.86910480260849,1.231471300125122,0.5476164221763611 +779,2.360053,1380675,7,True,True,True,False,0,624364852,273.2674865722656,483.8565673828125,467.7377624511719,-57.32021713256836,-89.61822509765625,-62.151634216308594,60.54624557495117,0.6683387756347656,-1.0251753330230713,-0.8690953850746155,1.231454849243164,0.5476164221763611 +781,2.36804,1380676,7,True,True,True,False,0,624364860,273.2675476074219,483.85662841796875,467.7376403808594,-57.32190704345703,-89.61825561523438,-62.14994812011719,60.54624557495117,0.6683501601219177,-1.0251888036727905,-0.869085967540741,1.2314382791519165,0.5476329922676086 +783,2.376055,1380677,7,True,True,True,False,0,624364868,273.2669372558594,483.8568115234375,467.7378845214844,-57.31821823120117,-89.61823272705078,-62.1535758972168,60.54630661010742,0.668333113193512,-1.025161862373352,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +787,2.384051,1380678,7,True,True,True,False,0,624364876,273.2674560546875,483.8567810058594,467.7375793457031,-57.322471618652344,-89.61824798583984,-62.14936828613281,60.546260833740234,0.6683614253997803,-1.0251954793930054,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +789,2.392048,1380679,7,True,True,True,False,0,624364884,273.26751708984375,483.8565979003906,467.73785400390625,-57.319541931152344,-89.61821746826172,-62.15230941772461,60.54624557495117,0.6683444380760193,-1.0251686573028564,-0.8691142201423645,1.231454849243164,0.5476329922676086 +791,2.400059,1380680,7,True,True,True,False,0,624364892,273.2677001953125,483.85675048828125,467.73760986328125,-57.321617126464844,-89.61824798583984,-62.15024185180664,60.546241760253906,0.6683728098869324,-1.0251888036727905,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +793,2.408059,1380681,7,True,True,True,False,0,624364900,273.2674255371094,483.8567199707031,467.7377624511719,-57.319637298583984,-89.61823272705078,-62.15220260620117,60.546260833740234,0.6683557629585266,-1.0251753330230713,-0.86910480260849,1.231454849243164,0.5476329922676086 +796,2.416052,1380682,7,True,True,True,False,0,624364908,273.2673645019531,483.8565979003906,467.7378845214844,-57.31641387939453,-89.61824035644531,-62.155426025390625,60.546260833740234,0.6683387756347656,-1.025161862373352,-0.869085967540741,1.231454849243164,0.5476329922676086 +799,2.42405,1380683,7,True,True,True,False,0,624364916,273.26702880859375,483.85693359375,467.7377014160156,-57.32106399536133,-89.61822509765625,-62.150726318359375,60.54630661010742,0.6683501601219177,-1.0251820087432861,-0.8690953850746155,1.231454849243164,0.5476164221763611 +801,2.432066,1380684,7,True,True,True,False,0,624364924,273.26690673828125,483.8567199707031,467.7379150390625,-57.31796646118164,-89.6181869506836,-62.15382766723633,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.8691142201423645,1.231471300125122,0.5476164221763611 +805,2.440102,1380685,7,True,True,True,False,0,624364932,273.2677307128906,483.8565368652344,467.73760986328125,-57.323394775390625,-89.61827850341797,-62.14847946166992,60.546226501464844,0.6683501601219177,-1.0251888036727905,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +807,2.448051,1380686,7,True,True,True,False,0,624364940,273.2673034667969,483.85650634765625,467.73785400390625,-57.32029342651367,-89.61824035644531,-62.151546478271484,60.546260833740234,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2314300537109375,0.5476329922676086 +809,2.456066,1380687,7,True,True,True,False,0,624364948,273.2673034667969,483.85650634765625,467.7379150390625,-57.322593688964844,-89.61822509765625,-62.14924621582031,60.546260833740234,0.6683217287063599,-1.0251551866531372,-0.86910480260849,1.2314218282699585,0.5476164221763611 +813,2.46405,1380688,7,True,True,True,False,0,624364956,273.26751708984375,483.85675048828125,467.737548828125,-57.326202392578125,-89.61822509765625,-62.145652770996094,60.5462532043457,0.6683614253997803,-1.0252022743225098,-0.8691142201423645,1.2314465045928955,0.5476164221763611 +815,2.47206,1380689,7,True,True,True,False,0,624364964,273.2668762207031,483.8566589355469,467.7379455566406,-57.31813430786133,-89.61822509765625,-62.153656005859375,60.54630661010742,0.6683160662651062,-1.0251551866531372,-0.86910480260849,1.2314465045928955,0.5476329922676086 +817,2.480077,1380690,7,True,True,True,False,0,624364972,273.26751708984375,483.8563537597656,467.7378845214844,-57.31898880004883,-89.61822509765625,-62.15287780761719,60.546234130859375,0.6683217287063599,-1.025161862373352,-0.86910480260849,1.2314465045928955,0.5476329922676086 +820,2.488048,1380691,7,True,True,True,False,0,624364980,273.2674865722656,483.8565368652344,467.73785400390625,-57.32012939453125,-89.61820983886719,-62.1517219543457,60.54624557495117,0.668333113193512,-1.0251686573028564,-0.86910480260849,1.231454849243164,0.5476164221763611 +823,2.496061,1380692,7,True,True,True,False,0,624364988,273.2669677734375,483.8568420410156,467.73785400390625,-57.317474365234375,-89.61820983886719,-62.154319763183594,60.54630661010742,0.668333113193512,-1.0251686573028564,-0.8691142201423645,1.231471300125122,0.5476329922676086 +830,2.504054,1380693,7,True,True,True,False,0,624364996,273.2674255371094,483.8567199707031,467.73779296875,-57.322364807128906,-89.61824798583984,-62.14947509765625,60.546260833740234,0.6683501601219177,-1.0251686573028564,-0.8691142201423645,1.2314218282699585,0.5476495623588562 +834,2.512125,1380694,7,True,True,True,False,0,624365004,273.26690673828125,483.8567199707031,467.7380065917969,-57.31758117675781,-89.61820220947266,-62.15420913696289,60.54630661010742,0.6683217287063599,-1.0251483917236328,-0.86910480260849,1.231454849243164,0.5476164221763611 +836,2.520056,1380695,7,True,True,True,False,0,624365012,273.2674255371094,483.8562927246094,467.7382507324219,-57.31439208984375,-89.61817932128906,-62.157466888427734,60.546241760253906,0.6683047413825989,-1.0251215696334839,-0.8691235780715942,1.231454849243164,0.5476329922676086 +838,2.528055,1380696,7,True,True,True,False,0,624365020,273.2674255371094,483.8567199707031,467.7377624511719,-57.321449279785156,-89.61822509765625,-62.150390625,60.546260833740234,0.6683557629585266,-1.0251753330230713,-0.8691142201423645,1.2314465045928955,0.5476329922676086 +842,2.53607,1380697,7,True,True,True,False,0,624365028,273.2669677734375,483.8568420410156,467.7378845214844,-57.318702697753906,-89.61820983886719,-62.15309143066406,60.54630661010742,0.6683444380760193,-1.025161862373352,-0.8691142201423645,1.231454849243164,0.5476329922676086 +846,2.54407,1380698,7,True,True,True,False,0,624365036,273.2675476074219,483.85662841796875,467.7377014160156,-57.323646545410156,-89.61821746826172,-62.1482048034668,60.54624557495117,0.6683501601219177,-1.0251820087432861,-0.8691142201423645,1.2314465045928955,0.5476164221763611 +848,2.55206,1380699,7,True,True,True,False,0,624365044,273.2672119140625,483.8563232421875,467.73822021484375,-57.31467819213867,-89.61819458007812,-62.157161712646484,60.546260833740234,0.6682990789413452,-1.0251215696334839,-0.8691142201423645,1.2314465045928955,0.5476329922676086 +850,2.560056,1380700,7,True,True,True,False,0,624365052,273.2672424316406,483.85638427734375,467.7381896972656,-57.31013870239258,-89.61817169189453,-62.16170120239258,60.546260833740234,0.6683047413825989,-1.0251350402832031,-0.8691142201423645,1.231495976448059,0.5476329922676086 +853,2.568065,1380701,7,True,True,True,False,0,624365060,273.2673034667969,483.85650634765625,467.73809814453125,-57.3128547668457,-89.6181869506836,-62.15898513793945,60.546260833740234,0.6683217287063599,-1.025141716003418,-0.8690953850746155,1.231479525566101,0.5476164221763611 +856,2.576035,1380702,7,True,True,True,False,0,624365068,273.2669677734375,483.8568420410156,467.7378845214844,-57.3170166015625,-89.61819458007812,-62.15477752685547,60.54630661010742,0.668333113193512,-1.0251686573028564,-0.86910480260849,1.231479525566101,0.5476164221763611 +858,2.584085,1380703,7,True,True,True,False,0,624365076,273.267578125,483.85675048828125,467.7374267578125,-57.32748031616211,-89.61827087402344,-62.14437484741211,60.54624557495117,0.6683671474456787,-1.0252090692520142,-0.8691142201423645,1.2314218282699585,0.5476495623588562 +861,2.592053,1380704,7,True,True,True,False,0,624365084,273.2673034667969,483.85650634765625,467.7375183105469,-57.325130462646484,-89.61824798583984,-62.14670944213867,60.546260833740234,0.6683217287063599,-1.0252022743225098,-0.86910480260849,1.2314382791519165,0.5476329922676086 +864,2.60005,1380705,7,True,True,True,False,0,624365092,273.26727294921875,483.8564453125,467.7376403808594,-57.324493408203125,-89.61821746826172,-62.14734649658203,60.546260833740234,0.6683104038238525,-1.0251888036727905,-0.8691142201423645,1.2314465045928955,0.5476164221763611 +866,2.608084,1380706,7,True,True,True,False,0,624365100,273.2674865722656,483.85638427734375,467.7379455566406,-57.31993865966797,-89.61821746826172,-62.151920318603516,60.546241760253906,0.6683217287063599,-1.0251551866531372,-0.8691142201423645,1.2314382791519165,0.5476329922676086 +870,2.616053,1380707,7,True,True,True,False,0,624365108,273.2669982910156,483.85687255859375,467.73779296875,-57.320438385009766,-89.6181869506836,-62.151363372802734,60.54630661010742,0.6683444380760193,-1.0251753330230713,-0.8691235780715942,1.231471300125122,0.5476164221763611 +872,2.624065,1380708,7,True,True,True,False,0,624365116,273.2674865722656,483.8565673828125,467.73785400390625,-57.3221321105957,-89.61820983886719,-62.149723052978516,60.54624557495117,0.6683444380760193,-1.025161862373352,-0.8691142201423645,1.2314382791519165,0.5476164221763611 +874,2.632038,1380709,7,True,True,True,False,0,624365124,273.26739501953125,483.8566589355469,467.73779296875,-57.322364807128906,-89.61824798583984,-62.14947509765625,60.546260833740234,0.6683444380760193,-1.0251686573028564,-0.8691142201423645,1.2314218282699585,0.5476495623588562 +877,2.640074,1380710,7,True,True,True,False,0,624365132,273.267578125,483.8564758300781,467.73785400390625,-57.32136535644531,-89.61821746826172,-62.1505012512207,60.546234130859375,0.6683387756347656,-1.025161862373352,-0.86910480260849,1.2314382791519165,0.5476164221763611 +880,2.648062,1380711,7,True,True,True,False,0,624365140,273.2674865722656,483.85638427734375,467.738037109375,-57.318817138671875,-89.61820220947266,-62.15304183959961,60.546241760253906,0.6683217287063599,-1.025141716003418,-0.86910480260849,1.2314382791519165,0.5476164221763611 +882,2.656061,1380712,7,True,True,True,False,0,624365148,273.2673645019531,483.8565979003906,467.7378234863281,-57.321632385253906,-89.61822509765625,-62.15020751953125,60.546260833740234,0.668333113193512,-1.0251686573028564,-0.8691142201423645,1.2314382791519165,0.5476329922676086 +884,2.664065,1380713,7,True,True,True,False,0,624365156,273.26739501953125,483.8566589355469,467.73785400390625,-57.320308685302734,-89.61820983886719,-62.15153121948242,60.546260833740234,0.6683444380760193,-1.0251686573028564,-0.8691235780715942,1.231454849243164,0.5476329922676086 +887,2.672058,1380714,7,True,True,True,False,0,624365164,273.2669372558594,483.8567810058594,467.7377624511719,-57.32221221923828,-89.61821746826172,-62.14957809448242,60.54630661010742,0.6683273911476135,-1.0251753330230713,-0.8691235780715942,1.2314465045928955,0.5476329922676086 +890,2.680076,1380715,7,True,True,True,False,0,624365172,273.2675476074219,483.85638427734375,467.7375183105469,-57.32588577270508,-89.61824035644531,-62.14598083496094,60.546234130859375,0.6683217287063599,-1.0252022743225098,-0.8691142201423645,1.2314382791519165,0.5476329922676086 +892,2.688096,1380716,7,True,True,True,False,0,624365180,273.26727294921875,483.8564453125,467.7377014160156,-57.326332092285156,-89.6181869506836,-62.1455078125,60.546260833740234,0.6683160662651062,-1.0251820087432861,-0.8691142201423645,1.2314465045928955,0.5475832223892212 +896,2.69606,1380717,7,True,True,True,False,0,624365188,273.26690673828125,483.85675048828125,467.7377014160156,-57.32019805908203,-89.61824798583984,-62.15159225463867,60.54630661010742,0.6683217287063599,-1.0251820087432861,-0.86910480260849,1.2314465045928955,0.5476495623588562 +898,2.704048,1380718,7,True,True,True,False,0,624365196,273.2673034667969,483.85650634765625,467.7380065917969,-57.31717300415039,-89.61820220947266,-62.154666900634766,60.546260833740234,0.6683273911476135,-1.0251483917236328,-0.8691329956054688,1.231454849243164,0.5476495623588562 +900,2.71206,1380719,7,True,True,True,False,0,624365204,273.2669677734375,483.8568420410156,467.7378234863281,-57.320594787597656,-89.61822509765625,-62.15119934082031,60.54630661010742,0.668333113193512,-1.0251686573028564,-0.8691142201423645,1.2314465045928955,0.5476329922676086 +904,2.720055,1380720,7,True,True,True,False,0,624365212,273.2674255371094,483.8564147949219,467.7380676269531,-57.32047653198242,-89.61817169189453,-62.15137481689453,60.54624557495117,0.6683217287063599,-1.025141716003418,-0.86910480260849,1.2314465045928955,0.5475832223892212 +906,2.728062,1380721,7,True,True,True,False,0,624365220,273.2675476074219,483.8565368652344,467.73785400390625,-57.3215446472168,-89.61821746826172,-62.15031814575195,60.546241760253906,0.6683444380760193,-1.025161862373352,-0.8691235780715942,1.2314382791519165,0.5476329922676086 +908,2.736079,1380722,7,True,True,True,False,0,624365228,273.26702880859375,483.8569641113281,467.7377624511719,-57.32307434082031,-89.61822509765625,-62.148719787597656,60.54630661010742,0.6683557629585266,-1.0251753330230713,-0.86910480260849,1.2314382791519165,0.5476164221763611 +911,2.744061,1380723,7,True,True,True,False,0,624365236,273.26690673828125,483.8567199707031,467.73797607421875,-57.31880569458008,-89.61820220947266,-62.15298843383789,60.54630661010742,0.6683217287063599,-1.0251483917236328,-0.8691235780715942,1.2314465045928955,0.5476329922676086 +914,2.752069,1380724,7,True,True,True,False,0,624365244,273.26690673828125,483.8567199707031,467.7378234863281,-57.31998825073242,-89.61825561523438,-62.15180587768555,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +916,2.760072,1380725,7,True,True,True,False,0,624365252,273.26739501953125,483.85638427734375,467.7380676269531,-57.3212776184082,-89.61824035644531,-62.150577545166016,60.54624557495117,0.6683160662651062,-1.0251350402832031,-0.8691142201423645,1.2313971519470215,0.5476495623588562 +920,2.768066,1380726,7,True,True,True,False,0,624365260,273.267578125,483.8564758300781,467.7379150390625,-57.323360443115234,-89.61820983886719,-62.14850616455078,60.546234130859375,0.6683387756347656,-1.0251551866531372,-0.8691142201423645,1.2314218282699585,0.5476164221763611 +922,2.776086,1380727,7,True,True,True,False,0,624365268,273.267333984375,483.8565673828125,467.7378234863281,-57.32600402832031,-89.61821746826172,-62.145835876464844,60.546260833740234,0.668333113193512,-1.025161862373352,-0.8691235780715942,1.2314136028289795,0.5476164221763611 +924,2.784059,1380728,7,True,True,True,False,0,624365276,273.26751708984375,483.85687255859375,467.7372741699219,-57.33460235595703,-89.61827087402344,-62.137237548828125,60.546260833740234,0.668378472328186,-1.025215744972229,-0.8691235780715942,1.2313889265060425,0.5476329922676086 +926,2.792067,1380729,7,True,True,True,False,0,624365284,273.26702880859375,483.8569641113281,467.737548828125,-57.329219818115234,-89.61823272705078,-62.14257049560547,60.54630661010742,0.6683557629585266,-1.0251954793930054,-0.8691235780715942,1.2314218282699585,0.5476164221763611 +930,2.800044,1380730,7,True,True,True,False,0,624365292,273.2674865722656,483.8565673828125,467.7372741699219,-57.33034133911133,-89.6182632446289,-62.14151382446289,60.54624557495117,0.668333113193512,-1.0252292156219482,-0.8691142201423645,1.2314300537109375,0.5476329922676086 +932,2.808053,1380731,7,True,True,True,False,0,624365300,273.26763916015625,483.8565368652344,467.7371826171875,-57.33264923095703,-89.61823272705078,-62.139217376708984,60.546234130859375,0.6683444380760193,-1.0252426862716675,-0.8691142201423645,1.2314465045928955,0.5475997924804688 +934,2.816033,1380732,7,True,True,True,False,0,624365308,273.2673645019531,483.85662841796875,467.7374572753906,-57.329891204833984,-89.61823272705078,-62.14194869995117,60.546260833740234,0.6683387756347656,-1.0252090692520142,-0.8691235780715942,1.2314300537109375,0.5476164221763611 +937,2.824059,1380733,7,True,True,True,False,0,624365316,273.26739501953125,483.8566589355469,467.7378234863281,-57.32087326049805,-89.61823272705078,-62.15096664428711,60.546260833740234,0.6683444380760193,-1.0251686573028564,-0.86910480260849,1.2314382791519165,0.5476329922676086 +940,2.83206,1380734,7,True,True,True,False,0,624365324,273.26690673828125,483.8567199707031,467.7378845214844,-57.3189811706543,-89.61822509765625,-62.152809143066406,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.86910480260849,1.2314465045928955,0.5476329922676086 +942,2.840089,1380735,7,True,True,True,False,0,624365332,273.26727294921875,483.8564453125,467.7381286621094,-57.319400787353516,-89.61820220947266,-62.15243911743164,60.546260833740234,0.6683160662651062,-1.0251282453536987,-0.8691235780715942,1.2314218282699585,0.5476329922676086 +946,2.848082,1380736,7,True,True,True,False,0,624365340,273.2674255371094,483.8562927246094,467.7381896972656,-57.32048416137695,-89.61817932128906,-62.15138244628906,60.546241760253906,0.6683047413825989,-1.0251215696334839,-0.8691142201423645,1.2314218282699585,0.5475997924804688 +948,2.856055,1380737,7,True,True,True,False,0,624365348,273.2674865722656,483.8568115234375,467.73760986328125,-57.32492446899414,-89.61825561523438,-62.146915435791016,60.546260833740234,0.6683671474456787,-1.0251888036727905,-0.8691142201423645,1.2314218282699585,0.5476495623588562 +950,2.864076,1380738,7,True,True,True,False,0,624365356,273.26702880859375,483.85693359375,467.7376403808594,-57.322357177734375,-89.61827087402344,-62.149436950683594,60.54630661010742,0.6683501601219177,-1.0251888036727905,-0.8690953850746155,1.2314300537109375,0.5476495623588562 +954,2.872064,1380739,7,True,True,True,False,0,624365364,273.26690673828125,483.8567199707031,467.7378234863281,-57.32419967651367,-89.61821746826172,-62.14759063720703,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.8691142201423645,1.2314218282699585,0.5476164221763611 +956,2.880054,1380740,7,True,True,True,False,0,624365372,273.2674865722656,483.8565368652344,467.73779296875,-57.32429504394531,-89.61823272705078,-62.14755630493164,60.54624557495117,0.668333113193512,-1.0251686573028564,-0.86910480260849,1.2314218282699585,0.5476164221763611 +958,2.888082,1380741,7,True,True,True,False,0,624365380,273.2676086425781,483.85662841796875,467.7375183105469,-57.323604583740234,-89.6182632446289,-62.14826202392578,60.546241760253906,0.6683557629585266,-1.0252022743225098,-0.869085967540741,1.2314382791519165,0.5476329922676086 +961,2.896082,1380742,7,True,True,True,False,0,624365388,273.26702880859375,483.8569641113281,467.73724365234375,-57.33452224731445,-89.61824798583984,-62.137271881103516,60.54630661010742,0.6683501601219177,-1.0252292156219482,-0.8691235780715942,1.2314136028289795,0.5476164221763611 +964,2.904067,1380743,7,True,True,True,False,0,624365396,273.26739501953125,483.85638427734375,467.7379150390625,-57.319637298583984,-89.61824035644531,-62.1522216796875,60.54624557495117,0.6683160662651062,-1.0251551866531372,-0.8691142201423645,1.2314300537109375,0.5476495623588562 +968,2.912062,1380744,7,True,True,True,False,0,624365404,273.26690673828125,483.8567199707031,467.738037109375,-57.318511962890625,-89.61822509765625,-62.153282165527344,60.54630661010742,0.6683217287063599,-1.025141716003418,-0.86910480260849,1.2314300537109375,0.5476329922676086 +970,2.920084,1380745,7,True,True,True,False,0,624365412,273.26751708984375,483.85675048828125,467.7375793457031,-57.3245849609375,-89.61823272705078,-62.14726257324219,60.5462532043457,0.6683614253997803,-1.0251954793930054,-0.86910480260849,1.2314465045928955,0.5476164221763611 +975,2.92802,1380746,7,True,True,True,False,0,624365420,273.2676696777344,483.856689453125,467.7377014160156,-57.31972122192383,-89.61824035644531,-62.15214157104492,60.546241760253906,0.6683671474456787,-1.0251820087432861,-0.8690953850746155,1.231454849243164,0.5476329922676086 +978,2.936019,1380747,7,True,True,True,False,0,624365428,273.2669677734375,483.8568420410156,467.7379150390625,-57.31720733642578,-89.61819458007812,-62.15458679199219,60.54630661010742,0.6683444380760193,-1.025161862373352,-0.86910480260849,1.231471300125122,0.5476164221763611 +982,2.944082,1380748,7,True,True,True,False,0,624365436,273.2668762207031,483.8566589355469,467.73797607421875,-57.31728744506836,-89.61821746826172,-62.154502868652344,60.54630661010742,0.6683160662651062,-1.0251483917236328,-0.86910480260849,1.2314465045928955,0.5476329922676086 +986,2.952076,1380749,7,True,True,True,False,0,624365444,273.2674255371094,483.856689453125,467.7376403808594,-57.32297134399414,-89.61824035644531,-62.148868560791016,60.546260833740234,0.6683501601219177,-1.0251888036727905,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +988,2.960064,1380750,7,True,True,True,False,0,624365452,273.2676696777344,483.8565979003906,467.7375793457031,-57.32295227050781,-89.61823272705078,-62.14891815185547,60.546234130859375,0.6683557629585266,-1.0251954793930054,-0.8691142201423645,1.231454849243164,0.5476329922676086 +990,2.968079,1380751,7,True,True,True,False,0,624365460,273.2676086425781,483.85675048828125,467.737548828125,-57.3236198425293,-89.61824035644531,-62.14823532104492,60.54624557495117,0.6683671474456787,-1.0252022743225098,-0.8690953850746155,1.231454849243164,0.5476164221763611 +993,2.976063,1380752,7,True,True,True,False,0,624365468,273.2669372558594,483.8568115234375,467.7378845214844,-57.32004165649414,-89.61819458007812,-62.15175247192383,60.54630661010742,0.668333113193512,-1.025161862373352,-0.8691142201423645,1.231454849243164,0.5476164221763611 +996,2.984053,1380753,7,True,True,True,False,0,624365476,273.2672119140625,483.8563232421875,467.7381591796875,-57.31172180175781,-89.61820220947266,-62.160118103027344,60.546260833740234,0.6682990789413452,-1.0251350402832031,-0.8690953850746155,1.231471300125122,0.5476329922676086 +998,2.992102,1380754,7,True,True,True,False,0,624365484,273.2674865722656,483.8565673828125,467.7377014160156,-57.31896209716797,-89.61824798583984,-62.15289306640625,60.54624557495117,0.6683387756347656,-1.0251820087432861,-0.869085967540741,1.231454849243164,0.5476329922676086 +1002,3.000067,1380755,7,True,True,True,False,0,624365492,273.267333984375,483.8565368652344,467.7378845214844,-57.3170166015625,-89.61819458007812,-62.154823303222656,60.546260833740234,0.6683273911476135,-1.0251686573028564,-0.86910480260849,1.231479525566101,0.5476164221763611 +1004,3.008057,1380756,7,True,True,True,False,0,624365500,273.2676086425781,483.85650634765625,467.73773193359375,-57.31898880004883,-89.61821746826172,-62.15287780761719,60.546234130859375,0.6683444380760193,-1.0251820087432861,-0.8690953850746155,1.231471300125122,0.5476164221763611 +1006,3.016102,1380757,7,True,True,True,False,0,624365508,273.2668762207031,483.856689453125,467.7376708984375,-57.31849670410156,-89.61824035644531,-62.15329360961914,60.54630661010742,0.6683160662651062,-1.0251888036727905,-0.8690953850746155,1.231471300125122,0.5476329922676086 +1010,3.024056,1380758,7,True,True,True,False,0,624365516,273.2674255371094,483.8564453125,467.73785400390625,-57.3172607421875,-89.61824035644531,-62.15459060668945,60.54624557495117,0.6683217287063599,-1.0251686573028564,-0.869085967540741,1.231454849243164,0.5476329922676086 +1012,3.032076,1380759,7,True,True,True,False,0,624365524,273.267333984375,483.8565368652344,467.73785400390625,-57.313838958740234,-89.61824035644531,-62.15800094604492,60.546260833740234,0.6683273911476135,-1.0251686573028564,-0.869085967540741,1.231471300125122,0.5476495623588562 +1014,3.04003,1380760,7,True,True,True,False,0,624365532,273.2673645019531,483.8564453125,467.7379455566406,-57.31269836425781,-89.6182632446289,-62.159156799316406,60.5462532043457,0.6683217287063599,-1.0251551866531372,-0.8690671920776367,1.231454849243164,0.5476495623588562 +1016,3.048111,1380761,7,True,True,True,False,0,624365540,273.26763916015625,483.8565673828125,467.73785400390625,-57.319366455078125,-89.61821746826172,-62.152503967285156,60.546234130859375,0.6683501601219177,-1.0251686573028564,-0.8690953850746155,1.231454849243164,0.5476164221763611 +1020,3.056064,1380762,7,True,True,True,False,0,624365548,273.2674255371094,483.8564453125,467.73785400390625,-57.318782806396484,-89.61822509765625,-62.15306854248047,60.54624557495117,0.6683217287063599,-1.0251686573028564,-0.86910480260849,1.231454849243164,0.5476329922676086 +1022,3.064075,1380763,7,True,True,True,False,0,624365556,273.2674865722656,483.8565673828125,467.7377624511719,-57.317527770996094,-89.61824798583984,-62.154327392578125,60.54624557495117,0.6683387756347656,-1.0251753330230713,-0.8690953850746155,1.231454849243164,0.5476495623588562 +1024,3.072089,1380764,7,True,True,True,False,0,624365564,273.2673645019531,483.8563537597656,467.73809814453125,-57.31407165527344,-89.61819458007812,-62.15778350830078,60.54624557495117,0.6683104038238525,-1.025141716003418,-0.8691142201423645,1.231471300125122,0.5476329922676086 +1027,3.080063,1380765,7,True,True,True,False,0,624365572,273.26751708984375,483.8563537597656,467.73797607421875,-57.31558609008789,-89.61820220947266,-62.156280517578125,60.546234130859375,0.6683217287063599,-1.0251551866531372,-0.8690953850746155,1.231471300125122,0.5476164221763611 +1030,3.088073,1380766,7,True,True,True,False,0,624365580,273.2677001953125,483.8567810058594,467.7373962402344,-57.32267761230469,-89.61824035644531,-62.14918899536133,60.546241760253906,0.668378472328186,-1.025215744972229,-0.86910480260849,1.231471300125122,0.5476329922676086 +1032,3.096091,1380767,7,True,True,True,False,0,624365588,273.2675476074219,483.85662841796875,467.7376708984375,-57.32057189941406,-89.61824035644531,-62.15127944946289,60.54624557495117,0.6683501601219177,-1.0251888036727905,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1036,3.104066,1380768,7,True,True,True,False,0,624365596,273.2674560546875,483.8563232421875,467.7377014160156,-57.31943130493164,-89.61825561523438,-62.152427673339844,60.546241760253906,0.6683104038238525,-1.0251820087432861,-0.8690953850746155,1.2314465045928955,0.5476495623588562 +1038,3.11206,1380769,7,True,True,True,False,0,624365604,273.267333984375,483.85626220703125,467.7380065917969,-57.318790435791016,-89.61824035644531,-62.15306091308594,60.54624557495117,0.6682990789413452,-1.025141716003418,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +1040,3.120089,1380770,7,True,True,True,False,0,624365612,273.26739501953125,483.85626220703125,467.737548828125,-57.326351165771484,-89.61825561523438,-62.1455078125,60.546241760253906,0.6682990789413452,-1.0251954793930054,-0.86910480260849,1.2314218282699585,0.5476329922676086 +1043,3.128053,1380771,7,True,True,True,False,0,624365620,273.2674865722656,483.8565673828125,467.7375793457031,-57.32381820678711,-89.61824035644531,-62.14803695678711,60.54624557495117,0.6683387756347656,-1.0251954793930054,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1046,3.136066,1380772,7,True,True,True,False,0,624365628,273.2673034667969,483.85650634765625,467.737548828125,-57.32466506958008,-89.61824035644531,-62.14717483520508,60.546260833740234,0.6683217287063599,-1.0252022743225098,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1048,3.144087,1380773,7,True,True,True,False,0,624365636,273.2673034667969,483.85650634765625,467.7378234863281,-57.31907272338867,-89.61824035644531,-62.152767181396484,60.546260833740234,0.6683217287063599,-1.0251686573028564,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1052,3.152058,1380774,7,True,True,True,False,0,624365644,273.2674865722656,483.8565368652344,467.7378234863281,-57.31907272338867,-89.61824035644531,-62.15278244018555,60.54624557495117,0.668333113193512,-1.0251686573028564,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1054,3.160081,1380775,7,True,True,True,False,0,624365652,273.2674865722656,483.85638427734375,467.7378845214844,-57.3195686340332,-89.61821746826172,-62.15229797363281,60.546241760253906,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1056,3.168068,1380776,7,True,True,True,False,0,624365660,273.2674865722656,483.8562927246094,467.7377624511719,-57.318115234375,-89.61824035644531,-62.153751373291016,60.546234130859375,0.6683104038238525,-1.0251753330230713,-0.869085967540741,1.231454849243164,0.5476329922676086 +1058,3.176083,1380777,7,True,True,True,False,0,624365668,273.2672119140625,483.8563232421875,467.7380065917969,-57.31471252441406,-89.61823272705078,-62.157127380371094,60.546260833740234,0.6682990789413452,-1.0251483917236328,-0.869085967540741,1.231454849243164,0.5476329922676086 +1062,3.184113,1380778,7,True,True,True,False,0,624365676,273.2668762207031,483.856689453125,467.7377014160156,-57.32106399536133,-89.61822509765625,-62.150726318359375,60.54630661010742,0.6683160662651062,-1.0251820087432861,-0.8690953850746155,1.231454849243164,0.5476164221763611 +1064,3.192066,1380779,7,True,True,True,False,0,624365684,273.266845703125,483.85662841796875,467.7374572753906,-57.322669982910156,-89.61827087402344,-62.14912033081055,60.54630661010742,0.6683047413825989,-1.0252090692520142,-0.8690766096115112,1.2314465045928955,0.5476329922676086 +1066,3.200087,1380780,7,True,True,True,False,0,624365692,273.2674865722656,483.8562927246094,467.7376708984375,-57.32057189941406,-89.61824035644531,-62.15129470825195,60.546234130859375,0.6683104038238525,-1.0251888036727905,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1070,3.208065,1380781,7,True,True,True,False,0,624365700,273.26739501953125,483.85638427734375,467.7375793457031,-57.321537017822266,-89.6182632446289,-62.15031814575195,60.54624557495117,0.6683160662651062,-1.0251954793930054,-0.8690671920776367,1.2314465045928955,0.5476164221763611 +1072,3.216064,1380782,7,True,True,True,False,0,624365708,273.26702880859375,483.85693359375,467.7376708984375,-57.31981658935547,-89.61824798583984,-62.15198516845703,60.54630661010742,0.6683501601219177,-1.0251888036727905,-0.869085967540741,1.231454849243164,0.5476329922676086 +1074,3.224088,1380783,7,True,True,True,False,0,624365716,273.26727294921875,483.8564758300781,467.7376403808594,-57.315162658691406,-89.61824798583984,-62.15667724609375,60.546260833740234,0.6683160662651062,-1.0251954793930054,-0.869085967540741,1.23148775100708,0.5476495623588562 +1077,3.232066,1380784,7,True,True,True,False,0,624365724,273.2668151855469,483.8565979003906,467.73779296875,-57.314693450927734,-89.61824798583984,-62.157100677490234,60.54630661010742,0.6682990789413452,-1.0251753330230713,-0.869085967540741,1.231471300125122,0.5476495623588562 +1080,3.240074,1380785,7,True,True,True,False,0,624365732,273.26739501953125,483.85638427734375,467.73748779296875,-57.32600402832031,-89.61822509765625,-62.145851135253906,60.54624557495117,0.6683104038238525,-1.0252090692520142,-0.8691142201423645,1.231454849243164,0.5476164221763611 +1082,3.248085,1380786,7,True,True,True,False,0,624365740,273.2676086425781,483.8563232421875,467.7380676269531,-57.314910888671875,-89.61823272705078,-62.15696334838867,60.546226501464844,0.6683217287063599,-1.025141716003418,-0.869085967540741,1.2314465045928955,0.5476329922676086 +1086,3.256083,1380787,7,True,True,True,False,0,624365748,273.26727294921875,483.8564453125,467.7379455566406,-57.31767272949219,-89.61820983886719,-62.15416717529297,60.546260833740234,0.6683160662651062,-1.0251551866531372,-0.8690953850746155,1.231454849243164,0.5476164221763611 +1088,3.264051,1380788,7,True,True,True,False,0,624365756,273.2673645019531,483.8563537597656,467.7377014160156,-57.32095718383789,-89.61824035644531,-62.15089797973633,60.54624557495117,0.6683047413825989,-1.0251820087432861,-0.8691142201423645,1.2314465045928955,0.5476495623588562 +1090,3.272106,1380789,7,True,True,True,False,0,624365764,273.2674255371094,483.8563232421875,467.7380065917969,-57.3177604675293,-89.61819458007812,-62.15410232543945,60.546241760253906,0.6683104038238525,-1.0251483917236328,-0.8691235780715942,1.231454849243164,0.5476329922676086 +1094,3.28007,1380790,7,True,True,True,False,0,624365772,273.267578125,483.8565673828125,467.73748779296875,-57.32448196411133,-89.61824035644531,-62.14738082885742,60.546241760253906,0.6683444380760193,-1.0252090692520142,-0.8690953850746155,1.231454849243164,0.5476164221763611 +1096,3.288065,1380791,7,True,True,True,False,0,624365780,273.26751708984375,483.8563232421875,467.7374267578125,-57.3219108581543,-89.61824798583984,-62.14995574951172,60.546234130859375,0.6683160662651062,-1.025215744972229,-0.8690953850746155,1.231471300125122,0.5476329922676086 +1098,3.296076,1380792,7,True,True,True,False,0,624365788,273.267333984375,483.8564147949219,467.7376403808594,-57.32315444946289,-89.61823272705078,-62.14869689941406,60.5462532043457,0.6683104038238525,-1.0251888036727905,-0.8691142201423645,1.2314465045928955,0.5476329922676086 +1100,3.304084,1380793,7,True,True,True,False,0,624365796,273.26739501953125,483.8562316894531,467.738037109375,-57.319583892822266,-89.61819458007812,-62.15227508544922,60.546241760253906,0.6682990789413452,-1.025141716003418,-0.8691142201423645,1.2314382791519165,0.5476164221763611 +1103,3.31208,1380794,7,True,True,True,False,0,624365804,273.2673645019531,483.8563232421875,467.73797607421875,-57.3177375793457,-89.61823272705078,-62.15411376953125,60.54624557495117,0.6683047413825989,-1.0251483917236328,-0.8691142201423645,1.2314382791519165,0.5476495623588562 +1106,3.320074,1380795,7,True,True,True,False,0,624365812,273.2674255371094,483.8562927246094,467.7377014160156,-57.320777893066406,-89.61824035644531,-62.15108871459961,60.546241760253906,0.6683047413825989,-1.0251820087432861,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1108,3.328068,1380796,7,True,True,True,False,0,624365820,273.26751708984375,483.8563537597656,467.7378845214844,-57.3222541809082,-89.6181869506836,-62.14961242675781,60.546234130859375,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2314465045928955,0.5475832223892212 +1112,3.336085,1380797,7,True,True,True,False,0,624365828,273.2672119140625,483.8563232421875,467.73809814453125,-57.3153076171875,-89.61823272705078,-62.156532287597656,60.546260833740234,0.6682990789413452,-1.0251350402832031,-0.86910480260849,1.2314382791519165,0.5476495623588562 +1114,3.344084,1380798,7,True,True,True,False,0,624365836,273.26739501953125,483.8563537597656,467.7374572753906,-57.324466705322266,-89.61827087402344,-62.14738464355469,60.54624557495117,0.6683104038238525,-1.0252090692520142,-0.869085967540741,1.2314382791519165,0.5476329922676086 +1116,3.352084,1380799,7,True,True,True,False,0,624365844,273.2672119140625,483.8563537597656,467.7377624511719,-57.32203674316406,-89.61821746826172,-62.149803161621094,60.546260833740234,0.6682990789413452,-1.0251753330230713,-0.86910480260849,1.2314465045928955,0.5476164221763611 +1120,3.360073,1380800,7,True,True,True,False,0,624365852,273.2674865722656,483.8562927246094,467.7379455566406,-57.3173713684082,-89.61823272705078,-62.15449523925781,60.546234130859375,0.6683160662651062,-1.0251551866531372,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1122,3.368067,1380801,7,True,True,True,False,0,624365860,273.2676086425781,483.8564758300781,467.7377624511719,-57.32230758666992,-89.61823272705078,-62.149559020996094,60.546234130859375,0.6683387756347656,-1.0251753330230713,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +1124,3.376091,1380802,7,True,True,True,False,0,624365868,273.2673034667969,483.85650634765625,467.7380065917969,-57.32089614868164,-89.61821746826172,-62.150943756103516,60.546260833740234,0.6683217287063599,-1.025141716003418,-0.86910480260849,1.2314218282699585,0.5476164221763611 +1127,3.384079,1380803,7,True,True,True,False,0,624365876,273.26727294921875,483.8564453125,467.7377014160156,-57.319610595703125,-89.61825561523438,-62.15222930908203,60.546260833740234,0.6683160662651062,-1.0251820087432861,-0.8691142201423645,1.2314465045928955,0.5476661920547485 +1130,3.392079,1380804,7,True,True,True,False,0,624365884,273.2667541503906,483.8564453125,467.7378845214844,-57.31821823120117,-89.61823272705078,-62.1535758972168,60.54630661010742,0.6682820320129395,-1.025161862373352,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1132,3.400087,1380805,7,True,True,True,False,0,624365892,273.26739501953125,483.8562316894531,467.7380676269531,-57.31462478637695,-89.61821746826172,-62.15723419189453,60.546241760253906,0.6682990789413452,-1.025141716003418,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1136,3.408068,1380806,7,True,True,True,False,0,624365900,273.2674865722656,483.85626220703125,467.7377014160156,-57.32229995727539,-89.61823272705078,-62.149566650390625,60.546234130859375,0.6683047413825989,-1.0251820087432861,-0.8691142201423645,1.2314465045928955,0.5476329922676086 +1138,3.416074,1380807,7,True,True,True,False,0,624365908,273.2675476074219,483.8566589355469,467.7375793457031,-57.3245849609375,-89.61823272705078,-62.14727020263672,60.54624557495117,0.6683557629585266,-1.0251954793930054,-0.86910480260849,1.2314465045928955,0.5476164221763611 +1140,3.424074,1380808,7,True,True,True,False,0,624365916,273.267333984375,483.8564147949219,467.7381286621094,-57.316673278808594,-89.61817932128906,-62.155174255371094,60.5462532043457,0.6683160662651062,-1.0251350402832031,-0.8691142201423645,1.231454849243164,0.5476164221763611 +1142,3.432081,1380809,7,True,True,True,False,0,624365924,273.266845703125,483.85662841796875,467.73809814453125,-57.31454086303711,-89.61824035644531,-62.157249450683594,60.54630661010742,0.6683047413825989,-1.0251350402832031,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1146,3.440088,1380810,7,True,True,True,False,0,624365932,273.2671813964844,483.85601806640625,467.73834228515625,-57.31403732299805,-89.6181640625,-62.15781784057617,60.54624557495117,0.6682650446891785,-1.0251080989837646,-0.8691235780715942,1.231454849243164,0.5476164221763611 +1148,3.448072,1380811,7,True,True,True,False,0,624365940,273.26763916015625,483.85638427734375,467.7372131347656,-57.32761001586914,-89.61827087402344,-62.14427185058594,60.546226501464844,0.6683217287063599,-1.0252360105514526,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1150,3.456107,1380812,7,True,True,True,False,0,624365948,273.2672424316406,483.8563537597656,467.7373962402344,-57.327152252197266,-89.61824035644531,-62.14469528198242,60.546260833740234,0.6682990789413452,-1.025215744972229,-0.86910480260849,1.2314465045928955,0.5476164221763611 +1153,3.464067,1380813,7,True,True,True,False,0,624365956,273.2672424316406,483.8564147949219,467.737548828125,-57.32484817504883,-89.61824035644531,-62.14699172973633,60.546260833740234,0.6683047413825989,-1.0252022743225098,-0.8691142201423645,1.2314465045928955,0.5476329922676086 +1156,3.472079,1380814,7,True,True,True,False,0,624365964,273.2668762207031,483.856689453125,467.7375793457031,-57.32353210449219,-89.61822509765625,-62.14826202392578,60.54630661010742,0.6683160662651062,-1.0251954793930054,-0.86910480260849,1.231454849243164,0.5476164221763611 +1158,3.480086,1380815,7,True,True,True,False,0,624365972,273.2673034667969,483.85650634765625,467.7378234863281,-57.32011032104492,-89.61824035644531,-62.151729583740234,60.546260833740234,0.6683217287063599,-1.0251686573028564,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1162,3.488113,1380816,7,True,True,True,False,0,624365980,273.26763916015625,483.8564147949219,467.73785400390625,-57.32329177856445,-89.61819458007812,-62.148582458496094,60.546226501464844,0.668333113193512,-1.025161862373352,-0.8690953850746155,1.2314382791519165,0.5475832223892212 +1164,3.49606,1380817,7,True,True,True,False,0,624365988,273.26727294921875,483.8564453125,467.7379150390625,-57.32200622558594,-89.61823272705078,-62.14982986450195,60.546260833740234,0.6683160662651062,-1.0251551866531372,-0.8691142201423645,1.2314218282699585,0.5476329922676086 +1166,3.504083,1380818,7,True,True,True,False,0,624365996,273.2666015625,483.856201171875,467.73858642578125,-57.31013870239258,-89.61819458007812,-62.16165542602539,60.54630661010742,0.6682479381561279,-1.0250811576843262,-0.8690953850746155,1.2314300537109375,0.5476329922676086 +1170,3.512072,1380819,7,True,True,True,False,0,624366004,273.2667236328125,483.8564147949219,467.73773193359375,-57.32200241088867,-89.61824798583984,-62.1497917175293,60.54630661010742,0.6682707071304321,-1.0251753330230713,-0.8690953850746155,1.2314300537109375,0.5476329922676086 +1172,3.520069,1380820,7,True,True,True,False,0,624366012,273.26739501953125,483.85638427734375,467.7374572753906,-57.323429107666016,-89.6182632446289,-62.1484260559082,60.54624557495117,0.6683104038238525,-1.0252090692520142,-0.869085967540741,1.2314465045928955,0.5476329922676086 +1174,3.52808,1380821,7,True,True,True,False,0,624366020,273.2675476074219,483.8564147949219,467.7373352050781,-57.32854461669922,-89.61827087402344,-62.1433219909668,60.546234130859375,0.6683217287063599,-1.0252225399017334,-0.869085967540741,1.2314300537109375,0.5476164221763611 +1177,3.536075,1380822,7,True,True,True,False,0,624366028,273.26727294921875,483.8564453125,467.73736572265625,-57.327388763427734,-89.6182861328125,-62.14445114135742,60.546260833740234,0.6683104038238525,-1.025215744972229,-0.869085967540741,1.2314218282699585,0.5476329922676086 +1180,3.544078,1380823,7,True,True,True,False,0,624366036,273.2672119140625,483.8563232421875,467.7374267578125,-57.328033447265625,-89.61829376220703,-62.14380645751953,60.546260833740234,0.6682933568954468,-1.0252090692520142,-0.8690953850746155,1.2314053773880005,0.5476495623588562 +1182,3.552079,1380824,7,True,True,True,False,0,624366044,273.2673034667969,483.8564758300781,467.7371520996094,-57.33355712890625,-89.6182861328125,-62.138282775878906,60.546260833740234,0.6683160662651062,-1.0252360105514526,-0.86910480260849,1.2314053773880005,0.5476329922676086 +1184,3.560086,1380825,7,True,True,True,False,0,624366052,273.266845703125,483.85662841796875,467.7375793457031,-57.32948684692383,-89.61827087402344,-62.142303466796875,60.54630661010742,0.6683047413825989,-1.0251888036727905,-0.869085967540741,1.2313889265060425,0.5476164221763611 +1188,3.568073,1380826,7,True,True,True,False,0,624366060,273.2674865722656,483.85626220703125,467.7376403808594,-57.32672882080078,-89.61825561523438,-62.145137786865234,60.546234130859375,0.6683047413825989,-1.0251820087432861,-0.86910480260849,1.2314053773880005,0.5476329922676086 +1191,3.576071,1380827,7,True,True,True,False,0,624366068,273.2668762207031,483.8566589355469,467.7373352050781,-57.330806732177734,-89.61827850341797,-62.140987396240234,60.54630661010742,0.6683104038238525,-1.025215744972229,-0.869085967540741,1.2314053773880005,0.5476164221763611 +1193,3.584092,1380828,7,True,True,True,False,0,624366076,273.26727294921875,483.8564453125,467.7373352050781,-57.3282356262207,-89.6182861328125,-62.14360427856445,60.546260833740234,0.6683047413825989,-1.0252225399017334,-0.869085967540741,1.2314218282699585,0.5476329922676086 +1197,3.592088,1380829,7,True,True,True,False,0,624366084,273.2672119140625,483.8563232421875,467.7374572753906,-57.32476806640625,-89.61824798583984,-62.147071838378906,60.546260833740234,0.6682933568954468,-1.0252090692520142,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1199,3.600068,1380830,7,True,True,True,False,0,624366092,273.2668151855469,483.8565979003906,467.7374572753906,-57.325408935546875,-89.6182632446289,-62.14638137817383,60.54630661010742,0.6682990789413452,-1.0252090692520142,-0.8691142201423645,1.2314382791519165,0.5476495623588562 +1201,3.608102,1380831,7,True,True,True,False,0,624366100,273.26763916015625,483.85638427734375,467.7373046875,-57.32568359375,-89.6182632446289,-62.14619064331055,60.546226501464844,0.6683217287063599,-1.0252292156219482,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1204,3.61607,1380832,7,True,True,True,False,0,624366108,273.2667236328125,483.85638427734375,467.73797607421875,-57.31755447387695,-89.61823272705078,-62.15423583984375,60.54630661010742,0.6682707071304321,-1.0251483917236328,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1207,3.624078,1380833,7,True,True,True,False,0,624366116,273.2671203613281,483.8561706542969,467.7378234863281,-57.3219108581543,-89.61824035644531,-62.14992904663086,60.546260833740234,0.6682707071304321,-1.0251686573028564,-0.86910480260849,1.2314300537109375,0.5476329922676086 +1209,3.6321,1380834,7,True,True,True,False,0,624366124,273.266845703125,483.85662841796875,467.7379150390625,-57.32183074951172,-89.61823272705078,-62.14996337890625,60.54630661010742,0.6683104038238525,-1.0251551866531372,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +1212,3.640079,1380835,7,True,True,True,False,0,624366132,273.2673645019531,483.8564453125,467.7378845214844,-57.31687927246094,-89.61824035644531,-62.15497589111328,60.5462532043457,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2314465045928955,0.5476495623588562 +1215,3.648081,1380836,7,True,True,True,False,0,624366140,273.2675476074219,483.85638427734375,467.7375793457031,-57.32275390625,-89.6182632446289,-62.149112701416016,60.546234130859375,0.6683217287063599,-1.0251954793930054,-0.869085967540741,1.2314382791519165,0.5476329922676086 +1217,3.656094,1380837,7,True,True,True,False,0,624366148,273.2673645019531,483.85662841796875,467.7373352050781,-57.32571792602539,-89.61824035644531,-62.146121978759766,60.546260833740234,0.668333113193512,-1.0252292156219482,-0.86910480260849,1.231471300125122,0.5476164221763611 +1221,3.664102,1380838,7,True,True,True,False,0,624366156,273.26727294921875,483.8564758300781,467.7374267578125,-57.32282257080078,-89.61827087402344,-62.149017333984375,60.546260833740234,0.6683160662651062,-1.025215744972229,-0.8691142201423645,1.231454849243164,0.5476661920547485 +1223,3.672067,1380839,7,True,True,True,False,0,624366164,273.2674255371094,483.8564147949219,467.73736572265625,-57.32770538330078,-89.61823272705078,-62.14414978027344,60.54624557495117,0.6683160662651062,-1.0252225399017334,-0.8691142201423645,1.231454849243164,0.5476164221763611 +1225,3.680089,1380840,7,True,True,True,False,0,624366172,273.267333984375,483.85638427734375,467.7376708984375,-57.32392120361328,-89.61822509765625,-62.147926330566406,60.5462532043457,0.6683047413825989,-1.0251820087432861,-0.86910480260849,1.2314382791519165,0.5476164221763611 +1227,3.688092,1380841,7,True,True,True,False,0,624366180,273.2673645019531,483.856201171875,467.73828125,-57.314964294433594,-89.61820983886719,-62.15689468383789,60.546241760253906,0.6682990789413452,-1.0251147747039795,-0.869085967540741,1.2314300537109375,0.5476164221763611 +1231,3.69609,1380842,7,True,True,True,False,0,624366188,273.2668151855469,483.8565673828125,467.73809814453125,-57.31720733642578,-89.61824035644531,-62.15458679199219,60.54630661010742,0.6682990789413452,-1.0251350402832031,-0.869085967540741,1.2314218282699585,0.5476329922676086 +1233,3.70408,1380843,7,True,True,True,False,0,624366196,273.2671203613281,483.85614013671875,467.7384338378906,-57.311378479003906,-89.61819458007812,-62.16046142578125,60.546260833740234,0.668276309967041,-1.0250946283340454,-0.869085967540741,1.2314382791519165,0.5476164221763611 +1235,3.712096,1380844,7,True,True,True,False,0,624366204,273.266845703125,483.85662841796875,467.7381896972656,-57.310848236083984,-89.61823272705078,-62.160945892333984,60.54630661010742,0.6683104038238525,-1.0251282453536987,-0.869085967540741,1.231454849243164,0.5476495623588562 +1238,3.720064,1380845,7,True,True,True,False,0,624366212,273.2674255371094,483.8564453125,467.737548828125,-57.324092864990234,-89.61824035644531,-62.147762298583984,60.54624557495117,0.6683217287063599,-1.0252022743225098,-0.86910480260849,1.2314465045928955,0.5476329922676086 +1241,3.728084,1380846,7,True,True,True,False,0,624366220,273.26739501953125,483.85626220703125,467.7378234863281,-57.3180046081543,-89.6182632446289,-62.15386199951172,60.546241760253906,0.6682990789413452,-1.0251686573028564,-0.869085967540741,1.2314382791519165,0.5476495623588562 +1243,3.736102,1380847,7,True,True,True,False,0,624366228,273.26702880859375,483.85601806640625,467.73846435546875,-57.30776596069336,-89.61820220947266,-62.1640739440918,60.546260833740234,0.6682536602020264,-1.0250946283340454,-0.8690671920776367,1.231454849243164,0.5476164221763611 +1247,3.744074,1380848,7,True,True,True,False,0,624366236,273.26715087890625,483.856201171875,467.7383728027344,-57.30411148071289,-89.61824035644531,-62.167728424072266,60.546260833740234,0.6682820320129395,-1.0251080989837646,-0.8690766096115112,1.231471300125122,0.5476661920547485 +1249,3.752089,1380849,7,True,True,True,False,0,624366244,273.266845703125,483.85662841796875,467.73797607421875,-57.315757751464844,-89.61824035644531,-62.15603256225586,60.54630661010742,0.6683104038238525,-1.0251483917236328,-0.869085967540741,1.2314465045928955,0.5476329922676086 +1251,3.760089,1380850,7,True,True,True,False,0,624366252,273.2674255371094,483.85614013671875,467.7378234863281,-57.32041549682617,-89.61822509765625,-62.151451110839844,60.546234130859375,0.6682876944541931,-1.0251686573028564,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1255,3.768091,1380851,7,True,True,True,False,0,624366260,273.267333984375,483.8559875488281,467.7378845214844,-57.317935943603516,-89.61822509765625,-62.1539306640625,60.546234130859375,0.6682650446891785,-1.025161862373352,-0.86910480260849,1.231454849243164,0.5476329922676086 +1257,3.776125,1380852,7,True,True,True,False,0,624366268,273.266845703125,483.85662841796875,467.7374572753906,-57.32284164428711,-89.61827087402344,-62.14895248413086,60.54630661010742,0.6683047413825989,-1.0252090692520142,-0.8690953850746155,1.2314465045928955,0.5476495623588562 +1259,3.784101,1380853,7,True,True,True,False,0,624366276,273.2669677734375,483.8568420410156,467.7374572753906,-57.326255798339844,-89.6182632446289,-62.14553451538086,60.54630661010742,0.668333113193512,-1.0252090692520142,-0.8690953850746155,1.2314300537109375,0.5476329922676086 +1262,3.792089,1380854,7,True,True,True,False,0,624366284,273.2668151855469,483.8565979003906,467.73773193359375,-57.32514953613281,-89.61823272705078,-62.14664077758789,60.54630661010742,0.6682990789413452,-1.0251753330230713,-0.86910480260849,1.2314218282699585,0.5476164221763611 +1265,3.800081,1380855,7,True,True,True,False,0,624366292,273.26727294921875,483.8564453125,467.7378845214844,-57.32543182373047,-89.61822509765625,-62.14640808105469,60.546260833740234,0.6683160662651062,-1.0251551866531372,-0.8691142201423645,1.2314053773880005,0.5476164221763611 +1267,3.808102,1380856,7,True,True,True,False,0,624366300,273.2674560546875,483.8563537597656,467.7378234863281,-57.32057189941406,-89.61825561523438,-62.15128707885742,60.546241760253906,0.6683160662651062,-1.025161862373352,-0.869085967540741,1.2314218282699585,0.5476329922676086 +1269,3.816076,1380857,7,True,True,True,False,0,624366308,273.266845703125,483.85662841796875,467.7377014160156,-57.32001495361328,-89.61824798583984,-62.15177917480469,60.54630661010742,0.6683047413825989,-1.0251820087432861,-0.869085967540741,1.2314465045928955,0.5476329922676086 +1273,3.824117,1380858,7,True,True,True,False,0,624366316,273.2673034667969,483.85650634765625,467.7378234863281,-57.31934356689453,-89.61824798583984,-62.152496337890625,60.546260833740234,0.6683217287063599,-1.0251686573028564,-0.869085967540741,1.2314382791519165,0.5476329922676086 +1275,3.832093,1380859,7,True,True,True,False,0,624366324,273.2668151855469,483.8565673828125,467.7380676269531,-57.31520462036133,-89.61820983886719,-62.156585693359375,60.54630661010742,0.6682990789413452,-1.025141716003418,-0.869085967540741,1.231454849243164,0.5476164221763611 +1277,3.840074,1380860,7,True,True,True,False,0,624366332,273.2671813964844,483.85601806640625,467.73834228515625,-57.3111686706543,-89.6181869506836,-62.16068649291992,60.54624557495117,0.6682650446891785,-1.0251080989837646,-0.86910480260849,1.231454849243164,0.5476329922676086 +1281,3.848089,1380861,7,True,True,True,False,0,624366340,273.2674865722656,483.85626220703125,467.7379150390625,-57.321250915527344,-89.61824035644531,-62.15061569213867,60.546234130859375,0.6683104038238525,-1.0251551866531372,-0.86910480260849,1.2314218282699585,0.5476329922676086 +1283,3.856082,1380862,7,True,True,True,False,0,624366348,273.26678466796875,483.8564758300781,467.7379455566406,-57.31840896606445,-89.61824035644531,-62.153385162353516,60.54630661010742,0.6682876944541931,-1.0251551866531372,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1285,3.864109,1380863,7,True,True,True,False,0,624366356,273.2668762207031,483.8566589355469,467.7379150390625,-57.322593688964844,-89.61822509765625,-62.14919662475586,60.54630661010742,0.6683160662651062,-1.0251551866531372,-0.86910480260849,1.2314218282699585,0.5476164221763611 +1288,3.872085,1380864,7,True,True,True,False,0,624366364,273.2669372558594,483.8567810058594,467.7374572753906,-57.323429107666016,-89.6182632446289,-62.14836502075195,60.54630661010742,0.6683273911476135,-1.0252090692520142,-0.869085967540741,1.2314465045928955,0.5476329922676086 +1291,3.880085,1380865,7,True,True,True,False,0,624366372,273.2673034667969,483.8563232421875,467.73760986328125,-57.32038879394531,-89.61824035644531,-62.151458740234375,60.5462532043457,0.6682990789413452,-1.0251954793930054,-0.8690953850746155,1.231463074684143,0.5476329922676086 +1293,3.888098,1380866,7,True,True,True,False,0,624366380,273.26739501953125,483.8561096191406,467.73773193359375,-57.316707611083984,-89.61824035644531,-62.15515899658203,60.546234130859375,0.6682820320129395,-1.0251820087432861,-0.8690671920776367,1.231471300125122,0.5476164221763611 +1297,3.896108,1380867,7,True,True,True,False,0,624366388,273.2668762207031,483.856689453125,467.73760986328125,-57.31993103027344,-89.61823272705078,-62.151859283447266,60.54630661010742,0.6683160662651062,-1.0251954793930054,-0.869085967540741,1.231471300125122,0.5476164221763611 +1299,3.904095,1380868,7,True,True,True,False,0,624366396,273.26690673828125,483.85675048828125,467.737548828125,-57.324092864990234,-89.61824035644531,-62.147701263427734,60.54630661010742,0.6683217287063599,-1.0252022743225098,-0.86910480260849,1.2314465045928955,0.5476329922676086 +1301,3.912089,1380869,7,True,True,True,False,0,624366404,273.2672424316406,483.8563537597656,467.73760986328125,-57.31934356689453,-89.61824035644531,-62.152496337890625,60.546260833740234,0.6682990789413452,-1.0251954793930054,-0.8690953850746155,1.231471300125122,0.5476329922676086 +1305,3.920088,1380870,7,True,True,True,False,0,624366412,273.2671203613281,483.85614013671875,467.7383117675781,-57.31336212158203,-89.61817932128906,-62.158477783203125,60.546260833740234,0.668276309967041,-1.0251147747039795,-0.86910480260849,1.231454849243164,0.5476164221763611 +1307,3.928085,1380871,7,True,True,True,False,0,624366420,273.2674255371094,483.8562927246094,467.73809814453125,-57.31930923461914,-89.61821746826172,-62.152549743652344,60.546241760253906,0.6683047413825989,-1.0251350402832031,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +1309,3.936082,1380872,7,True,True,True,False,0,624366428,273.2668151855469,483.8565673828125,467.7381896972656,-57.312950134277344,-89.61820983886719,-62.15884017944336,60.54630661010742,0.6682990789413452,-1.0251282453536987,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1311,3.944101,1380873,7,True,True,True,False,0,624366436,273.266845703125,483.85662841796875,467.7381286621094,-57.314849853515625,-89.61821746826172,-62.15694046020508,60.54630661010742,0.6683047413825989,-1.0251350402832031,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1314,3.952089,1380874,7,True,True,True,False,0,624366444,273.2671813964844,483.85626220703125,467.7382507324219,-57.31344985961914,-89.61819458007812,-62.158390045166016,60.546260833740234,0.6682876944541931,-1.0251215696334839,-0.8690953850746155,1.231454849243164,0.5476164221763611 +1317,3.960108,1380875,7,True,True,True,False,0,624366452,273.2672119140625,483.8562927246094,467.7382507324219,-57.314971923828125,-89.61817169189453,-62.15686798095703,60.546260833740234,0.6682933568954468,-1.0251215696334839,-0.8691142201423645,1.231454849243164,0.5476164221763611 +1319,3.968082,1380876,7,True,True,True,False,0,624366460,273.267333984375,483.85614013671875,467.7383728027344,-57.309364318847656,-89.61819458007812,-62.16249465942383,60.546241760253906,0.6682876944541931,-1.0251080989837646,-0.8690953850746155,1.231463074684143,0.5476329922676086 +1322,3.976081,1380877,7,True,True,True,False,0,624366468,273.2672424316406,483.8564147949219,467.73809814453125,-57.31123733520508,-89.6181869506836,-62.16060256958008,60.546260833740234,0.6683104038238525,-1.025141716003418,-0.86910480260849,1.23148775100708,0.5476329922676086 +1325,3.98408,1380878,7,True,True,True,False,0,624366476,273.2672424316406,483.8564147949219,467.7381896972656,-57.30965042114258,-89.61819458007812,-62.162193298339844,60.546260833740234,0.6683104038238525,-1.0251350402832031,-0.8690953850746155,1.23148775100708,0.5476329922676086 +1327,3.992107,1380879,7,True,True,True,False,0,624366484,273.2666931152344,483.8563537597656,467.73834228515625,-57.31251907348633,-89.61817169189453,-62.159278869628906,60.54630661010742,0.6682707071304321,-1.0251080989837646,-0.86910480260849,1.231454849243164,0.5476164221763611 +1331,4.000093,1380880,7,True,True,True,False,0,624366492,273.267333984375,483.85638427734375,467.73779296875,-57.32008743286133,-89.61827087402344,-62.151763916015625,60.5462532043457,0.6683047413825989,-1.0251686573028564,-0.869085967540741,1.2314218282699585,0.5476495623588562 +1334,4.008073,1380881,7,True,True,True,False,0,624366500,273.26739501953125,483.85638427734375,467.7376708984375,-57.3223991394043,-89.61824035644531,-62.14945602416992,60.54624557495117,0.6683160662651062,-1.0251820087432861,-0.869085967540741,1.2314382791519165,0.5476164221763611 +1336,4.016107,1380882,7,True,True,True,False,0,624366508,273.266845703125,483.85662841796875,467.7379455566406,-57.322021484375,-89.61823272705078,-62.1497688293457,60.54630661010742,0.6683104038238525,-1.0251483917236328,-0.8690953850746155,1.2314136028289795,0.5476164221763611 +1339,4.024081,1380883,7,True,True,True,False,0,624366516,273.26678466796875,483.8564758300781,467.7382507324219,-57.312870025634766,-89.61819458007812,-62.1589241027832,60.54630661010742,0.6682876944541931,-1.0251215696334839,-0.86910480260849,1.231454849243164,0.5476329922676086 +1342,4.032095,1380884,7,True,True,True,False,0,624366524,273.26678466796875,483.8564758300781,467.7381896972656,-57.30952835083008,-89.61821746826172,-62.162261962890625,60.54630661010742,0.6682876944541931,-1.0251282453536987,-0.8690953850746155,1.231471300125122,0.5476495623588562 +1344,4.040099,1380885,7,True,True,True,False,0,624366532,273.26702880859375,483.8559875488281,467.73822021484375,-57.31060028076172,-89.6181869506836,-62.16123962402344,60.546260833740234,0.6682479381561279,-1.0251282453536987,-0.86910480260849,1.231479525566101,0.5476329922676086 +1348,4.048167,1380886,7,True,True,True,False,0,624366540,273.2674560546875,483.8560791015625,467.7378845214844,-57.32109451293945,-89.61820220947266,-62.150779724121094,60.546226501464844,0.6682876944541931,-1.025161862373352,-0.8691142201423645,1.2314465045928955,0.5476164221763611 +1350,4.056089,1380887,7,True,True,True,False,0,624366548,273.2672424316406,483.85638427734375,467.7380676269531,-57.3170166015625,-89.61820983886719,-62.154823303222656,60.546260833740234,0.6683047413825989,-1.025141716003418,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1352,4.064072,1380888,7,True,True,True,False,0,624366556,273.2668762207031,483.8566589355469,467.7381286621094,-57.3145637512207,-89.61820983886719,-62.157230377197266,60.54630661010742,0.6683160662651062,-1.0251350402832031,-0.86910480260849,1.231454849243164,0.5476329922676086 +1354,4.072112,1380889,7,True,True,True,False,0,624366564,273.26678466796875,483.8565368652344,467.73834228515625,-57.30784225463867,-89.61820983886719,-62.16395568847656,60.54630661010742,0.6682990789413452,-1.0251147747039795,-0.8690953850746155,1.231471300125122,0.5476495623588562 +1358,4.080092,1380890,7,True,True,True,False,0,624366572,273.26727294921875,483.85601806640625,467.7384338378906,-57.313079833984375,-89.61817169189453,-62.15877914428711,60.546241760253906,0.6682707071304321,-1.0250946283340454,-0.8691235780715942,1.2314382791519165,0.5476329922676086 +1360,4.088097,1380891,7,True,True,True,False,0,624366580,273.2673645019531,483.8563537597656,467.7378234863281,-57.32038116455078,-89.61824798583984,-62.15147018432617,60.54624557495117,0.6683047413825989,-1.0251686573028564,-0.869085967540741,1.2314300537109375,0.5476329922676086 +1362,4.096108,1380892,7,True,True,True,False,0,624366588,273.2672424316406,483.8562316894531,467.73797607421875,-57.31890869140625,-89.61821746826172,-62.15294647216797,60.5462532043457,0.6682876944541931,-1.0251483917236328,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +1366,4.10409,1380893,7,True,True,True,False,0,624366596,273.2671813964844,483.85626220703125,467.7376403808594,-57.320735931396484,-89.61827087402344,-62.15110397338867,60.546260833740234,0.6682876944541931,-1.0251888036727905,-0.86910480260849,1.2314382791519165,0.5476661920547485 +1368,4.112042,1380894,7,True,True,True,False,0,624366604,273.26739501953125,483.8562316894531,467.7378845214844,-57.314903259277344,-89.61821746826172,-62.15695571899414,60.546241760253906,0.6682933568954468,-1.0251686573028564,-0.8690953850746155,1.231479525566101,0.5476329922676086 +1370,4.120109,1380895,7,True,True,True,False,0,624366612,273.26739501953125,483.8562316894531,467.7381591796875,-57.312808990478516,-89.61817932128906,-62.1590576171875,60.546241760253906,0.6682990789413452,-1.0251350402832031,-0.86910480260849,1.231479525566101,0.5476164221763611 +1373,4.128103,1380896,7,True,True,True,False,0,624366620,273.2673034667969,483.8563537597656,467.73797607421875,-57.315757751464844,-89.61824035644531,-62.156089782714844,60.5462532043457,0.6683047413825989,-1.0251483917236328,-0.869085967540741,1.2314465045928955,0.5476329922676086 +1376,4.136086,1380897,7,True,True,True,False,0,624366628,273.266845703125,483.85662841796875,467.73809814453125,-57.3096809387207,-89.61824035644531,-62.162113189697266,60.54630661010742,0.6683047413825989,-1.025141716003418,-0.8690766096115112,1.231471300125122,0.5476495623588562 +1378,4.144111,1380898,7,True,True,True,False,0,624366636,273.2672424316406,483.85638427734375,467.7381591796875,-57.30857849121094,-89.61822509765625,-62.16326141357422,60.546260833740234,0.6683047413825989,-1.0251350402832031,-0.869085967540741,1.231479525566101,0.5476495623588562 +1382,4.152094,1380899,7,True,True,True,False,0,624366644,273.2667541503906,483.8564453125,467.73828125,-57.31447982788086,-89.61823272705078,-62.157310485839844,60.54630661010742,0.6682820320129395,-1.0251147747039795,-0.8690671920776367,1.2314218282699585,0.5476164221763611 +1384,4.160094,1380900,7,True,True,True,False,0,624366652,273.2672119140625,483.8563232421875,467.7381591796875,-57.31382369995117,-89.61821746826172,-62.158016204833984,60.546260833740234,0.6682990789413452,-1.0251282453536987,-0.8690766096115112,1.2314465045928955,0.5476164221763611 +1386,4.168077,1380901,7,True,True,True,False,0,624366660,273.2677001953125,483.8566589355469,467.7375793457031,-57.322296142578125,-89.61824798583984,-62.14957046508789,60.546234130859375,0.6683614253997803,-1.0251954793930054,-0.8690766096115112,1.2314465045928955,0.5476164221763611 +1389,4.176075,1380902,7,True,True,True,False,0,624366668,273.2667541503906,483.8564453125,467.7383728027344,-57.30756759643555,-89.61819458007812,-62.164222717285156,60.54630661010742,0.6682820320129395,-1.0251080989837646,-0.869085967540741,1.231471300125122,0.5476329922676086 +1392,4.18409,1380903,7,True,True,True,False,0,624366676,273.2671813964844,483.85601806640625,467.7384948730469,-57.309913635253906,-89.61823272705078,-62.16194152832031,60.54624557495117,0.6682650446891785,-1.0250879526138306,-0.869085967540741,1.2314218282699585,0.5476495623588562 +1394,4.192094,1380904,7,True,True,True,False,0,624366684,273.2672119140625,483.8562927246094,467.73828125,-57.31438064575195,-89.61821746826172,-62.1574592590332,60.546260833740234,0.6682990789413452,-1.0251147747039795,-0.8690953850746155,1.2314300537109375,0.5476329922676086 +1396,4.200121,1380905,7,True,True,True,False,0,624366692,273.2668762207031,483.8566589355469,467.7380676269531,-57.31520462036133,-89.61820983886719,-62.156585693359375,60.54630661010742,0.6683160662651062,-1.025141716003418,-0.869085967540741,1.231454849243164,0.5476164221763611 +1399,4.208062,1380906,7,True,True,True,False,0,624366700,273.2674865722656,483.8564147949219,467.73797607421875,-57.31711196899414,-89.61821746826172,-62.154754638671875,60.546241760253906,0.6683273911476135,-1.0251483917236328,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1402,4.216094,1380907,7,True,True,True,False,0,624366708,273.26678466796875,483.8565368652344,467.7381591796875,-57.31248092651367,-89.61823272705078,-62.1593132019043,60.54630661010742,0.6682933568954468,-1.0251282453536987,-0.8690766096115112,1.2314465045928955,0.5476329922676086 +1404,4.224084,1380908,7,True,True,True,False,0,624366716,273.2666931152344,483.8563537597656,467.73834228515625,-57.31145477294922,-89.61820220947266,-62.160335540771484,60.54630661010742,0.6682707071304321,-1.0251080989837646,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1408,4.232095,1380909,7,True,True,True,False,0,624366724,273.2671813964844,483.85626220703125,467.7381591796875,-57.31155014038086,-89.61820983886719,-62.1602897644043,60.546260833740234,0.6682876944541931,-1.0251350402832031,-0.8690766096115112,1.231471300125122,0.5476164221763611 +1410,4.240095,1380910,7,True,True,True,False,0,624366732,273.2672119140625,483.8563232421875,467.7381286621094,-57.31169509887695,-89.61824035644531,-62.1601448059082,60.546260833740234,0.6682990789413452,-1.0251350402832031,-0.869085967540741,1.231454849243164,0.5476495623588562 +1412,4.248134,1380911,7,True,True,True,False,0,624366740,273.2674255371094,483.8563232421875,467.7380676269531,-57.31327819824219,-89.61823272705078,-62.1585807800293,60.546241760253906,0.6683104038238525,-1.025141716003418,-0.8690953850746155,1.231454849243164,0.5476495623588562 +1416,4.256102,1380912,7,True,True,True,False,0,624366748,273.267333984375,483.85638427734375,467.7380676269531,-57.31597137451172,-89.61820220947266,-62.15587615966797,60.5462532043457,0.6683104038238525,-1.025141716003418,-0.8690953850746155,1.231454849243164,0.5476164221763611 +1418,4.264085,1380913,7,True,True,True,False,0,624366756,273.266845703125,483.85662841796875,467.73797607421875,-57.31621551513672,-89.61824035644531,-62.155574798583984,60.54630661010742,0.6683047413825989,-1.0251483917236328,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1420,4.27211,1380914,7,True,True,True,False,0,624366764,273.2671813964844,483.85626220703125,467.7381286621094,-57.31245803833008,-89.61823272705078,-62.15938186645508,60.546260833740234,0.6682876944541931,-1.0251350402832031,-0.8690953850746155,1.231454849243164,0.5476495623588562 +1423,4.280089,1380915,7,True,True,True,False,0,624366772,273.2672119140625,483.8563232421875,467.738037109375,-57.31536102294922,-89.61824035644531,-62.15647888183594,60.546260833740234,0.6682990789413452,-1.025141716003418,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1426,4.288109,1380916,7,True,True,True,False,0,624366780,273.2672424316406,483.8561096191406,467.73828125,-57.310028076171875,-89.61819458007812,-62.161827087402344,60.54624557495117,0.668276309967041,-1.0251215696334839,-0.8690953850746155,1.231471300125122,0.5476329922676086 +1428,4.296106,1380917,7,True,True,True,False,0,624366788,273.2671203613281,483.8561706542969,467.7382507324219,-57.31134033203125,-89.61821746826172,-62.160499572753906,60.546260833740234,0.668276309967041,-1.0251215696334839,-0.869085967540741,1.231454849243164,0.5476329922676086 +1432,4.304096,1380918,7,True,True,True,False,0,624366796,273.2671203613281,483.85614013671875,467.73846435546875,-57.310340881347656,-89.6181869506836,-62.1614990234375,60.546260833740234,0.668276309967041,-1.0250946283340454,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1434,4.312095,1380919,7,True,True,True,False,0,624366804,273.26727294921875,483.85626220703125,467.7383117675781,-57.31019973754883,-89.61823272705078,-62.16164779663086,60.5462532043457,0.6682990789413452,-1.0251147747039795,-0.869085967540741,1.2314465045928955,0.5476495623588562 +1436,4.320094,1380920,7,True,True,True,False,0,624366812,273.2673645019531,483.8563537597656,467.73809814453125,-57.316654205322266,-89.61821746826172,-62.15520095825195,60.54624557495117,0.6683104038238525,-1.0251350402832031,-0.86910480260849,1.2314382791519165,0.5476329922676086 +1438,4.328087,1380921,7,True,True,True,False,0,624366820,273.2674865722656,483.85626220703125,467.7381591796875,-57.31458282470703,-89.61820983886719,-62.157283782958984,60.546234130859375,0.6683104038238525,-1.0251282453536987,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1442,4.336087,1380922,7,True,True,True,False,0,624366828,273.2673645019531,483.8563537597656,467.7381591796875,-57.314002990722656,-89.61821746826172,-62.15785217285156,60.54624557495117,0.6683104038238525,-1.0251282453536987,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1444,4.344107,1380923,7,True,True,True,False,0,624366836,273.2671203613281,483.85601806640625,467.73858642578125,-57.30774688720703,-89.61820983886719,-62.164100646972656,60.5462532043457,0.66825932264328,-1.0250811576843262,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1446,4.352116,1380924,7,True,True,True,False,0,624366844,273.26715087890625,483.856201171875,467.7383728027344,-57.31178283691406,-89.61815643310547,-62.160057067871094,60.546260833740234,0.6682820320129395,-1.0251080989837646,-0.86910480260849,1.231471300125122,0.5475997924804688 +1449,4.360095,1380925,7,True,True,True,False,0,624366852,273.26739501953125,483.8562316894531,467.73822021484375,-57.310909271240234,-89.61817169189453,-62.16094970703125,60.546241760253906,0.6682990789413452,-1.0251282453536987,-0.86910480260849,1.23148775100708,0.5476164221763611 +1452,4.368103,1380926,7,True,True,True,False,0,624366860,273.2674560546875,483.856201171875,467.7381591796875,-57.30961990356445,-89.61823272705078,-62.16225051879883,60.546234130859375,0.6682990789413452,-1.0251350402832031,-0.869085967540741,1.231471300125122,0.5476495623588562 +1454,4.376098,1380927,7,True,True,True,False,0,624366868,273.26727294921875,483.8561706542969,467.7381896972656,-57.31218719482422,-89.61821746826172,-62.15966796875,60.54624557495117,0.6682820320129395,-1.0251282453536987,-0.869085967540741,1.231454849243164,0.5476329922676086 +1458,4.384117,1380928,7,True,True,True,False,0,624366876,273.2666931152344,483.8563537597656,467.73828125,-57.31331253051758,-89.61824035644531,-62.158477783203125,60.54630661010742,0.6682707071304321,-1.0251147747039795,-0.869085967540741,1.2314218282699585,0.5476495623588562 +1460,4.392088,1380929,7,True,True,True,False,0,624366884,273.2672119140625,483.8563232421875,467.7380065917969,-57.31802749633789,-89.61824035644531,-62.153812408447266,60.546260833740234,0.6682990789413452,-1.025141716003418,-0.869085967540741,1.2314218282699585,0.5476329922676086 +1462,4.400122,1380930,7,True,True,True,False,0,624366892,273.2672424316406,483.8564147949219,467.7379455566406,-57.316612243652344,-89.61824035644531,-62.15522766113281,60.546260833740234,0.6683104038238525,-1.0251551866531372,-0.869085967540741,1.2314465045928955,0.5476329922676086 +1466,4.408085,1380931,7,True,True,True,False,0,624366900,273.267333984375,483.85614013671875,467.7381286621094,-57.3145637512207,-89.61820220947266,-62.15729904174805,60.546241760253906,0.6682876944541931,-1.0251350402832031,-0.86910480260849,1.231454849243164,0.5476329922676086 +1468,4.416079,1380932,7,True,True,True,False,0,624366908,273.2672424316406,483.8559875488281,467.7384033203125,-57.31222915649414,-89.61819458007812,-62.15963363647461,60.546241760253906,0.6682650446891785,-1.0251014232635498,-0.869085967540741,1.2314382791519165,0.5476164221763611 +1470,4.424125,1380933,7,True,True,True,False,0,624366916,273.2672119140625,483.8563232421875,467.7381591796875,-57.31458282470703,-89.61820983886719,-62.157257080078125,60.546260833740234,0.6682990789413452,-1.0251282453536987,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1474,4.432089,1380934,7,True,True,True,False,0,624366924,273.2673034667969,483.85650634765625,467.7378845214844,-57.315826416015625,-89.61824035644531,-62.15601348876953,60.546260833740234,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.231454849243164,0.5476495623588562 +1477,4.440083,1380935,7,True,True,True,False,0,624366932,273.2672424316406,483.8564147949219,467.7380676269531,-57.316734313964844,-89.61819458007812,-62.15510559082031,60.546260833740234,0.6683104038238525,-1.025141716003418,-0.86910480260849,1.231454849243164,0.5476164221763611 +1479,4.4481,1380936,7,True,True,True,False,0,624366940,273.267333984375,483.8559875488281,467.73822021484375,-57.31373596191406,-89.61820220947266,-62.15813064575195,60.546234130859375,0.6682650446891785,-1.0251215696334839,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1481,4.456114,1380937,7,True,True,True,False,0,624366948,273.2671203613281,483.85614013671875,467.7383117675781,-57.3107795715332,-89.61822509765625,-62.16106033325195,60.546260833740234,0.668276309967041,-1.0251147747039795,-0.8690766096115112,1.2314465045928955,0.5476329922676086 +1484,4.464096,1380938,7,True,True,True,False,0,624366956,273.26690673828125,483.8567199707031,467.73785400390625,-57.31868362426758,-89.61824035644531,-62.15311050415039,60.54630661010742,0.6683217287063599,-1.025161862373352,-0.86910480260849,1.2314382791519165,0.5476495623588562 +1487,4.472099,1380939,7,True,True,True,False,0,624366964,273.26739501953125,483.8562316894531,467.7381286621094,-57.31322479248047,-89.61822509765625,-62.158634185791016,60.546241760253906,0.6682990789413452,-1.0251350402832031,-0.86910480260849,1.231454849243164,0.5476495623588562 +1489,4.480104,1380940,7,True,True,True,False,0,624366972,273.2672424316406,483.85638427734375,467.7381591796875,-57.31172180175781,-89.61820220947266,-62.160118103027344,60.546260833740234,0.6683047413825989,-1.0251350402832031,-0.8690953850746155,1.231471300125122,0.5476329922676086 +1493,4.488073,1380941,7,True,True,True,False,0,624366980,273.26739501953125,483.8562316894531,467.7381286621094,-57.31729507446289,-89.61822509765625,-62.154563903808594,60.546241760253906,0.6682990789413452,-1.0251282453536987,-0.8691142201423645,1.2314218282699585,0.5476495623588562 +1495,4.496087,1380942,7,True,True,True,False,0,624366988,273.2673645019531,483.8563232421875,467.7381286621094,-57.31350326538086,-89.61823272705078,-62.158348083496094,60.54624557495117,0.6683047413825989,-1.0251350402832031,-0.8690953850746155,1.2314465045928955,0.5476495623588562 +1497,4.504052,1380943,7,True,True,True,False,0,624366996,273.2671203613281,483.85614013671875,467.73834228515625,-57.313743591308594,-89.61817932128906,-62.15809631347656,60.546260833740234,0.668276309967041,-1.0251080989837646,-0.8691235780715942,1.2314465045928955,0.5476329922676086 +1500,4.512061,1380944,7,True,True,True,False,0,624367004,273.26678466796875,483.8564758300781,467.73822021484375,-57.31553268432617,-89.61820220947266,-62.15625762939453,60.54630661010742,0.6682876944541931,-1.0251215696334839,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +1503,4.520062,1380945,7,True,True,True,False,0,624367012,273.2672424316406,483.8564147949219,467.7380676269531,-57.315391540527344,-89.61820983886719,-62.15644836425781,60.546260833740234,0.6683104038238525,-1.025141716003418,-0.86910480260849,1.231454849243164,0.5476329922676086 +1505,4.528067,1380946,7,True,True,True,False,0,624367020,273.2674255371094,483.8561706542969,467.7381896972656,-57.310874938964844,-89.61820220947266,-62.16099166870117,60.546234130859375,0.6682933568954468,-1.0251282453536987,-0.8690953850746155,1.231471300125122,0.5476329922676086 +1509,4.536168,1380947,7,True,True,True,False,0,624367028,273.267333984375,483.85626220703125,467.7378234863281,-57.32402420043945,-89.61821746826172,-62.147830963134766,60.54624557495117,0.6682990789413452,-1.025161862373352,-0.8690953850746155,1.2314218282699585,0.5475997924804688 +1511,4.544159,1380948,7,True,True,True,False,0,624367036,273.2671203613281,483.8561706542969,467.73828125,-57.311859130859375,-89.6181640625,-62.15998077392578,60.546260833740234,0.668276309967041,-1.0251215696334839,-0.8691142201423645,1.231479525566101,0.5476164221763611 +1513,4.552175,1380949,7,True,True,True,False,0,624367044,273.2667541503906,483.8564453125,467.7383117675781,-57.31259536743164,-89.6181869506836,-62.15919494628906,60.54630661010742,0.6682820320129395,-1.0251147747039795,-0.8690953850746155,1.231454849243164,0.5476164221763611 +1517,4.560064,1380950,7,True,True,True,False,0,624367052,273.2666931152344,483.8563537597656,467.73834228515625,-57.310401916503906,-89.61820220947266,-62.1613883972168,60.54630661010742,0.6682707071304321,-1.0251080989837646,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1519,4.568142,1380951,7,True,True,True,False,0,624367060,273.2674255371094,483.8561706542969,467.7382507324219,-57.312103271484375,-89.61820983886719,-62.15976333618164,60.546234130859375,0.6682933568954468,-1.0251215696334839,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1521,4.576127,1380952,7,True,True,True,False,0,624367068,273.2672119140625,483.8563232421875,467.7381286621094,-57.314849853515625,-89.61821746826172,-62.15699005126953,60.546260833740234,0.6682990789413452,-1.0251350402832031,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1524,4.584141,1380953,7,True,True,True,False,0,624367076,273.2667541503906,483.8564453125,467.7381286621094,-57.31998825073242,-89.61819458007812,-62.15180587768555,60.54630661010742,0.6682820320129395,-1.0251282453536987,-0.8691142201423645,1.2314218282699585,0.5476164221763611 +1528,4.592105,1380954,7,True,True,True,False,0,624367084,273.2671813964844,483.8561096191406,467.73828125,-57.31303405761719,-89.61823272705078,-62.1588134765625,60.5462532043457,0.668276309967041,-1.0251147747039795,-0.8690953850746155,1.2314300537109375,0.5476495623588562 +1530,4.600099,1380955,7,True,True,True,False,0,624367092,273.2667236328125,483.85638427734375,467.73834228515625,-57.31222152709961,-89.61819458007812,-62.15957260131836,60.54630661010742,0.668276309967041,-1.0251080989837646,-0.86910480260849,1.2314465045928955,0.5476329922676086 +1532,4.608113,1380956,7,True,True,True,False,0,624367100,273.2674560546875,483.8560485839844,467.7381896972656,-57.31353759765625,-89.61820220947266,-62.1583366394043,60.546226501464844,0.6682820320129395,-1.0251282453536987,-0.869085967540741,1.231454849243164,0.5476164221763611 +1535,4.616094,1380957,7,True,True,True,False,0,624367108,273.26678466796875,483.8565368652344,467.7381286621094,-57.31304168701172,-89.61822509765625,-62.15875244140625,60.54630661010742,0.6682933568954468,-1.0251350402832031,-0.869085967540741,1.231454849243164,0.5476329922676086 +1538,4.624101,1380958,7,True,True,True,False,0,624367116,273.26708984375,483.8561096191406,467.7383117675781,-57.31172180175781,-89.61821746826172,-62.160118103027344,60.546260833740234,0.6682707071304321,-1.0251147747039795,-0.86910480260849,1.2314465045928955,0.5476495623588562 +1540,4.632125,1380959,7,True,True,True,False,0,624367124,273.267333984375,483.85626220703125,467.7381286621094,-57.315616607666016,-89.61820983886719,-62.1562385559082,60.54624557495117,0.6682990789413452,-1.0251350402832031,-0.86910480260849,1.2314465045928955,0.5476329922676086 +1544,4.640112,1380960,7,True,True,True,False,0,624367132,273.2666931152344,483.8563537597656,467.7383117675781,-57.314125061035156,-89.61817169189453,-62.15766525268555,60.54630661010742,0.6682707071304321,-1.0251147747039795,-0.8691142201423645,1.231454849243164,0.5476164221763611 +1546,4.648113,1380961,7,True,True,True,False,0,624367140,273.267333984375,483.8559875488281,467.7383117675781,-57.315914154052734,-89.61820220947266,-62.15595245361328,60.546234130859375,0.6682707071304321,-1.0251080989837646,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +1548,4.656116,1380962,7,True,True,True,False,0,624367148,273.26715087890625,483.8562316894531,467.7383728027344,-57.3123779296875,-89.61823272705078,-62.159461975097656,60.546260833740234,0.6682876944541931,-1.0251014232635498,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +1551,4.664115,1380963,7,True,True,True,False,0,624367156,273.2672119140625,483.8562927246094,467.7384338378906,-57.310211181640625,-89.61820983886719,-62.16162872314453,60.546260833740234,0.6682990789413452,-1.0250946283340454,-0.86910480260849,1.2314382791519165,0.5476495623588562 +1554,4.672098,1380964,7,True,True,True,False,0,624367164,273.26678466796875,483.8565368652344,467.7383117675781,-57.31095504760742,-89.61822509765625,-62.16083526611328,60.54630661010742,0.6682990789413452,-1.0251147747039795,-0.8690953850746155,1.2314465045928955,0.5476495623588562 +1556,4.680123,1380965,7,True,True,True,False,0,624367172,273.2672119140625,483.8563232421875,467.7382507324219,-57.312103271484375,-89.61820983886719,-62.15973663330078,60.546260833740234,0.6682990789413452,-1.0251215696334839,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1559,4.688106,1380966,7,True,True,True,False,0,624367180,273.2673645019531,483.8564453125,467.7380065917969,-57.31758117675781,-89.61819458007812,-62.154266357421875,60.5462532043457,0.6683217287063599,-1.0251483917236328,-0.86910480260849,1.231454849243164,0.5476164221763611 +1562,4.696095,1380967,7,True,True,True,False,0,624367188,273.2674255371094,483.8564147949219,467.73809814453125,-57.316619873046875,-89.61824035644531,-62.155235290527344,60.54624557495117,0.6683217287063599,-1.0251350402832031,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +1564,4.704087,1380968,7,True,True,True,False,0,624367196,273.2667236328125,483.85638427734375,467.73834228515625,-57.313270568847656,-89.61817169189453,-62.15851974487305,60.54630661010742,0.668276309967041,-1.0251080989837646,-0.8691142201423645,1.231454849243164,0.5476164221763611 +1566,4.712115,1380969,7,True,True,True,False,0,624367204,273.2672119140625,483.8563232421875,467.7378234863281,-57.32133102416992,-89.61824035644531,-62.150508880615234,60.546260833740234,0.6682990789413452,-1.025161862373352,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +1570,4.720109,1380970,7,True,True,True,False,0,624367212,273.2672424316406,483.8564147949219,467.7375183105469,-57.32643508911133,-89.61827087402344,-62.14540481567383,60.546260833740234,0.6683047413825989,-1.0252022743225098,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +1572,4.728099,1380971,7,True,True,True,False,0,624367220,273.2674560546875,483.856201171875,467.7378234863281,-57.322105407714844,-89.61824035644531,-62.14976501464844,60.546234130859375,0.6682990789413452,-1.025161862373352,-0.86910480260849,1.2314218282699585,0.5476329922676086 +1574,4.736122,1380972,7,True,True,True,False,0,624367228,273.2671813964844,483.85601806640625,467.7385559082031,-57.31117630004883,-89.61820220947266,-62.16067886352539,60.54624557495117,0.6682650446891785,-1.0250811576843262,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +1578,4.744102,1380973,7,True,True,True,False,0,624367236,273.26715087890625,483.8562316894531,467.73834228515625,-57.313087463378906,-89.61817169189453,-62.15875244140625,60.546260833740234,0.6682876944541931,-1.0251147747039795,-0.8691142201423645,1.231463074684143,0.5476164221763611 +1580,4.752105,1380974,7,True,True,True,False,0,624367244,273.26715087890625,483.8562316894531,467.7383117675781,-57.31218719482422,-89.61823272705078,-62.15965270996094,60.546260833740234,0.6682876944541931,-1.0251080989837646,-0.8690953850746155,1.2314300537109375,0.5476495623588562 +1582,4.760118,1380975,7,True,True,True,False,0,624367252,273.2671813964844,483.8562927246094,467.73834228515625,-57.310401916503906,-89.61820220947266,-62.16143798828125,60.546260833740234,0.6682933568954468,-1.0251080989837646,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1585,4.768104,1380976,7,True,True,True,False,0,624367260,273.26739501953125,483.8562316894531,467.73822021484375,-57.313148498535156,-89.61820983886719,-62.15871047973633,60.546241760253906,0.6682990789413452,-1.0251215696334839,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1588,4.776144,1380977,7,True,True,True,False,0,624367268,273.2673645019531,483.8563537597656,467.7380676269531,-57.31327819824219,-89.61823272705078,-62.158573150634766,60.54624557495117,0.6683104038238525,-1.025141716003418,-0.8690953850746155,1.231454849243164,0.5476495623588562 +1590,4.784117,1380978,7,True,True,True,False,0,624367276,273.26666259765625,483.85626220703125,467.73858642578125,-57.30671691894531,-89.61820220947266,-62.165077209472656,60.54630661010742,0.66825932264328,-1.0250811576843262,-0.8690953850746155,1.2314465045928955,0.5476495623588562 +1594,4.792107,1380979,7,True,True,True,False,0,624367284,273.2669372558594,483.8558349609375,467.73883056640625,-57.303321838378906,-89.6181869506836,-62.16851806640625,60.546260833740234,0.6682309508323669,-1.0250543355941772,-0.8690953850746155,1.2314465045928955,0.5476495623588562 +1596,4.800096,1380980,7,True,True,True,False,0,624367292,273.2667236328125,483.85638427734375,467.73834228515625,-57.312801361083984,-89.6181869506836,-62.158992767333984,60.54630661010742,0.668276309967041,-1.0251080989837646,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1598,4.808118,1380981,7,True,True,True,False,0,624367300,273.26739501953125,483.8560791015625,467.73834228515625,-57.309059143066406,-89.61821746826172,-62.16280746459961,60.546234130859375,0.6682876944541931,-1.0251080989837646,-0.8690953850746155,1.231454849243164,0.5476495623588562 +1602,4.816096,1380982,7,True,True,True,False,0,624367308,273.267333984375,483.8565673828125,467.7378845214844,-57.31880569458008,-89.61823272705078,-62.15303421020508,60.546260833740234,0.668333113193512,-1.025161862373352,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1604,4.824095,1380983,7,True,True,True,False,0,624367316,273.2674255371094,483.8564147949219,467.7378845214844,-57.317935943603516,-89.61822509765625,-62.15391540527344,60.54624557495117,0.6683217287063599,-1.025161862373352,-0.86910480260849,1.231454849243164,0.5476329922676086 +1606,4.832115,1380984,7,True,True,True,False,0,624367324,273.26708984375,483.8561096191406,467.7384338378906,-57.310035705566406,-89.61820983886719,-62.16180419921875,60.546260833740234,0.6682650446891785,-1.0250946283340454,-0.869085967540741,1.2314382791519165,0.5476329922676086 +1608,4.840113,1380985,7,True,True,True,False,0,624367332,273.2665100097656,483.85601806640625,467.7387390136719,-57.30685806274414,-89.6181640625,-62.16493225097656,60.54630661010742,0.6682252883911133,-1.025061011314392,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1611,4.848109,1380986,7,True,True,True,False,0,624367340,273.267333984375,483.8559875488281,467.73834228515625,-57.3124885559082,-89.61820983886719,-62.15938186645508,60.546234130859375,0.6682707071304321,-1.0251080989837646,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1614,4.856097,1380987,7,True,True,True,False,0,624367348,273.2674865722656,483.8565368652344,467.7374572753906,-57.325225830078125,-89.6182632446289,-62.146629333496094,60.54624557495117,0.668333113193512,-1.0252090692520142,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1616,4.864128,1380988,7,True,True,True,False,0,624367356,273.26727294921875,483.8564453125,467.73797607421875,-57.31711196899414,-89.61821746826172,-62.15473556518555,60.546260833740234,0.6683160662651062,-1.0251483917236328,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1620,4.872131,1380989,7,True,True,True,False,0,624367364,273.2672424316406,483.8564147949219,467.73809814453125,-57.31740951538086,-89.61820983886719,-62.15442657470703,60.546260833740234,0.6683104038238525,-1.0251350402832031,-0.8691142201423645,1.2314382791519165,0.5476329922676086 +1622,4.880103,1380990,7,True,True,True,False,0,624367372,273.26715087890625,483.856201171875,467.7383117675781,-57.31019973754883,-89.61823272705078,-62.16164016723633,60.546260833740234,0.6682820320129395,-1.0251147747039795,-0.869085967540741,1.2314465045928955,0.5476495623588562 +1624,4.88814,1380991,7,True,True,True,False,0,624367380,273.267333984375,483.85601806640625,467.73834228515625,-57.308414459228516,-89.61820220947266,-62.1634521484375,60.546234130859375,0.668276309967041,-1.0251147747039795,-0.869085967540741,1.231471300125122,0.5476329922676086 +1628,4.896112,1380992,7,True,True,True,False,0,624367388,273.2674255371094,483.8561706542969,467.73828125,-57.31333923339844,-89.61820983886719,-62.15852737426758,60.546234130859375,0.6682990789413452,-1.0251147747039795,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1630,4.90412,1380993,7,True,True,True,False,0,624367396,273.2672424316406,483.85638427734375,467.7381591796875,-57.315040588378906,-89.61821746826172,-62.15679931640625,60.546260833740234,0.6683047413825989,-1.0251282453536987,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1632,4.91212,1380994,7,True,True,True,False,0,624367404,273.2674255371094,483.8564147949219,467.7380676269531,-57.31432342529297,-89.61824035644531,-62.157527923583984,60.54624557495117,0.6683217287063599,-1.025141716003418,-0.8690953850746155,1.2314465045928955,0.5476495623588562 +1635,4.920109,1380995,7,True,True,True,False,0,624367412,273.2674255371094,483.8564147949219,467.738037109375,-57.31833267211914,-89.61822509765625,-62.15351867675781,60.54624557495117,0.6683217287063599,-1.025141716003418,-0.869085967540741,1.2314300537109375,0.5476164221763611 +1638,4.928104,1380996,7,True,True,True,False,0,624367420,273.2674560546875,483.856201171875,467.73822021484375,-57.31464385986328,-89.61822509765625,-62.157222747802734,60.546234130859375,0.6682990789413452,-1.0251215696334839,-0.86910480260849,1.2314300537109375,0.5476495623588562 +1640,4.936118,1380997,7,True,True,True,False,0,624367428,273.267333984375,483.85626220703125,467.7381591796875,-57.31562042236328,-89.61821746826172,-62.15623092651367,60.54624557495117,0.6682990789413452,-1.0251282453536987,-0.869085967540741,1.2314382791519165,0.5476164221763611 +1644,4.944128,1380998,7,True,True,True,False,0,624367436,273.2672119140625,483.8563232421875,467.7381591796875,-57.311134338378906,-89.61824035644531,-62.16070556640625,60.546260833740234,0.6682990789413452,-1.0251282453536987,-0.8690766096115112,1.2314465045928955,0.5476495623588562 +1646,4.95206,1380999,7,True,True,True,False,0,624367444,273.2672424316406,483.85638427734375,467.7381286621094,-57.31332778930664,-89.61824035644531,-62.158512115478516,60.546260833740234,0.6683047413825989,-1.0251350402832031,-0.8690766096115112,1.2314465045928955,0.5476329922676086 +1648,4.960106,1381000,7,True,True,True,False,0,624367452,273.26678466796875,483.8564758300781,467.7381591796875,-57.31534957885742,-89.61820220947266,-62.15644454956055,60.54630661010742,0.6682876944541931,-1.0251282453536987,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1650,4.968117,1381001,7,True,True,True,False,0,624367460,273.267333984375,483.8558654785156,467.7386474609375,-57.307220458984375,-89.6181869506836,-62.1646614074707,60.546226501464844,0.6682536602020264,-1.0250744819641113,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1654,4.97612,1381002,7,True,True,True,False,0,624367468,273.26727294921875,483.856201171875,467.73822021484375,-57.31180191040039,-89.61822509765625,-62.16004943847656,60.54624557495117,0.6682876944541931,-1.0251215696334839,-0.8690953850746155,1.2314465045928955,0.5476495623588562 +1656,4.984093,1381003,7,True,True,True,False,0,624367476,273.2674255371094,483.8567199707031,467.7372741699219,-57.32911682128906,-89.61825561523438,-62.142723083496094,60.546260833740234,0.6683501601219177,-1.0252292156219482,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +1658,4.992132,1381004,7,True,True,True,False,0,624367484,273.2674560546875,483.8564758300781,467.7374572753906,-57.32387924194336,-89.61827850341797,-62.147972106933594,60.54624557495117,0.6683273911476135,-1.0252090692520142,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1661,5.000113,1381005,7,True,True,True,False,0,624367492,273.266845703125,483.85662841796875,467.7379455566406,-57.31917190551758,-89.61822509765625,-62.152618408203125,60.54630661010742,0.6683104038238525,-1.0251551866531372,-0.86910480260849,1.2314382791519165,0.5476329922676086 +1664,5.008088,1381006,7,True,True,True,False,0,624367500,273.2673645019531,483.8559265136719,467.73846435546875,-57.311100006103516,-89.61817932128906,-62.16077423095703,60.546226501464844,0.6682650446891785,-1.0250946283340454,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1666,5.016121,1381007,7,True,True,True,False,0,624367508,273.26727294921875,483.856201171875,467.7381591796875,-57.31637954711914,-89.61820220947266,-62.15547180175781,60.54624557495117,0.6682876944541931,-1.0251282453536987,-0.8690953850746155,1.2314382791519165,0.5476164221763611 +1669,5.024107,1381008,7,True,True,True,False,0,624367516,273.2672119140625,483.8563232421875,467.7380676269531,-57.315853118896484,-89.61822509765625,-62.15598678588867,60.546260833740234,0.6682990789413452,-1.025141716003418,-0.8691142201423645,1.2314465045928955,0.5476495623588562 +1672,5.032119,1381009,7,True,True,True,False,0,624367524,273.2674560546875,483.8564758300781,467.73779296875,-57.32218551635742,-89.61824798583984,-62.1496696472168,60.54624557495117,0.6683273911476135,-1.0251686573028564,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +1674,5.040123,1381010,7,True,True,True,False,0,624367532,273.2668151855469,483.8565673828125,467.7381286621094,-57.317874908447266,-89.61822509765625,-62.15391540527344,60.54630661010742,0.6682990789413452,-1.0251282453536987,-0.86910480260849,1.2314218282699585,0.5476329922676086 +1678,5.048079,1381011,7,True,True,True,False,0,624367540,273.2673645019531,483.8560791015625,467.73828125,-57.312583923339844,-89.61822509765625,-62.15928649902344,60.546234130859375,0.6682820320129395,-1.0251147747039795,-0.869085967540741,1.2314382791519165,0.5476329922676086 +1680,5.056125,1381012,7,True,True,True,False,0,624367548,273.2672424316406,483.85638427734375,467.73809814453125,-57.31796646118164,-89.61823272705078,-62.153873443603516,60.546260833740234,0.6683047413825989,-1.0251350402832031,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +1682,5.064119,1381013,7,True,True,True,False,0,624367556,273.26678466796875,483.8565368652344,467.7383117675781,-57.31441116333008,-89.6181869506836,-62.157379150390625,60.54630661010742,0.6682990789413452,-1.0251147747039795,-0.86910480260849,1.2314465045928955,0.5476164221763611 +1685,5.072117,1381014,7,True,True,True,False,0,624367564,273.2674865722656,483.85638427734375,467.738037109375,-57.315948486328125,-89.61824035644531,-62.15591049194336,60.546241760253906,0.6683217287063599,-1.025141716003418,-0.869085967540741,1.2314382791519165,0.5476329922676086 +1688,5.080098,1381015,7,True,True,True,False,0,624367572,273.2673645019531,483.8563537597656,467.7379150390625,-57.31716537475586,-89.6182632446289,-62.15468978881836,60.54624557495117,0.6683104038238525,-1.0251551866531372,-0.8690671920776367,1.2314300537109375,0.5476329922676086 +1690,5.088133,1381016,7,True,True,True,False,0,624367580,273.2674255371094,483.85614013671875,467.7378234863281,-57.31917190551758,-89.61824798583984,-62.15269470214844,60.546234130859375,0.6682876944541931,-1.0251686573028564,-0.8690671920776367,1.2314382791519165,0.5476164221763611 +1692,5.096129,1381017,7,True,True,True,False,0,624367588,273.2674560546875,483.856201171875,467.73809814453125,-57.315887451171875,-89.61822509765625,-62.15597915649414,60.546234130859375,0.6682990789413452,-1.0251350402832031,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1695,5.10412,1381018,7,True,True,True,False,0,624367596,273.2667541503906,483.8564453125,467.7379455566406,-57.31899642944336,-89.61823272705078,-62.15279769897461,60.54630661010742,0.6682820320129395,-1.0251551866531372,-0.869085967540741,1.2314382791519165,0.5476164221763611 +1698,5.112113,1381019,7,True,True,True,False,0,624367604,273.26727294921875,483.8564453125,467.7379150390625,-57.32079315185547,-89.61822509765625,-62.15104675292969,60.546260833740234,0.6683160662651062,-1.0251551866531372,-0.8690953850746155,1.2314300537109375,0.5476164221763611 +1700,5.120127,1381020,7,True,True,True,False,0,624367612,273.2672424316406,483.8564147949219,467.7379455566406,-57.3187141418457,-89.61821746826172,-62.15312576293945,60.546260833740234,0.6683104038238525,-1.0251551866531372,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1704,5.128126,1381021,7,True,True,True,False,0,624367620,273.2674560546875,483.8562316894531,467.7377624511719,-57.32096481323242,-89.61824035644531,-62.15090560913086,60.546234130859375,0.6682990789413452,-1.0251753330230713,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1706,5.1361,1381022,7,True,True,True,False,0,624367628,273.2673645019531,483.8563537597656,467.73797607421875,-57.317562103271484,-89.61823272705078,-62.154293060302734,60.54624557495117,0.6683104038238525,-1.0251483917236328,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1708,5.144131,1381023,7,True,True,True,False,0,624367636,273.266845703125,483.85662841796875,467.7380065917969,-57.32220458984375,-89.61823272705078,-62.14958572387695,60.54630661010742,0.6683104038238525,-1.025141716003418,-0.8690953850746155,1.2314053773880005,0.5476164221763611 +1712,5.152112,1381024,7,True,True,True,False,0,624367644,273.266845703125,483.85662841796875,467.7379150390625,-57.320369720458984,-89.6182632446289,-62.151424407958984,60.54630661010742,0.6683104038238525,-1.0251483917236328,-0.8690953850746155,1.2314053773880005,0.5476495623588562 +1714,5.160095,1381025,7,True,True,True,False,0,624367652,273.2673034667969,483.85650634765625,467.73779296875,-57.32655715942383,-89.61827087402344,-62.14528274536133,60.546260833740234,0.6683217287063599,-1.025161862373352,-0.8690953850746155,1.2313807010650635,0.5476329922676086 +1716,5.168123,1381026,7,True,True,True,False,0,624367660,273.2674255371094,483.8562927246094,467.73779296875,-57.3258056640625,-89.61824035644531,-62.146053314208984,60.546241760253906,0.6683047413825989,-1.025161862373352,-0.8690953850746155,1.2313971519470215,0.5476164221763611 +1719,5.176101,1381027,7,True,True,True,False,0,624367668,273.2673034667969,483.8562316894531,467.7381286621094,-57.31711959838867,-89.61823272705078,-62.15473556518555,60.54624557495117,0.6682933568954468,-1.0251282453536987,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +1722,5.184126,1381028,7,True,True,True,False,0,624367676,273.2667236328125,483.85638427734375,467.7382507324219,-57.3174934387207,-89.61823272705078,-62.154296875,60.54630661010742,0.668276309967041,-1.0251147747039795,-0.8690953850746155,1.2314053773880005,0.5476329922676086 +1724,5.192133,1381029,7,True,True,True,False,0,624367684,273.2672119140625,483.8562927246094,467.7380676269531,-57.32062911987305,-89.61824035644531,-62.15121078491211,60.546260833740234,0.6682933568954468,-1.0251350402832031,-0.869085967540741,1.2314053773880005,0.5476164221763611 +1728,5.200122,1381030,7,True,True,True,False,0,624367692,273.266845703125,483.85662841796875,467.7380065917969,-57.318782806396484,-89.61824035644531,-62.15300750732422,60.54630661010742,0.6683047413825989,-1.025141716003418,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +1730,5.208108,1381031,7,True,True,True,False,0,624367700,273.26739501953125,483.8562316894531,467.73809814453125,-57.316654205322266,-89.61821746826172,-62.155208587646484,60.546241760253906,0.6682990789413452,-1.0251350402832031,-0.86910480260849,1.2314382791519165,0.5476329922676086 +1732,5.216142,1381032,7,True,True,True,False,0,624367708,273.2673645019531,483.8560485839844,467.7380065917969,-57.31937026977539,-89.61823272705078,-62.152496337890625,60.546234130859375,0.668276309967041,-1.025141716003418,-0.869085967540741,1.2314218282699585,0.5476164221763611 +1734,5.224123,1381033,7,True,True,True,False,0,624367716,273.2672119140625,483.8563232421875,467.7381286621094,-57.31549835205078,-89.61823272705078,-62.156341552734375,60.546260833740234,0.6682990789413452,-1.0251282453536987,-0.86910480260849,1.2314300537109375,0.5476495623588562 +1738,5.232106,1381034,7,True,True,True,False,0,624367724,273.2672424316406,483.8564147949219,467.7381591796875,-57.315040588378906,-89.61821746826172,-62.15679931640625,60.546260833740234,0.6683104038238525,-1.0251282453536987,-0.8690953850746155,1.2314382791519165,0.5476329922676086 +1740,5.240098,1381035,7,True,True,True,False,0,624367732,273.26727294921875,483.8564758300781,467.73779296875,-57.320838928222656,-89.6182632446289,-62.1510009765625,60.546260833740234,0.6683160662651062,-1.0251686573028564,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +1742,5.248121,1381036,7,True,True,True,False,0,624367740,273.2673645019531,483.8563232421875,467.7381896972656,-57.318382263183594,-89.61820220947266,-62.153472900390625,60.54624557495117,0.6683047413825989,-1.0251215696334839,-0.86910480260849,1.2314218282699585,0.5476164221763611 +1745,5.256125,1381037,7,True,True,True,False,0,624367748,273.2672119140625,483.8560485839844,467.7383117675781,-57.313804626464844,-89.61822509765625,-62.15804672241211,60.54624557495117,0.6682707071304321,-1.0251080989837646,-0.869085967540741,1.2314218282699585,0.5476329922676086 +1748,5.264109,1381038,7,True,True,True,False,0,624367756,273.266845703125,483.85662841796875,467.7381896972656,-57.31834030151367,-89.61824035644531,-62.15345001220703,60.54630661010742,0.6683047413825989,-1.0251215696334839,-0.8690953850746155,1.2314053773880005,0.5476329922676086 +1750,5.272124,1381039,7,True,True,True,False,0,624367764,273.2674255371094,483.8565673828125,467.73773193359375,-57.32124328613281,-89.61825561523438,-62.150604248046875,60.5462532043457,0.668333113193512,-1.0251753330230713,-0.869085967540741,1.2314300537109375,0.5476329922676086 +1754,5.280133,1381040,7,True,True,True,False,0,624367772,273.2672119140625,483.8563537597656,467.7377014160156,-57.32588195800781,-89.61825561523438,-62.145957946777344,60.546260833740234,0.6682990789413452,-1.0251753330230713,-0.86910480260849,1.2314053773880005,0.5476329922676086 +1756,5.288114,1381041,7,True,True,True,False,0,624367780,273.2673645019531,483.8563537597656,467.7376403808594,-57.32386016845703,-89.6182861328125,-62.14799499511719,60.54624557495117,0.6683047413825989,-1.0251820087432861,-0.869085967540741,1.2314053773880005,0.5476495623588562 +1758,5.296147,1381042,7,True,True,True,False,0,624367788,273.26708984375,483.8561096191406,467.73834228515625,-57.31637954711914,-89.61821746826172,-62.155460357666016,60.546260833740234,0.6682650446891785,-1.0251014232635498,-0.869085967540741,1.2314053773880005,0.5476164221763611 +1762,5.304117,1381043,7,True,True,True,False,0,624367796,273.2667541503906,483.8564453125,467.7378234863281,-57.31770324707031,-89.6182861328125,-62.15408706665039,60.54630661010742,0.6682820320129395,-1.025161862373352,-0.8690671920776367,1.2314218282699585,0.5476495623588562 +1764,5.31213,1381044,7,True,True,True,False,0,624367804,273.2673034667969,483.85650634765625,467.7379455566406,-57.316490173339844,-89.61825561523438,-62.15534973144531,60.546260833740234,0.6683273911476135,-1.0251483917236328,-0.869085967540741,1.2314300537109375,0.5476495623588562 +1766,5.320114,1381045,7,True,True,True,False,0,624367812,273.2668762207031,483.8566589355469,467.73785400390625,-57.32088088989258,-89.61824035644531,-62.150909423828125,60.54630661010742,0.6683160662651062,-1.025161862373352,-0.869085967540741,1.2314300537109375,0.5476164221763611 +1769,5.328115,1381046,7,True,True,True,False,0,624367820,273.267333984375,483.85626220703125,467.73809814453125,-57.31454086303711,-89.61824035644531,-62.157310485839844,60.54624557495117,0.6682990789413452,-1.0251350402832031,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1772,5.336118,1381047,7,True,True,True,False,0,624367828,273.2671813964844,483.85601806640625,467.73822021484375,-57.31449508666992,-89.61819458007812,-62.1573600769043,60.54624557495117,0.66825932264328,-1.0251215696334839,-0.8690953850746155,1.2314465045928955,0.5476164221763611 +1774,5.34411,1381048,7,True,True,True,False,0,624367836,273.2666931152344,483.8563232421875,467.7384033203125,-57.310302734375,-89.61821746826172,-62.16149139404297,60.54630661010742,0.6682650446891785,-1.0251014232635498,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1776,5.35215,1381049,7,True,True,True,False,0,624367844,273.26708984375,483.8561096191406,467.73846435546875,-57.311866760253906,-89.61817169189453,-62.15997314453125,60.546260833740234,0.6682707071304321,-1.0250946283340454,-0.86910480260849,1.2314465045928955,0.5476164221763611 +1780,5.360107,1381050,7,True,True,True,False,0,624367852,273.2666931152344,483.8563537597656,467.7384033203125,-57.310302734375,-89.61821746826172,-62.16149139404297,60.54630661010742,0.6682707071304321,-1.0251014232635498,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1782,5.368126,1381051,7,True,True,True,False,0,624367860,273.2674560546875,483.8563232421875,467.7376708984375,-57.32133102416992,-89.61824035644531,-62.15052795410156,60.546241760253906,0.6683104038238525,-1.0251888036727905,-0.86910480260849,1.231454849243164,0.5476329922676086 +1784,5.37613,1381052,7,True,True,True,False,0,624367868,273.26739501953125,483.85626220703125,467.7375793457031,-57.32171630859375,-89.61825561523438,-62.150146484375,60.546241760253906,0.6682990789413452,-1.0251954793930054,-0.869085967540741,1.2314465045928955,0.5476329922676086 +1788,5.384125,1381053,7,True,True,True,False,0,624367876,273.2668151855469,483.8565673828125,467.73760986328125,-57.325321197509766,-89.61825561523438,-62.1464729309082,60.54630661010742,0.6682933568954468,-1.0251888036727905,-0.869085967540741,1.2314218282699585,0.5476164221763611 +1790,5.39209,1381054,7,True,True,True,False,0,624367884,273.26739501953125,483.85638427734375,467.737548828125,-57.32680892944336,-89.6182632446289,-62.145042419433594,60.54624557495117,0.6683160662651062,-1.0251954793930054,-0.8691142201423645,1.2314136028289795,0.5476495623588562 +1792,5.40015,1381055,7,True,True,True,False,0,624367892,273.2668151855469,483.8565979003906,467.7377014160156,-57.32435607910156,-89.61827087402344,-62.14743423461914,60.54630661010742,0.6682990789413452,-1.0251753330230713,-0.869085967540741,1.2314053773880005,0.5476329922676086 +1795,5.408112,1381056,7,True,True,True,False,0,624367900,273.2674255371094,483.8563232421875,467.7378234863281,-57.32191848754883,-89.61824035644531,-62.149940490722656,60.546241760253906,0.6683104038238525,-1.025161862373352,-0.869085967540741,1.2314218282699585,0.5476164221763611 +1798,5.416101,1381057,7,True,True,True,False,0,624367908,273.26739501953125,483.85638427734375,467.7379455566406,-57.31917190551758,-89.61822509765625,-62.152679443359375,60.54624557495117,0.6683160662651062,-1.0251551866531372,-0.86910480260849,1.2314382791519165,0.5476329922676086 +1800,5.424149,1381058,7,True,True,True,False,0,624367916,273.2672119140625,483.8563537597656,467.7377624511719,-57.319923400878906,-89.61824035644531,-62.15191650390625,60.546260833740234,0.6682990789413452,-1.0251753330230713,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1804,5.432135,1381059,7,True,True,True,False,0,624367924,273.26715087890625,483.8562316894531,467.7378845214844,-57.31641387939453,-89.61824035644531,-62.155426025390625,60.546260833740234,0.6682820320129395,-1.025161862373352,-0.869085967540741,1.231454849243164,0.5476329922676086 +1806,5.440089,1381060,7,True,True,True,False,0,624367932,273.2667236328125,483.8564147949219,467.7379455566406,-57.31269073486328,-89.6182632446289,-62.15909957885742,60.54630661010742,0.668276309967041,-1.0251551866531372,-0.8690671920776367,1.231454849243164,0.5476495623588562 +1808,5.448119,1381061,7,True,True,True,False,0,624367940,273.267333984375,483.85626220703125,467.7381896972656,-57.312774658203125,-89.61820983886719,-62.159080505371094,60.54624557495117,0.6682990789413452,-1.0251282453536987,-0.8690766096115112,1.231454849243164,0.5476164221763611 +1812,5.456084,1381062,7,True,True,True,False,0,624367948,273.2673034667969,483.8562316894531,467.73828125,-57.3167610168457,-89.61820983886719,-62.15509033203125,60.54624557495117,0.6682990789413452,-1.0251147747039795,-0.8690953850746155,1.2314218282699585,0.5476164221763611 +1814,5.464079,1381063,7,True,True,True,False,0,624367956,273.2666931152344,483.8563537597656,467.73858642578125,-57.30805587768555,-89.6181869506836,-62.16373825073242,60.54630661010742,0.6682707071304321,-1.0250811576843262,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1816,5.472092,1381064,7,True,True,True,False,0,624367964,273.26678466796875,483.85650634765625,467.7383728027344,-57.30622482299805,-89.61820983886719,-62.16556930541992,60.54630661010742,0.6682933568954468,-1.0251080989837646,-0.869085967540741,1.231471300125122,0.5476495623588562 +1818,5.480116,1381065,7,True,True,True,False,0,624367972,273.2668762207031,483.8566589355469,467.7381286621094,-57.31379699707031,-89.61821746826172,-62.15799331665039,60.54630661010742,0.6683160662651062,-1.0251350402832031,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1821,5.488085,1381066,7,True,True,True,False,0,624367980,273.2674865722656,483.8565673828125,467.737548828125,-57.32227325439453,-89.61824798583984,-62.14957809448242,60.54624557495117,0.6683387756347656,-1.0252022743225098,-0.8690953850746155,1.231454849243164,0.5476329922676086 +1824,5.496107,1381067,7,True,True,True,False,0,624367988,273.26727294921875,483.8561706542969,467.73797607421875,-57.31804656982422,-89.61820983886719,-62.15380859375,60.54624557495117,0.6682820320129395,-1.0251483917236328,-0.8691142201423645,1.2314465045928955,0.5476329922676086 +1827,5.504107,1381068,7,True,True,True,False,0,624367996,273.2668151855469,483.8565673828125,467.7379455566406,-57.31706237792969,-89.61824798583984,-62.154727935791016,60.54630661010742,0.6682990789413452,-1.0251551866531372,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1830,5.512091,1381069,7,True,True,True,False,0,624368004,273.2672119140625,483.8563232421875,467.7379150390625,-57.3194465637207,-89.61824035644531,-62.15239334106445,60.546260833740234,0.6682990789413452,-1.0251551866531372,-0.8690953850746155,1.2314300537109375,0.5476329922676086 +1833,5.520085,1381070,7,True,True,True,False,0,624368012,273.2668151855469,483.8565673828125,467.73822021484375,-57.313148498535156,-89.61820983886719,-62.15864181518555,60.54630661010742,0.6682990789413452,-1.0251215696334839,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1835,5.528101,1381071,7,True,True,True,False,0,624368020,273.267333984375,483.85614013671875,467.7381286621094,-57.31275177001953,-89.61824035644531,-62.159114837646484,60.546241760253906,0.6682876944541931,-1.0251350402832031,-0.869085967540741,1.2314465045928955,0.5476495623588562 +1839,5.53607,1381072,7,True,True,True,False,0,624368028,273.26715087890625,483.85595703125,467.7384033203125,-57.31136703491211,-89.6181869506836,-62.16048812866211,60.54624557495117,0.6682536602020264,-1.0251014232635498,-0.86910480260849,1.2314465045928955,0.5476329922676086 +1841,5.544085,1381073,7,True,True,True,False,0,624368036,273.2668151855469,483.8565673828125,467.7381286621094,-57.31543731689453,-89.61820983886719,-62.15635299682617,60.54630661010742,0.6682990789413452,-1.0251350402832031,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1843,5.55211,1381074,7,True,True,True,False,0,624368044,273.2673034667969,483.85650634765625,467.73797607421875,-57.31307601928711,-89.6182632446289,-62.15876388549805,60.546260833740234,0.6683217287063599,-1.0251483917236328,-0.869085967540741,1.2314465045928955,0.5476661920547485 +1846,5.560074,1381075,7,True,True,True,False,0,624368052,273.2668151855469,483.8565673828125,467.7381591796875,-57.31458282470703,-89.61820983886719,-62.15720748901367,60.54630661010742,0.6682990789413452,-1.0251282453536987,-0.869085967540741,1.2314465045928955,0.5476164221763611 +1849,5.568104,1381076,7,True,True,True,False,0,624368060,273.2673034667969,483.8562316894531,467.73822021484375,-57.3123893737793,-89.61821746826172,-62.159461975097656,60.54624557495117,0.6682933568954468,-1.0251215696334839,-0.869085967540741,1.2314465045928955,0.5476329922676086 +1851,5.576105,1381077,7,True,True,True,False,0,624368068,273.2671813964844,483.85626220703125,467.7379455566406,-57.31706237792969,-89.61824798583984,-62.15477752685547,60.546260833740234,0.6682876944541931,-1.0251551866531372,-0.8690953850746155,1.2314382791519165,0.5476495623588562 +1855,5.584113,1381078,7,True,True,True,False,0,624368076,273.2667236328125,483.8564453125,467.7377014160156,-57.320770263671875,-89.61824035644531,-62.15102005004883,60.54630661010742,0.668276309967041,-1.0251820087432861,-0.8690953850746155,1.2314465045928955,0.5476329922676086 +1857,5.592097,1381079,7,True,True,True,False,0,624368084,273.2671203613281,483.85614013671875,467.7381896972656,-57.31837463378906,-89.61820220947266,-62.153465270996094,60.546260833740234,0.6682707071304321,-1.0251215696334839,-0.86910480260849,1.2314218282699585,0.5476164221763611 +1859,5.600093,1381080,7,True,True,True,False,0,624368092,273.2668151855469,483.8565979003906,467.7376708984375,-57.32388687133789,-89.61825561523438,-62.14790725708008,60.54630661010742,0.6682990789413452,-1.0251820087432861,-0.8690953850746155,1.2314218282699585,0.5476329922676086 +1862,5.608195,1381081,7,True,True,True,False,0,624368100,273.26739501953125,483.8563537597656,467.7374267578125,-57.326541900634766,-89.61827850341797,-62.14530944824219,60.54624557495117,0.6683104038238525,-1.0252090692520142,-0.869085967540741,1.2314218282699585,0.5476329922676086 +1866,5.616183,1381082,7,True,True,True,False,0,624368108,273.26739501953125,483.85638427734375,467.7378234863281,-57.32057189941406,-89.61825561523438,-62.15127944946289,60.54624557495117,0.6683160662651062,-1.025161862373352,-0.869085967540741,1.2314218282699585,0.5476329922676086 +1868,5.624187,1381083,7,True,True,True,False,0,624368116,273.2668762207031,483.8566589355469,467.7379150390625,-57.32334518432617,-89.61824035644531,-62.14844512939453,60.54630661010742,0.6683160662651062,-1.0251483917236328,-0.869085967540741,1.2313971519470215,0.5476164221763611 +1870,5.632105,1381084,7,True,True,True,False,0,624368124,273.26690673828125,483.85675048828125,467.7379150390625,-57.31913757324219,-89.61825561523438,-62.152652740478516,60.54630661010742,0.6683273911476135,-1.0251551866531372,-0.8690953850746155,1.2314218282699585,0.5476495623588562 +1873,5.640167,1381085,7,True,True,True,False,0,624368132,273.2671203613281,483.85614013671875,467.73828125,-57.315887451171875,-89.61824035644531,-62.15595245361328,60.546260833740234,0.668276309967041,-1.0251080989837646,-0.869085967540741,1.2314053773880005,0.5476329922676086 +1876,5.64817,1381086,7,True,True,True,False,0,624368140,273.267333984375,483.85626220703125,467.73809814453125,-57.31978225708008,-89.61823272705078,-62.15207290649414,60.54624557495117,0.6682990789413452,-1.0251282453536987,-0.869085967540741,1.2314053773880005,0.5476164221763611 +1878,5.656102,1381087,7,True,True,True,False,0,624368148,273.26739501953125,483.8562316894531,467.73797607421875,-57.322486877441406,-89.61827087402344,-62.149375915527344,60.546241760253906,0.6682990789413452,-1.025141716003418,-0.8690766096115112,1.2313807010650635,0.5476329922676086 +1881,5.664125,1381088,7,True,True,True,False,0,624368156,273.266845703125,483.8566589355469,467.73785400390625,-57.32476806640625,-89.61827850341797,-62.14702606201172,60.54630661010742,0.6683104038238525,-1.0251551866531372,-0.8690671920776367,1.2313807010650635,0.5476164221763611 +1884,5.672123,1381089,7,True,True,True,False,0,624368164,273.2672119140625,483.8563232421875,467.7377624511719,-57.323211669921875,-89.6182861328125,-62.14862823486328,60.546260833740234,0.6682933568954468,-1.0251686573028564,-0.869085967540741,1.2313971519470215,0.5476495623588562 +1886,5.680127,1381090,7,True,True,True,False,0,624368172,273.266845703125,483.8566589355469,467.7377014160156,-57.332923889160156,-89.61827850341797,-62.138877868652344,60.54630661010742,0.6683047413825989,-1.0251686573028564,-0.8690953850746155,1.231347680091858,0.5476164221763611 +1890,5.688125,1381091,7,True,True,True,False,0,624368180,273.26727294921875,483.8564453125,467.7376403808594,-57.33273696899414,-89.61827850341797,-62.139102935791016,60.546260833740234,0.6683104038238525,-1.0251753330230713,-0.8690953850746155,1.231355905532837,0.5476164221763611 +1892,5.696116,1381092,15,True,True,True,True,0,624368188,273.2674255371094,483.8563232421875,467.7378234863281,-57.32779312133789,-89.61827087402344,-62.144065856933594,60.546241760253906,0.6683104038238525,-1.0251551866531372,-0.8690953850746155,1.231364130973816,0.5476329922676086 +1894,5.704159,1381093,15,True,True,True,True,0,624368196,273.266845703125,483.85662841796875,467.73785400390625,-57.32767868041992,-89.61829376220703,-62.14411544799805,60.54630661010742,0.6683047413825989,-1.0251483917236328,-0.8690953850746155,1.231347680091858,0.5476495623588562 +1897,5.712126,1381094,15,True,True,True,True,0,624368204,273.2672424316406,483.8564147949219,467.73785400390625,-57.325984954833984,-89.61827850341797,-62.14585494995117,60.546260833740234,0.6683104038238525,-1.0251551866531372,-0.869085967540741,1.2313724756240845,0.5476329922676086 +1900,5.720132,1381095,15,True,True,True,True,0,624368212,273.26727294921875,483.8564453125,467.73785400390625,-57.327232360839844,-89.61824798583984,-62.14460754394531,60.546260833740234,0.6683160662651062,-1.0251551866531372,-0.8691142201423645,1.2313807010650635,0.5476329922676086 +1902,5.728129,1381096,15,True,True,True,True,0,624368220,273.2685852050781,483.8561706542969,467.73638916015625,-57.340484619140625,-89.61836242675781,-62.13148498535156,60.5461311340332,0.6683557629585266,-1.0253300666809082,-0.869085967540741,1.2314218282699585,0.5476495623588562 +1904,5.736156,1381097,15,True,True,True,True,0,624368228,273.2785339355469,483.8548278808594,467.722412109375,-57.53761672973633,-89.61921691894531,-61.9353141784668,60.545166015625,0.6687417030334473,-1.02692449092865,-0.8690484166145325,1.2314465045928955,0.5476329922676086 +1907,5.744107,1381098,15,True,True,True,True,0,624368236,273.3247985839844,483.8434753417969,467.6722106933594,-58.19935989379883,-89.62213134765625,-61.27832794189453,60.54043960571289,0.670046865940094,-1.0327502489089966,-0.8689167499542236,1.2320151329040527,0.5476329922676086 +1910,5.752141,1381099,15,True,True,True,True,0,624368244,273.4884033203125,483.78704833984375,467.5223693847656,-59.996490478515625,-89.62932586669922,-59.49882888793945,60.52289962768555,0.6730204224586487,-1.050510287284851,-0.8687663674354553,1.2360113859176636,0.5476164221763611 +1912,5.760141,1381100,15,True,True,True,True,0,624368252,273.7647705078125,483.6991271972656,467.2921142578125,-62.48661422729492,-89.63948822021484,-57.03811264038086,60.493656158447266,0.6790583729743958,-1.0782265663146973,-0.8672434687614441,1.2449103593826294,0.5473508834838867 +1916,5.768143,1381101,15,True,True,True,True,0,624368260,274.1639099121094,483.5526428222656,466.9590148925781,-65.8532943725586,-89.65373992919922,-53.714874267578125,60.45043182373047,0.6855729818344116,-1.1187852621078491,-0.863426923751831,1.2605741024017334,0.5473011136054993 +1918,5.776192,1381102,15,True,True,True,True,0,624368268,274.7937316894531,483.3236999511719,466.4605407714844,-71.04737091064453,-89.67269897460938,-48.589195251464844,60.382362365722656,0.6964458227157593,-1.1798285245895386,-0.8559252619743347,1.2862738370895386,0.5461893081665039 +1920,5.784158,1381103,15,True,True,True,True,0,624368276,275.59112548828125,483.0283508300781,465.80462646484375,-78.20381164550781,-89.70630645751953,-41.519309997558594,60.2958984375,0.7096452713012695,-1.2601993083953857,-0.831756591796875,1.3203781843185425,0.5440652966499329 +1924,5.792142,1381104,15,True,True,True,True,0,624368284,276.65875244140625,482.6177062988281,464.96844482421875,-89.27861785888672,-89.75617980957031,-30.560762405395508,60.17937088012695,0.7263121008872986,-1.3630256652832031,-0.7865495681762695,1.3662240505218506,0.5425220131874084 +1926,5.800127,1381105,15,True,True,True,True,0,624368292,278.0359191894531,482.0716552734375,463.9055480957031,-109.93370819091797,-89.7962875366211,-10.056289672851562,60.0283203125,0.7467865943908691,-1.4937095642089844,-0.7323086857795715,1.4243719577789307,0.5405473709106445 +1928,5.808144,1381106,15,True,True,True,True,0,624368300,279.72076416015625,481.3811950683594,462.6033630371094,-139.40467834472656,-89.79389190673828,19.22947883605957,59.842525482177734,0.7703765630722046,-1.6541279554367065,-0.6711301803588867,1.4977221488952637,0.5367307066917419 +1931,5.816125,1381107,15,True,True,True,True,0,624368308,281.76556396484375,480.5337219238281,461.0092468261719,-165.0458526611328,-89.72872924804688,44.64578628540039,59.61675262451172,0.7994880676269531,-1.8506652116775513,-0.6007673740386963,1.5886642932891846,0.5304582715034485 +1934,5.824121,1381108,15,True,True,True,True,0,624368316,284.2015380859375,479.4874572753906,459.1225891113281,-177.99362182617188,-89.61547088623047,57.324310302734375,59.346168518066406,0.8324527144432068,-2.083745002746582,-0.5205905437469482,1.6995218992233276,0.5067952871322632 +1936,5.83214,1381109,15,True,True,True,True,0,624368324,287.0381774902344,478.2406921386719,456.8950500488281,174.46780395507812,-89.46788787841797,64.54871368408203,59.029945373535156,0.8707743883132935,-2.358654737472534,-0.4279955327510834,1.8290753364562988,0.4789506793022156 +1940,5.840104,1381110,15,True,True,True,True,0,624368332,290.3128662109375,476.7511291503906,454.2994079589844,168.35073852539062,-89.2879867553711,70.3016586303711,58.66269302368164,0.9137038588523865,-2.6792428493499756,-0.32318931818008423,1.9819226264953613,0.4666379988193512 +1942,5.848166,1381111,15,True,True,True,True,0,624368340,294.02252197265625,474.9956359863281,451.3618469238281,164.06813049316406,-89.08079528808594,74.16971588134766,58.24357223510742,0.9605547189712524,-3.0425894260406494,-0.20394392311573029,2.1587064266204834,0.4560178518295288 +1944,5.856119,1381112,15,True,True,True,True,0,624368348,298.1674499511719,472.95843505859375,448.10479736328125,161.06631469726562,-88.85243225097656,76.70613098144531,57.7718391418457,1.0119168758392334,-3.4463400840759277,-0.07095500081777573,2.3608686923980713,0.44324055314064026 +1946,5.864141,1381113,15,True,True,True,True,0,624368356,302.6752624511719,470.6452331542969,444.5804138183594,158.43492126464844,-88.60486602783203,78.8283920288086,57.25416564941406,1.065883755683899,-3.884305477142334,0.07360594719648361,2.5868518352508545,0.43942394852638245 +1950,5.872141,1381114,15,True,True,True,True,0,624368364,307.4538879394531,468.0850524902344,440.76470947265625,156.61636352539062,-88.3328628540039,80.1037826538086,56.70016860961914,1.1214340925216675,-4.357804298400879,0.22570611536502838,2.8276500701904297,0.4366859495639801 +1952,5.880128,1381115,15,True,True,True,True,0,624368372,312.54559326171875,465.2855529785156,436.6540222167969,155.40789794921875,-88.04191589355469,80.73186492919922,56.10670471191406,1.1846791505813599,-4.867832183837891,0.3783138692378998,3.0874905586242676,0.43799686431884766 +1954,5.888166,1381116,15,True,True,True,True,0,624368380,317.9097900390625,462.20635986328125,432.3655700683594,154.61227416992188,-87.74273681640625,80.9108657836914,55.47505187988281,1.2506256103515625,-5.400295734405518,0.5259769558906555,3.3612561225891113,0.44421958923339844 +1957,5.896084,1381117,15,True,True,True,True,0,624368388,323.47869873046875,458.9455871582031,427.7984924316406,153.97012329101562,-87.41947937011719,80.91169738769531,54.816650390625,1.3271950483322144,-5.96644926071167,0.6634969711303711,3.648073673248291,0.47261178493499756 +1960,5.904146,1381118,15,True,True,True,True,0,624368396,329.1320495605469,455.55084228515625,423.0604553222656,153.22328186035156,-87.08370208740234,81.00502014160156,54.144508361816406,1.4119588136672974,-6.554661750793457,0.7815203070640564,3.9516751766204834,0.5375272631645203 +1962,5.912135,1381119,15,True,True,True,True,0,624368404,334.8794250488281,451.9687805175781,418.2738342285156,152.4069366455078,-86.74020385742188,81.151123046875,53.45469284057617,1.5007177591323853,-7.149332046508789,0.8773490190505981,4.261135101318359,0.6395467519760132 +1966,5.920138,1381120,15,True,True,True,True,0,624368412,340.6769714355469,448.23199462890625,413.4272155761719,151.75387573242188,-86.38745880126953,81.12174987792969,52.752811431884766,1.5945271253585815,-7.750655651092529,0.943274736404419,4.569367408752441,0.775334894657135 +1968,5.928128,1381121,15,True,True,True,True,0,624368420,346.3681640625,444.45758056640625,408.5573425292969,151.51487731933594,-86.03842163085938,80.68402099609375,52.05881881713867,1.6933870315551758,-8.35399055480957,0.9704610109329224,4.873315811157227,0.9297912120819092 +1970,5.936171,1381122,15,True,True,True,True,0,624368428,351.9791259765625,440.62078857421875,403.7864990234375,151.09500122070312,-85.69811248779297,80.4296646118164,51.3688850402832,1.7955553531646729,-8.946932792663574,0.9646890759468079,5.182519912719727,1.1273421049118042 +1974,5.944137,1381123,15,True,True,True,True,0,624368436,357.5350341796875,436.73199462890625,399.1541748046875,150.30325317382812,-85.35610961914062,80.54730224609375,50.68179702758789,1.9031997919082642,-9.524118423461914,0.9131649136543274,5.491930961608887,1.4016231298446655 +1976,5.952133,1381124,15,True,True,True,True,0,624368444,362.8775939941406,432.9254150390625,394.64105224609375,149.75711059570312,-85.02234649658203,80.43700408935547,50.018863677978516,2.016110420227051,-10.08546257019043,0.800772488117218,5.787210464477539,1.7211724519729614 +1978,5.960136,1381125,15,True,True,True,True,0,624368452,367.9814453125,429.2303161621094,390.2540283203125,149.51394653320312,-84.70536041259766,80.04351806640625,49.38399124145508,2.1334757804870605,-10.63040542602539,0.6246352791786194,6.069759368896484,2.075950860977173 +1981,5.968124,1381126,15,True,True,True,True,10,624368460,372.9642028808594,425.5733642578125,386.0030822753906,149.23837280273438,-84.3915023803711,79.68795776367188,48.76339340209961,2.256481647491455,-11.157909393310547,0.37860527634620667,6.3406662940979,2.5025806427001953 +1984,5.976105,1381127,15,True,True,True,True,10,624368468,377.70684814453125,422.0657043457031,381.9544982910156,148.9918975830078,-84.09466552734375,79.32386779785156,48.17357635498047,2.383596181869507,-11.660605430603027,0.06426183879375458,6.601561069488525,2.98694109916687 +1986,5.984093,1381128,15,True,True,True,True,0,624368476,382.1481018066406,418.8027648925781,378.1485290527344,148.88613891601562,-83.8288803100586,78.84828186035156,47.625701904296875,2.5161473751068115,-12.133882522583008,-0.32370635867118835,6.853367805480957,3.515756368637085 +1988,5.99214,1381129,15,True,True,True,True,0,624368484,386.42279052734375,415.64910888671875,374.5618896484375,148.709228515625,-83.56887817382812,78.45539093017578,47.100399017333984,2.6512858867645264,-12.578971862792969,-0.7789633274078369,7.087071895599365,4.1142497062683105 +1992,6.000187,1381130,15,True,True,True,True,0,624368492,390.49395751953125,412.64453125,371.177978515625,148.64344787597656,-83.32444763183594,77.96694946289062,46.60310363769531,2.78839373588562,-12.99767780303955,-1.3010486364364624,7.302466869354248,4.759206295013428 +1994,6.008127,1381131,15,True,True,True,True,0,624368500,394.28955078125,409.8834533691406,368.0046691894531,148.63108825683594,-83.10030364990234,77.4520263671875,46.14557647705078,2.9282708168029785,-13.390111923217773,-1.88760244846344,7.506762981414795,5.450326919555664 +1996,6.016138,1381132,15,True,True,True,True,0,624368508,397.86724853515625,407.330322265625,365.04266357421875,148.55178833007812,-82.88404846191406,77.02538299560547,45.72103500366211,3.071070432662964,-13.755476951599121,-2.535701274871826,7.696375846862793,6.203342914581299 +2000,6.024124,1381133,15,True,True,True,True,0,624368516,401.28277587890625,404.9466857910156,362.2890625,148.4419708251953,-82.68225860595703,76.64336395263672,45.322689056396484,3.2170023918151855,-14.094767570495605,-3.2422337532043457,7.8761677742004395,7.0082316398620605 +2002,6.032137,1381134,15,True,True,True,True,0,624368524,404.5137634277344,402.7698974609375,359.7115173339844,148.347412109375,-82.49098205566406,76.26493072509766,44.95466995239258,3.3669521808624268,-14.411062240600586,-4.003411293029785,8.043169975280762,7.858405113220215 +2004,6.040138,1381135,15,True,True,True,True,0,624368532,407.5632019042969,400.80615234375,357.3108215332031,148.24440002441406,-82.30818939208984,75.91423797607422,44.61711883544922,3.5206246376037598,-14.70416259765625,-4.818228721618652,8.197385787963867,8.756451606750488 +2007,6.048116,1381136,15,True,True,True,True,0,624368540,410.4690856933594,399.0074462890625,355.0981140136719,148.09732055664062,-82.13225555419922,75.62155151367188,44.30378723144531,3.6759486198425293,-14.972823143005371,-5.683225154876709,8.339141845703125,9.704081535339355 +2010,6.05613,1381137,15,True,True,True,True,0,624368548,413.1831359863281,397.4249572753906,353.0829772949219,147.95535278320312,-81.9682846069336,75.3446273803711,44.02156066894531,3.8319530487060547,-15.215873718261719,-6.5959672927856445,8.468489646911621,10.690674781799316 +2012,6.064156,1381138,15,True,True,True,True,0,624368556,415.70672607421875,396.0842590332031,351.2968444824219,147.76695251464844,-81.81732940673828,75.13690948486328,43.77206802368164,3.989421844482422,-15.429766654968262,-7.556397914886475,8.587026596069336,11.721870422363281 +2016,6.072172,1381139,15,True,True,True,True,0,624368564,418.0942077636719,394.9440612792969,349.6893615722656,147.6433563232422,-81.67891693115234,74.88111877441406,43.54827880859375,4.150034427642822,-15.619564056396484,-8.560230255126953,8.691168785095215,12.780381202697754 +2018,6.080128,1381140,15,True,True,True,True,0,624368572,420.3145751953125,394.0252685546875,348.25946044921875,147.56333923339844,-81.55667114257812,74.6012191772461,43.35384750366211,4.312162399291992,-15.786298751831055,-9.609636306762695,8.786808013916016,13.868578910827637 +2020,6.088145,1381141,15,True,True,True,True,0,624368580,422.3955993652344,393.30853271484375,346.970703125,147.34823608398438,-81.43177795410156,74.47537231445312,43.185245513916016,4.477036476135254,-15.934568405151367,-10.696908950805664,8.877189636230469,15.011468887329102 +2024,6.096166,1381142,15,True,True,True,True,0,624368588,424.3355712890625,392.7925109863281,345.8460693359375,147.15875244140625,-81.31548309326172,74.34284210205078,43.04213333129883,4.6432785987854,-16.060636520385742,-11.820785522460938,8.95559024810791,16.182186126708984 +2026,6.104138,1381143,15,True,True,True,True,0,624368596,426.1522216796875,392.4553527832031,344.87890625,146.98069763183594,-81.2046127319336,74.21609497070312,42.92147445678711,4.810388565063477,-16.165111541748047,-12.979811668395996,9.021195411682129,17.38297462463379 +2028,6.112161,1381144,15,True,True,True,True,0,624368604,427.83416748046875,392.3031005859375,344.0580749511719,146.86180114746094,-81.10663604736328,74.04752349853516,42.82440185546875,4.9778056144714355,-16.249977111816406,-14.173844337463379,9.07845401763916,18.603275299072266 +2030,6.12015,1381145,15,True,True,True,True,0,624368612,429.3743896484375,392.36328125,343.4001770019531,146.68017578125,-81.01210021972656,73.96356964111328,42.75309753417969,5.146578311920166,-16.31325340270996,-15.398590087890625,9.127908706665039,19.860198974609375 +2034,6.12815,1381146,15,True,True,True,True,0,624368620,430.79632568359375,392.6013488769531,342.86407470703125,146.53062438964844,-80.92230224609375,73.86590576171875,42.703155517578125,5.31667947769165,-16.35927391052246,-16.652748107910156,9.166932106018066,21.139558792114258 +2036,6.13616,1381147,15,True,True,True,True,0,624368628,432.0796813964844,393.04339599609375,342.464111328125,146.38851928710938,-80.83846282958984,73.78276824951172,42.6776123046875,5.487955093383789,-16.386640548706055,-17.934478759765625,9.197352409362793,22.44228172302246 +2038,6.144137,1381148,15,True,True,True,True,0,624368636,433.24658203125,393.6669921875,342.19647216796875,146.27760314941406,-80.761962890625,73.68785095214844,42.673091888427734,5.660523891448975,-16.395641326904297,-19.243085861206055,9.218487739562988,23.763954162597656 +2041,6.152135,1381149,15,True,True,True,True,0,624368644,434.3384094238281,394.3977355957031,342.0519104003906,146.2025909423828,-80.6921157836914,73.56926727294922,42.68122100830078,5.832083225250244,-16.387596130371094,-20.575389862060547,9.230476379394531,25.10131072998047 +2044,6.160139,1381150,15,True,True,True,True,0,624368652,435.2576904296875,395.3786315917969,342.0408935546875,146.08148193359375,-80.6261978149414,73.52596282958984,42.718631744384766,6.0054755210876465,-16.36172103881836,-21.92779541015625,9.237817764282227,26.463539123535156 +2046,6.168173,1381151,15,True,True,True,True,0,624368660,436.09991455078125,396.4917907714844,342.15283203125,146.0391387939453,-80.5746841430664,73.4177017211914,42.7702522277832,6.179866313934326,-16.31904411315918,-23.300569534301758,9.23870849609375,27.8282413482666 +2050,6.176104,1381152,15,True,True,True,True,0,624368668,436.80743408203125,397.7814636230469,342.3969421386719,145.93121337890625,-80.52394104003906,73.3980712890625,42.842830657958984,6.353451728820801,-16.258869171142578,-24.690038681030273,9.234308242797852,29.21946144104004 +2052,6.184148,1381153,15,True,True,True,True,0,624368676,437.39208984375,399.2408142089844,342.72003173828125,145.8020477294922,-80.46932220458984,73.42326354980469,42.93464660644531,6.528137683868408,-16.186861038208008,-26.09326171875,9.221940994262695,30.63102149963379 +2054,6.192145,1381154,15,True,True,True,True,0,624368684,437.8929748535156,400.8126525878906,343.14996337890625,145.73565673828125,-80.42434692382812,73.40169525146484,43.038848876953125,6.702125549316406,-16.09999656677246,-27.509729385375977,9.201654434204102,32.04353332519531 +2057,6.200162,1381155,15,True,True,True,True,0,624368692,438.2629699707031,402.5342712402344,343.6673583984375,145.68429565429688,-80.38233947753906,73.38814544677734,43.16096115112305,6.874791622161865,-16.000852584838867,-28.935325622558594,9.174537658691406,33.46330642700195 +2060,6.208126,1381156,15,True,True,True,True,0,624368700,438.4931945800781,404.43389892578125,344.3065490722656,145.76690673828125,-80.36199188232422,73.26445770263672,43.3033447265625,7.047105312347412,-15.885337829589844,-30.372041702270508,9.140424728393555,34.86549758911133 +2062,6.216151,1381157,15,True,True,True,True,0,624368708,438.6140441894531,406.4583435058594,345.0696716308594,145.80007934570312,-80.3416748046875,73.21244049072266,43.45988464355469,7.217483997344971,-15.753320693969727,-31.81559181213379,9.098039627075195,36.28412628173828 +2066,6.224177,1381158,15,True,True,True,True,0,624368716,438.5950012207031,408.6329650878906,345.93963623046875,145.722900390625,-80.3148193359375,73.29727935791016,43.634281158447266,7.385939598083496,-15.60728645324707,-33.262237548828125,9.050025939941406,37.727718353271484 +2068,6.232125,1381159,15,True,True,True,True,0,624368724,438.4648742675781,410.9376220703125,346.8817138671875,145.74942016601562,-80.30580139160156,73.29627990722656,43.82344055175781,7.554593086242676,-15.451711654663086,-34.71428298950195,9.001124382019043,39.1540641784668 +2070,6.240161,1381160,15,True,True,True,True,0,624368732,438.2197570800781,413.3581848144531,347.8817138671875,145.77467346191406,-80.2942123413086,73.32085418701172,44.0257453918457,7.722293853759766,-15.28743839263916,-36.16422653198242,8.943116188049316,40.5828857421875 +2072,6.248182,1381161,15,True,True,True,True,0,624368740,437.8531188964844,415.90325927734375,348.950439453125,145.86279296875,-80.29293060302734,73.3044204711914,44.24242401123047,7.889251232147217,-15.113821029663086,-37.6144905090332,8.880345344543457,41.99999237060547 +2076,6.256162,1381162,15,True,True,True,True,0,624368748,437.39007568359375,418.5479431152344,350.0783996582031,145.999267578125,-80.30313110351562,73.25626373291016,44.47029495239258,8.056144714355469,-14.932547569274902,-39.062232971191406,8.817163467407227,43.4035530090332 +2078,6.264123,1381163,15,True,True,True,True,0,624368756,436.8180236816406,421.2857666015625,351.25732421875,146.03274536132812,-80.30371856689453,73.3333511352539,44.70924377441406,8.2216157913208,-14.744413375854492,-40.50140380859375,8.74975299835205,44.8212890625 +2080,6.272161,1381164,15,True,True,True,True,0,624368764,436.1382751464844,424.1145935058594,352.5319519042969,146.08364868164062,-80.3142318725586,73.41333770751953,44.958892822265625,8.384878158569336,-14.544269561767578,-41.93402099609375,8.677145004272461,46.2277946472168 +2084,6.280138,1381165,15,True,True,True,True,0,624368772,435.364990234375,426.998046875,353.8595886230469,146.14169311523438,-80.33174896240234,73.50112915039062,45.216148376464844,8.545258522033691,-14.337883949279785,-43.35618591308594,8.604042053222656,47.621402740478516 +2086,6.288133,1381166,15,True,True,True,True,0,624368780,434.4873046875,429.94091796875,355.2446594238281,146.26866149902344,-80.36176300048828,73.53718566894531,45.481727600097656,8.702256202697754,-14.124521255493164,-44.767024993896484,8.527215003967285,48.98991775512695 +2088,6.296176,1381167,15,True,True,True,True,0,624368788,433.50177001953125,432.94256591796875,356.66546630859375,146.4277801513672,-80.39852905273438,73.55859375,45.755859375,8.85623550415039,-13.90693473815918,-46.164146423339844,8.447906494140625,50.339107513427734 +2091,6.304086,1381168,15,True,True,True,True,0,624368796,432.42132568359375,436.0102844238281,358.10748291015625,146.54014587402344,-80.43146514892578,73.64607238769531,46.03797912597656,9.009527206420898,-13.68648910522461,-47.54410934448242,8.365007400512695,51.68254852294922 +2094,6.312155,1381169,15,True,True,True,True,0,624368804,431.24591064453125,439.13739013671875,359.6043701171875,146.6481475830078,-80.47071075439453,73.75541687011719,46.32768249511719,9.161088943481445,-13.45959186553955,-48.90668487548828,8.279099464416504,53.008689880371094 +2096,6.320144,1381170,15,True,True,True,True,0,624368812,429.9504089355469,442.3418273925781,361.1777038574219,146.7618865966797,-80.5194091796875,73.8804931640625,46.62755584716797,9.310158729553223,-13.223747253417969,-50.25043487548828,8.188157081604004,54.31343460083008 +2100,6.328165,1381171,15,True,True,True,True,0,624368820,428.5358581542969,445.6173400878906,362.7878112792969,146.8738250732422,-80.57405853271484,74.02735900878906,46.937286376953125,9.457362174987793,-12.983920097351074,-51.56940841674805,8.094669342041016,55.593177795410156 +2102,6.336174,1381172,15,True,True,True,True,0,624368828,427.027099609375,448.92169189453125,364.40997314453125,147.0861053466797,-80.64000701904297,74.09032440185547,47.25221633911133,9.601676940917969,-12.742814064025879,-52.86658477783203,7.995412826538086,56.83383560180664 +2104,6.344173,1381173,15,True,True,True,True,0,624368836,425.4496154785156,452.2221984863281,366.0611267089844,147.26048278808594,-80.71073150634766,74.19912719726562,47.56948471069336,9.742308616638184,-12.499616622924805,-54.13829803466797,7.899296760559082,58.053646087646484 +2107,6.352151,1381174,15,True,True,True,True,0,624368844,423.8023376464844,455.5244140625,367.742431640625,147.34011840820312,-80.78251647949219,74.41011810302734,47.889774322509766,9.87980842590332,-12.25442886352539,-55.37930679321289,7.808412551879883,59.25680160522461 +2110,6.360139,1381175,15,True,True,True,True,0,624368852,422.09588623046875,458.8166809082031,369.4670104980469,147.41830444335938,-80.86341094970703,74.6314468383789,48.21109390258789,10.013816833496094,-12.005243301391602,-56.589813232421875,7.716670989990234,60.42707443237305 +2112,6.368099,1381176,15,True,True,True,True,0,624368860,420.3311767578125,462.1021728515625,371.2565612792969,147.41867065429688,-80.94939422607422,74.94022369384766,48.533653259277344,10.144490242004395,-11.74969482421875,-57.76535415649414,7.624286651611328,61.5726203918457 +2114,6.376155,1381177,15,True,True,True,True,0,624368868,418.4712219238281,465.38629150390625,373.0682373046875,147.44155883789062,-81.04385375976562,75.23896789550781,48.860252380371094,10.269964218139648,-11.492908477783203,-58.906864166259766,7.529685974121094,62.6784553527832 +2117,6.384143,1381178,15,True,True,True,True,0,624368876,416.53021240234375,468.67449951171875,374.86798095703125,147.57382202148438,-81.15202331542969,75.43856811523438,49.19041442871094,10.392681121826172,-11.238603591918945,-60.01230239868164,7.433783531188965,63.72785186767578 +2120,6.392136,1381179,15,True,True,True,True,0,624368884,414.5166015625,471.9379577636719,376.6748962402344,147.68167114257812,-81.26396179199219,75.67036437988281,49.52211380004883,10.510375022888184,-10.985167503356934,-61.07887268066406,7.33848237991333,64.7411117553711 +2122,6.40014,1381180,15,True,True,True,True,0,624368892,412.4656982421875,475.1587219238281,378.4493408203125,147.75372314453125,-81.37388610839844,75.9426040649414,49.8519172668457,10.624926567077637,-10.736930847167969,-62.10367965698242,7.2436017990112305,65.71929168701172 +2126,6.408188,1381181,15,True,True,True,True,0,624368900,410.3647155761719,478.3320617675781,380.22332763671875,147.75225830078125,-81.48541259765625,76.2884750366211,50.18128204345703,10.734392166137695,-10.491035461425781,-63.08574676513672,7.155436992645264,66.66353607177734 +2128,6.416138,1381182,15,True,True,True,True,0,624368908,408.22760009765625,481.46337890625,382.0036315917969,147.69760131835938,-81.59814453125,76.69326782226562,50.508724212646484,10.840214729309082,-10.245866775512695,-64.02227783203125,7.065549373626709,67.56946563720703 +2130,6.424181,1381183,15,True,True,True,True,0,624368916,406.0491638183594,484.5352478027344,383.7657470703125,147.6367950439453,-81.71424102783203,77.10696411132812,50.833961486816406,10.940589904785156,-10.004781723022461,-64.91065216064453,6.9761810302734375,68.42652130126953 +2134,6.432118,1381184,15,True,True,True,True,0,624368924,403.81573486328125,487.5577392578125,385.4897766113281,147.5634307861328,-81.83056640625,77.53746032714844,51.15868377685547,11.035540580749512,-9.770088195800781,-65.74954223632812,6.887389659881592,69.23546600341797 +2136,6.440151,1381185,15,True,True,True,True,0,624368932,401.5471496582031,490.53363037109375,387.1794738769531,147.577880859375,-81.95741271972656,77.8813705444336,51.48185348510742,11.12689208984375,-9.541125297546387,-66.53753662109375,6.799867153167725,69.97724151611328 +2138,6.448163,1381186,15,True,True,True,True,0,624368940,399.25885009765625,493.4348449707031,388.85968017578125,147.55923461914062,-82.0867919921875,78.2577896118164,51.80093002319336,11.212728500366211,-9.31539249420166,-67.27291107177734,6.713621616363525,70.66927337646484 +2141,6.45614,1381187,15,True,True,True,True,0,624368948,396.98583984375,496.2500915527344,390.504150390625,147.43955993652344,-82.21185302734375,78.72771453857422,52.1135368347168,11.294966697692871,-9.095914840698242,-67.9538803100586,6.633935451507568,71.32013702392578 +2144,6.464131,1381188,15,True,True,True,True,0,624368956,394.7198486328125,498.9725341796875,392.1405029296875,147.25770568847656,-82.3407211303711,79.25012969970703,52.42024612426758,11.371803283691406,-8.880016326904297,-68.5748291015625,6.562859058380127,71.91490173339844 +2146,6.472141,1381189,15,True,True,True,True,0,624368964,392.4581604003906,501.5931091308594,393.75140380859375,147.0142364501953,-82.46778869628906,79.83089447021484,52.71996307373047,11.441926002502441,-8.669419288635254,-69.13424682617188,6.49228572845459,72.45526885986328 +2150,6.480138,1381190,15,True,True,True,True,0,624368972,390.2139587402344,504.1123352050781,395.3182373046875,146.7534942626953,-82.59598541259766,80.42050170898438,53.0124397277832,11.506516456604004,-8.466276168823242,-69.63382720947266,6.426647663116455,72.93537902832031 +2152,6.488141,1381191,15,True,True,True,True,0,624368980,387.95892333984375,506.5476989746094,396.8497009277344,146.5217742919922,-82.72913360595703,80.97805786132812,53.30036926269531,11.564779281616211,-8.269550323486328,-70.07038879394531,6.362443923950195,73.34532928466797 +2154,6.496147,1381192,15,True,True,True,True,0,624368988,385.7132568359375,508.8798828125,398.3299560546875,146.27288818359375,-82.86175537109375,81.54654693603516,53.5815544128418,11.616283416748047,-8.081260681152344,-70.44070434570312,6.301181316375732,73.68953704833984 +2156,6.504163,1381193,15,True,True,True,True,0,624368996,383.4936218261719,511.1146240234375,399.74517822265625,145.94786071777344,-82.9880599975586,82.18340301513672,53.85536193847656,11.66284465789795,-7.902759552001953,-70.74165344238281,6.244129180908203,73.9737777709961 +2160,6.512147,1381194,15,True,True,True,True,0,624369004,381.3189392089844,513.23095703125,401.1105041503906,145.5918426513672,-83.11348724365234,82.84037017822266,54.11943435668945,11.703378677368164,-7.732471942901611,-70.97406768798828,6.191378116607666,74.19099426269531 +2162,6.52015,1381195,15,True,True,True,True,0,624369012,379.2134094238281,515.20166015625,402.4170227050781,145.17759704589844,-83.23548126220703,83.5379867553711,54.3712272644043,11.736763954162598,-7.571804523468018,-71.13945007324219,6.147254467010498,74.34606170654297 +2164,6.528147,1381196,15,True,True,True,True,0,624369020,377.1607360839844,517.0484619140625,403.6288757324219,144.7174835205078,-83.35176849365234,84.26658630371094,54.61311721801758,11.763917922973633,-7.424921035766602,-71.22237396240234,6.111881256103516,74.423095703125 +2167,6.536187,1381197,15,True,True,True,True,0,624369028,375.1780090332031,518.7578125,404.7783203125,144.20358276367188,-83.46208953857422,85.03894805908203,54.842288970947266,11.784114837646484,-7.287651538848877,-71.23114013671875,6.0767717361450195,74.43223571777344 +2170,6.544144,1381198,15,True,True,True,True,0,624369036,373.2699279785156,520.3297729492188,405.8438415527344,143.53807067871094,-83.55453491210938,85.94750213623047,55.059326171875,11.797677040100098,-7.162288188934326,-71.19242095947266,6.048056125640869,74.41490936279297 +2172,6.552164,1381199,15,True,True,True,True,0,624369044,371.4211120605469,521.7694091796875,406.81927490234375,143.0185089111328,-83.65705108642578,86.69449615478516,55.265621185302734,11.803675651550293,-7.050373554229736,-71.06609344482422,6.026607990264893,74.28738403320312 +2176,6.560139,1381200,15,True,True,True,True,14,624369052,369.6341552734375,523.0882568359375,407.71136474609375,142.55894470214844,-83.76378631591797,87.36663055419922,55.46168899536133,11.803255081176758,-6.950931072235107,-70.85284423828125,6.012831211090088,74.0621566772461 +2178,6.568165,1381201,15,True,True,True,True,14,624369060,367.93017578125,524.26953125,408.5191955566406,142.03929138183594,-83.86351776123047,88.08589172363281,55.64468002319336,11.795923233032227,-6.864007949829102,-70.54788208007812,6.002811908721924,73.752197265625 +2180,6.57618,1381202,15,True,True,True,True,0,624369068,366.3221130371094,525.2999877929688,409.227294921875,141.4567108154297,-83.95429229736328,88.84981536865234,55.813575744628906,11.781112670898438,-6.791569232940674,-70.15971374511719,6.000974178314209,73.36666870117188 +2184,6.584155,1381203,15,True,True,True,True,0,624369076,364.78448486328125,526.208251953125,409.8485107421875,140.79368591308594,-84.03600311279297,89.68006896972656,55.97161102294922,11.759689331054688,-6.731911659240723,-69.69132232666016,6.006486892700195,72.91017150878906 +2186,6.592152,1381204,15,True,True,True,True,0,624369084,363.36181640625,526.9481811523438,410.38812255859375,139.99237060546875,-84.10462951660156,90.6278305053711,56.1134147644043,11.729795455932617,-6.684881210327148,-69.1430892944336,6.021235942840576,72.38996887207031 +2188,6.600171,1381205,15,True,True,True,True,0,624369092,362.04937744140625,527.5388793945312,410.8682861328125,139.18984985351562,-84.17520141601562,91.55628967285156,56.2403450012207,11.692824363708496,-6.647645950317383,-68.52003479003906,6.045551300048828,71.79058074951172 +2191,6.608147,1381206,15,True,True,True,True,0,624369100,360.8418884277344,527.9764404296875,411.2727966308594,138.40859985351562,-84.24820709228516,92.44149780273438,56.35285186767578,11.647869110107422,-6.622358322143555,-67.82499694824219,6.0817975997924805,71.11245727539062 +2194,6.616131,1381207,15,True,True,True,True,0,624369108,359.7380065917969,528.2694702148438,411.62469482421875,137.57058715820312,-84.32015991210938,93.36249542236328,56.45143508911133,11.595541954040527,-6.606219291687012,-67.06427764892578,6.12990140914917,70.3703384399414 +2196,6.624118,1381208,15,True,True,True,True,0,624369116,358.73602294921875,528.413330078125,411.9033508300781,136.70791625976562,-84.39000701904297,94.2889404296875,56.53574752807617,11.535139083862305,-6.601604461669922,-66.24043273925781,6.1880412101745605,69.5649185180664 +2198,6.632125,1381209,15,True,True,True,True,0,624369124,357.82623291015625,528.4244384765625,412.10150146484375,135.92745971679688,-84.4661636352539,95.1170654296875,56.607032775878906,11.467672348022461,-6.609051704406738,-65.35507202148438,6.254222869873047,68.68457794189453 +2201,6.640136,1381210,15,True,True,True,True,0,624369132,357.0440673828125,528.2600708007812,412.24383544921875,135.1002197265625,-84.54151153564453,95.96955108642578,56.66079330444336,11.3909273147583,-6.626279830932617,-64.4129638671875,6.33125638961792,67.74821472167969 +2204,6.648166,1381211,15,True,True,True,True,0,624369140,356.3256530761719,527.9833374023438,412.32037353515625,134.33273315429688,-84.62596893310547,96.74754333496094,56.70487594604492,11.307065963745117,-6.654117584228516,-63.4152946472168,6.4195122718811035,66.74266815185547 +2206,6.656154,1381212,15,True,True,True,True,0,624369148,355.76336669921875,527.5147094726562,412.37017822265625,133.54913330078125,-84.7198257446289,97.51530456542969,56.728492736816406,11.214005470275879,-6.688594818115234,-62.3646354675293,6.520605564117432,65.67560577392578 +2210,6.664164,1381213,15,True,True,True,True,0,624369156,355.2680358886719,526.919921875,412.3659973144531,132.78086853027344,-84.82174682617188,98.25321960449219,56.74129104614258,11.112483978271484,-6.732577800750732,-61.26396942138672,6.632896900177002,64.5486068725586 +2212,6.672138,1381214,15,True,True,True,True,0,624369164,354.8766784667969,526.1853637695312,412.3074035644531,131.9983367919922,-84.93464660644531,98.98212432861328,56.740516662597656,11.003835678100586,-6.786517143249512,-60.116580963134766,6.763957977294922,63.364097595214844 +2214,6.680153,1381215,15,True,True,True,True,0,624369172,354.57916259765625,525.3126220703125,412.183837890625,131.2099151611328,-85.05366516113281,99.70135498046875,56.725990295410156,10.887491226196289,-6.851132392883301,-58.92514419555664,6.905929088592529,62.12801742553711 +2217,6.688141,1381216,15,True,True,True,True,0,624369180,354.4382019042969,524.2609252929688,411.9830017089844,130.50283813476562,-85.18772888183594,100.31534576416016,56.69105911254883,10.763775825500488,-6.927970886230469,-57.69348907470703,7.061264991760254,60.83226013183594 +2220,6.696134,1381217,15,True,True,True,True,0,624369188,354.3689270019531,523.0807495117188,411.7304992675781,129.73739624023438,-85.33185577392578,100.9709701538086,56.644832611083984,10.631916999816895,-7.014658451080322,-56.42061996459961,7.233953475952148,59.487388610839844 +2222,6.704166,1381218,15,True,True,True,True,0,624369196,354.4615478515625,521.73046875,411.39251708984375,129.08172607421875,-85.4976806640625,101.4920654296875,56.5781364440918,10.494372367858887,-7.114619255065918,-55.112239837646484,7.423236846923828,58.080867767333984 +2225,6.712153,1381219,15,True,True,True,True,0,624369204,354.64154052734375,520.2365112304688,411.0166015625,128.53660583496094,-85.6883544921875,101.8909683227539,56.49692916870117,10.348287582397461,-7.222221851348877,-53.76968765258789,7.621740818023682,56.61295700073242 +2228,6.720164,1381220,15,True,True,True,True,0,624369212,354.9488830566406,518.583984375,410.5776062011719,127.97341918945312,-85.8946762084961,102.28762817382812,56.39805603027344,10.195398330688477,-7.340816974639893,-52.397178649902344,7.837819576263428,55.10065460205078 +2230,6.728173,1381221,15,True,True,True,True,0,624369220,355.3533935546875,516.789794921875,410.0983581542969,127.44760131835938,-86.1258316040039,102.63176727294922,56.28422546386719,10.03535270690918,-7.467720031738281,-50.99709701538086,8.06967830657959,53.536888122558594 +2234,6.736179,1381222,15,True,True,True,True,0,624369228,355.857666015625,514.8353881835938,409.579345703125,126.78107452392578,-86.37153625488281,103.09918212890625,56.15435791015625,9.866585731506348,-7.6035637855529785,-49.569374084472656,8.32126235961914,51.93650436401367 +2236,6.744143,1381223,15,True,True,True,True,0,624369236,356.4881896972656,512.7129516601562,409.0340881347656,126.15050506591797,-86.65099334716797,103.51219177246094,56.00559997558594,9.690372467041016,-7.746451377868652,-48.119205474853516,8.591203689575195,50.281856536865234 +2238,6.75215,1381224,15,True,True,True,True,0,624369244,357.2308654785156,510.4277038574219,408.47747802734375,125.37552642822266,-86.95626831054688,104.0539779663086,55.83895492553711,9.506386756896973,-7.8948211669921875,-46.645565032958984,8.879949569702148,48.58409881591797 +2240,6.76018,1381225,15,True,True,True,True,0,624369252,358.0640563964844,507.9783020019531,407.889892578125,124.61909484863281,-87.29595184326172,104.56565856933594,55.65542984008789,9.31325626373291,-8.050745964050293,-45.15478515625,9.18472957611084,46.835960388183594 +2244,6.768153,1381226,15,True,True,True,True,0,624369260,359.0025329589844,505.3606872558594,407.24493408203125,123.46118927001953,-87.64390563964844,105.46624755859375,55.45351028442383,9.112166404724121,-8.217300415039062,-43.647911071777344,9.50778579711914,45.069087982177734 +2246,6.776202,1381227,15,True,True,True,True,0,624369268,360.0196838378906,502.6075439453125,406.5508728027344,122.30107879638672,-88.03536987304688,106.36149597167969,55.2364616394043,8.904861450195312,-8.393163681030273,-42.12688446044922,9.848251342773438,43.24360656738281 +2248,6.784154,1381228,15,True,True,True,True,14,624369276,361.1238098144531,499.6980895996094,405.824951171875,120.3843002319336,-88.45054626464844,108.0033950805664,55.00252151489258,8.69005012512207,-8.577027320861816,-40.59286117553711,10.210430145263672,41.38724136352539 +2251,6.792119,1381229,15,True,True,True,True,14,624369284,362.29522705078125,496.65594482421875,405.07122802734375,117.55949401855469,-88.91580200195312,110.5479507446289,54.75393295288086,8.468990325927734,-8.767899513244629,-39.052391052246094,10.59228515625,39.47419357299805 +2254,6.800152,1381230,15,True,True,True,True,0,624369292,363.5135498046875,493.482421875,404.35858154296875,108.987060546875,-89.3958969116211,118.83901977539062,54.49177932739258,8.239995956420898,-8.957911491394043,-37.505088806152344,10.992497444152832,37.542144775390625 +2256,6.808169,1381231,15,True,True,True,True,0,624369300,364.7895812988281,490.1777648925781,403.6239929199219,51.627967834472656,-89.8045883178711,175.9184112548828,54.21451950073242,8.00464153289795,-9.153371810913086,-35.953617095947266,11.406461715698242,35.57948303222656 +2260,6.816137,1381232,15,True,True,True,True,0,624369308,366.11529541015625,486.7398681640625,402.86859130859375,-30.654098510742188,-89.4674301147461,-102.08023834228516,53.922908782958984,7.762562274932861,-9.355291366577148,-34.398193359375,11.843152046203613,33.607723236083984 +2262,6.824145,1381233,15,True,True,True,True,0,624369316,367.4916076660156,483.1791076660156,402.07952880859375,-42.611167907714844,-88.90348815917969,-90.39938354492188,53.616493225097656,7.51521635055542,-9.563796997070312,-32.84261703491211,12.294780731201172,31.591230392456055 +2264,6.832154,1381234,15,True,True,True,True,0,624369324,368.90338134765625,479.5117492675781,401.2785339355469,-46.237709045410156,-88.30477905273438,-87.04322814941406,53.297218322753906,7.263296604156494,-9.776659965515137,-31.288984298706055,12.764850616455078,29.567285537719727 +2268,6.840141,1381235,15,True,True,True,True,0,624369332,370.3187561035156,475.7560729980469,400.50054931640625,-47.64596939086914,-87.68586730957031,-85.89523315429688,52.968223571777344,7.006150722503662,-9.989941596984863,-29.73845672607422,13.254167556762695,27.54471778869629 +2270,6.848176,1381236,15,True,True,True,True,0,624369340,371.696533203125,471.9381103515625,399.6923522949219,-48.86158752441406,-87.03369140625,-84.91896057128906,52.63306427001953,6.743528842926025,-10.207979202270508,-28.19267463684082,13.751015663146973,25.49022102355957 +2272,6.856154,1381237,15,True,True,True,True,0,624369348,373.0290832519531,468.06634521484375,398.83123779296875,-49.810543060302734,-86.36669158935547,-84.18682098388672,52.29253387451172,6.475958347320557,-10.432751655578613,-26.655099868774414,14.253055572509766,23.427114486694336 +2275,6.864135,1381238,15,True,True,True,True,0,624369356,374.3437194824219,464.12152099609375,397.9370422363281,-50.6296501159668,-85.67473602294922,-83.56753540039062,51.94368362426758,6.203774452209473,-10.662405967712402,-25.131494522094727,14.765427589416504,21.35222625732422 +2278,6.872163,1381239,15,True,True,True,True,0,624369364,375.64935302734375,460.1210632324219,397.0302429199219,-50.949275970458984,-84.98741912841797,-83.43307495117188,51.58699417114258,5.929655075073242,-10.895586967468262,-23.618202209472656,15.29722785949707,19.307920455932617 +2280,6.880147,1381240,15,True,True,True,True,0,624369372,376.92816162109375,456.0674743652344,396.18231201171875,-51.22861099243164,-84.26687622070312,-83.31888580322266,51.22343063354492,5.651608943939209,-11.123170852661133,-22.118051528930664,15.842074394226074,17.25030517578125 +2282,6.888129,1381241,15,True,True,True,True,0,624369380,378.16607666015625,451.9823913574219,395.2756042480469,-51.42167282104492,-83.55886840820312,-83.26673126220703,50.854637145996094,5.3715996742248535,-11.35647201538086,-20.63309097290039,16.38766098022461,15.219440460205078 +2286,6.896155,1381242,15,True,True,True,True,0,624369388,379.33099365234375,447.8931884765625,394.313232421875,-51.68441390991211,-82.84363555908203,-83.11776733398438,50.48491287231445,5.089700222015381,-11.59496784210205,-19.169160842895508,16.934532165527344,13.195345878601074 +2288,6.904138,1381243,15,True,True,True,True,0,624369396,380.4280090332031,443.8106384277344,393.31622314453125,-51.94651412963867,-82.11361694335938,-82.94406127929688,50.114891052246094,4.807455062866211,-11.837518692016602,-17.724641799926758,17.492643356323242,11.17500114440918 +2290,6.912154,1381244,15,True,True,True,True,0,624369404,381.4649658203125,439.7428283691406,392.37249755859375,-52.00604248046875,-81.38529968261719,-82.9493637084961,49.744625091552734,4.525431156158447,-12.07463550567627,-16.30006980895996,18.067052841186523,9.191279411315918 +2294,6.920149,1381245,15,True,True,True,True,0,624369412,382.4361267089844,435.6975402832031,391.4187927246094,-52.11217498779297,-80.64952087402344,-82.88159942626953,49.37478256225586,4.244030952453613,-12.311065673828125,-14.902750968933105,18.642681121826172,7.222741603851318 +2296,6.928164,1381246,15,True,True,True,True,0,624369420,383.3284912109375,431.67230224609375,390.4223937988281,-52.21855926513672,-79.92186737060547,-82.78770446777344,49.00620651245117,3.9621999263763428,-12.550536155700684,-13.53134822845459,19.219200134277344,5.283873081207275 +2298,6.936194,1381247,15,True,True,True,True,0,624369428,384.1543884277344,427.66131591796875,389.3658142089844,-52.358367919921875,-79.19835662841797,-82.63750457763672,48.63786315917969,3.6805789470672607,-12.794903755187988,-12.18930435180664,19.796716690063477,3.3708581924438477 +2301,6.94416,1381248,15,True,True,True,True,0,624369436,384.8768310546875,423.7169189453125,388.3284912109375,-52.380985260009766,-78.49175262451172,-82.5783920288086,48.276344299316406,3.4002063274383545,-13.03612995147705,-10.87733268737793,20.383136749267578,1.5128357410430908 +2304,6.952116,1381249,15,True,True,True,True,0,624369444,385.48687744140625,419.8491516113281,387.3236389160156,-52.43214416503906,-77.77477264404297,-82.4621810913086,47.9232292175293,3.1205430030822754,-13.271531105041504,-9.6000394821167,20.97149085998535,-0.3239467442035675 +2306,6.960171,1381250,15,True,True,True,True,0,624369452,386.01715087890625,416.0372619628906,386.28228759765625,-52.43315124511719,-77.0892333984375,-82.3755874633789,47.574851989746094,2.84295654296875,-13.508256912231445,-8.357460975646973,21.557161331176758,-2.0964276790618896 +2310,6.968225,1381251,15,True,True,True,True,0,624369460,386.4508056640625,412.2992858886719,385.1748046875,-52.507568359375,-76.40814208984375,-82.19229888916016,47.23404312133789,2.5678441524505615,-13.74934196472168,-7.151499271392822,22.137832641601562,-3.8411967754364014 +2312,6.976163,1381252,15,True,True,True,True,0,624369468,386.79498291015625,408.64080810546875,384.0608215332031,-52.50826644897461,-75.75340270996094,-82.064453125,46.90128707885742,2.2956886291503906,-13.989263534545898,-5.985375881195068,22.722179412841797,-5.516520023345947 +2314,6.984196,1381253,15,True,True,True,True,0,624369476,387.0656433105469,405.06414794921875,382.9849853515625,-52.4996337890625,-75.10310363769531,-81.92780303955078,46.57588577270508,2.0271081924438477,-14.222235679626465,-4.860408306121826,23.306163787841797,-7.147272109985352 +2318,6.992147,1381254,15,True,True,True,True,0,624369484,387.2421569824219,401.5860290527344,381.8986511230469,-52.456722259521484,-74.48760986328125,-81.8098373413086,46.260963439941406,1.7618870735168457,-14.452637672424316,-3.7788426876068115,23.881553649902344,-8.702688217163086 +2320,7.000161,1381255,15,True,True,True,True,0,624369492,387.31256103515625,398.2457580566406,380.78094482421875,-52.465065002441406,-73.88209533691406,-81.62123107910156,45.96074295043945,1.50258469581604,-14.682697296142578,-2.742173910140991,24.44690704345703,-10.215191841125488 +2322,7.008146,1381256,15,True,True,True,True,0,624369500,387.2812194824219,395.0362243652344,379.6207580566406,-52.455352783203125,-73.30471801757812,-81.43718719482422,45.6751594543457,1.249144434928894,-14.914800643920898,-1.7540024518966675,25.008296966552734,-11.655213356018066 +2324,7.01617,1381257,15,True,True,True,True,0,624369508,387.16925048828125,391.9527587890625,378.5191650390625,-52.38523483276367,-72.7401351928711,-81.30164337158203,45.40293884277344,1.0016909837722778,-15.138805389404297,-0.8163115382194519,25.574771881103516,-13.025224685668945 +2327,7.024133,1381258,15,True,True,True,True,0,624369516,386.96209716796875,389.0127258300781,377.4496154785156,-52.27113723754883,-72.20541381835938,-81.19972229003906,45.14687728881836,0.7599974274635315,-15.356029510498047,0.07061658799648285,26.134258270263672,-14.313130378723145 +2330,7.032162,1381259,15,True,True,True,True,0,624369524,386.6558532714844,386.21661376953125,376.3677978515625,-52.13511276245117,-71.709716796875,-81.11392974853516,44.90807342529297,0.5238197445869446,-15.570743560791016,0.9032567739486694,26.680200576782227,-15.511393547058105 +2332,7.040172,1381260,15,True,True,True,True,0,624369532,386.2635498046875,383.558349609375,375.2587890625,-52.001197814941406,-71.24351501464844,-81.02268981933594,44.68568420410156,0.2938559353351593,-15.78504753112793,1.6812331676483154,27.21373176574707,-16.631683349609375 +2335,7.048148,1381261,15,True,True,True,True,0,624369540,385.8034973144531,381.0437927246094,374.1455078125,-51.85893249511719,-70.80148315429688,-80.93983459472656,44.479610443115234,0.07192186266183853,-15.996956825256348,2.40028715133667,27.737812042236328,-17.672090530395508 +2338,7.056176,1381262,15,True,True,True,True,0,624369548,385.2480773925781,378.70770263671875,373.11431884765625,-51.64915466308594,-70.38036346435547,-80.92033386230469,44.29512405395508,-0.14259527623653412,-16.197986602783203,3.0587077140808105,28.261011123657227,-18.621644973754883 +2340,7.064172,1381263,15,True,True,True,True,0,624369556,384.5882873535156,376.5692443847656,372.1214599609375,-51.41703414916992,-69.98876190185547,-80.92163848876953,44.13508987426758,-0.3491734564304352,-16.39113426208496,3.655404567718506,28.76877784729004,-19.48064613342285 +2344,7.072178,1381264,15,True,True,True,True,0,624369564,383.8662109375,374.5880126953125,371.1092224121094,-51.172462463378906,-69.63469696044922,-80.94442749023438,43.994056701660156,-0.546881914138794,-16.58308219909668,4.188404083251953,29.259363174438477,-20.24220848083496 +2346,7.080147,1381265,15,True,True,True,True,0,624369572,383.0506286621094,372.80023193359375,370.1000671386719,-50.90290832519531,-69.31455993652344,-80.99842071533203,43.877899169921875,-0.7357548475265503,-16.77187156677246,4.654339790344238,29.73536491394043,-20.90321159362793 +2348,7.088178,1381266,15,True,True,True,True,0,624369580,382.1471862792969,371.22332763671875,369.09771728515625,-50.59621810913086,-69.03510284423828,-81.09803009033203,43.788185119628906,-0.9140386581420898,-16.95696449279785,5.0525922775268555,30.19467544555664,-21.454593658447266 +2351,7.096156,1381267,15,True,True,True,True,0,624369588,381.1907653808594,369.8333740234375,368.1449890136719,-50.213104248046875,-68.79634857177734,-81.28811645507812,43.72101974487305,-1.081188678741455,-17.134626388549805,5.381920337677002,30.643796920776367,-21.886201858520508 +2354,7.104168,1381268,15,True,True,True,True,0,624369596,380.1478271484375,368.6686096191406,367.2776794433594,-49.77054214477539,-68.587890625,-81.54756927490234,43.68262481689453,-1.2379368543624878,-17.300203323364258,5.639766693115234,31.078676223754883,-22.20741081237793 +2356,7.112212,1381269,15,True,True,True,True,0,624369604,379.03826904296875,367.7179260253906,366.4397277832031,-49.283226013183594,-68.42256164550781,-81.8693618774414,43.671302795410156,-1.3830175399780273,-17.459228515625,5.8254547119140625,31.4915771484375,-22.409774780273438 +2360,7.120201,1381270,15,True,True,True,True,0,624369612,377.9140930175781,366.92333984375,365.63543701171875,-48.75568771362305,-68.29216766357422,-82.2620620727539,43.6798210144043,-1.516839623451233,-17.61178207397461,5.928775310516357,31.884645462036133,-22.490886688232422 +2362,7.128175,1381271,15,True,True,True,True,10,624369620,376.65997314453125,366.4142150878906,364.8851318359375,-48.217124938964844,-68.17362976074219,-82.67330932617188,43.72715759277344,-1.6386029720306396,-17.75617218017578,5.952699661254883,32.2616081237793,-22.47926902770996 +2364,7.1362,1381272,15,True,True,True,True,10,624369628,375.35516357421875,366.1067199707031,364.1886901855469,-47.62009811401367,-68.09353637695312,-83.15589141845703,43.79885482788086,-1.7487329244613647,-17.891738891601562,5.925880432128906,32.61921310424805,-22.37019920349121 +2366,7.144182,1381273,15,True,True,True,True,0,624369636,373.9822998046875,366.0359802246094,363.5490417480469,-46.9077262878418,-68.07063293457031,-83.77798461914062,43.90114212036133,-1.8455612659454346,-18.01869010925293,5.825003623962402,32.95897674560547,-22.11168098449707 +2370,7.152182,1381274,15,True,True,True,True,0,624369644,372.5220947265625,366.2002868652344,362.96759033203125,-46.08481216430664,-68.09740447998047,-84.53689575195312,44.036190032958984,-1.9309380054473877,-18.137022018432617,5.643244743347168,33.28175354003906,-21.70388412475586 +2372,7.160159,1381275,15,True,True,True,True,0,624369652,371.0329895019531,366.54046630859375,362.4375,-45.189876556396484,-68.15985107421875,-85.40159606933594,44.19497299194336,-2.00484037399292,-18.246992111206055,5.382681846618652,33.58424758911133,-21.169395446777344 +2374,7.168178,1381276,15,True,True,True,True,0,624369660,369.4686584472656,367.0892639160156,361.9562683105469,-44.260616302490234,-68.2374267578125,-86.32218933105469,44.383209228515625,-2.068136692047119,-18.349302291870117,5.049546718597412,33.86973190307617,-20.53998565673828 +2378,7.176159,1381277,15,True,True,True,True,0,624369668,367.8321228027344,367.8430480957031,361.5323486328125,-43.29279708862305,-68.33238220214844,-87.30375671386719,44.6003303527832,-2.121053695678711,-18.442466735839844,4.6461615562438965,34.13592529296875,-19.815263748168945 +2380,7.184163,1381278,15,True,True,True,True,0,624369676,366.1749572753906,368.7586364746094,361.14300537109375,-42.25925064086914,-68.4546127319336,-88.3819808959961,44.838706970214844,-2.1619629859924316,-18.529518127441406,4.175751209259033,34.38535690307617,-18.983840942382812 +2382,7.192187,1381279,15,True,True,True,True,0,624369684,364.4827575683594,369.8417053222656,360.7807312011719,-41.143882751464844,-68.6144027709961,-89.57185363769531,45.09945297241211,-2.191693067550659,-18.61039161682129,3.6438138484954834,34.61329650878906,-18.03971290588379 +2385,7.200183,1381280,15,True,True,True,True,0,624369692,362.7396545410156,371.10107421875,360.4622802734375,-39.96934509277344,-68.79432678222656,-90.84232330322266,45.38383102416992,-2.2107601165771484,-18.68352508544922,3.0567712783813477,34.823516845703125,-17.008747100830078 +2388,7.208157,1381281,15,True,True,True,True,0,624369700,360.96484375,372.5228576660156,360.16455078125,-38.78384780883789,-68.98491668701172,-92.14496612548828,45.68900680541992,-2.218234062194824,-18.751018524169922,2.4164371490478516,35.012882232666016,-15.913333892822266 +2390,7.216168,1381282,15,True,True,True,True,0,624369708,359.15484619140625,374.0835876464844,359.92987060546875,-37.515140533447266,-69.19791412353516,-93.55532836914062,46.013057708740234,-2.2171103954315186,-18.808053970336914,1.7251428365707397,35.18421936035156,-14.72824478149414 +2394,7.224193,1381283,15,True,True,True,True,0,624369716,357.2967834472656,375.8166809082031,359.7125244140625,-36.168235778808594,-69.43488311767578,-95.06217956542969,46.35887145996094,-2.2040810585021973,-18.860164642333984,0.9887920022010803,35.3388557434082,-13.46053409576416 +2396,7.232147,1381284,15,True,True,True,True,0,624369724,355.3636779785156,377.7104797363281,359.5654296875,-34.791996002197266,-69.67041015625,-96.60713958740234,46.727027893066406,-2.1833853721618652,-18.901084899902344,0.2124325931072235,35.4787712097168,-12.14660930633545 +2398,7.240193,1381285,15,True,True,True,True,0,624369732,353.3844909667969,379.74774169921875,359.443359375,-33.34269714355469,-69.92494201660156,-98.24240112304688,47.11376190185547,-2.1530253887176514,-18.935583114624023,-0.604913055896759,35.60096740722656,-10.760618209838867 +2401,7.248151,1381286,15,True,True,True,True,0,624369740,351.36517333984375,381.9192199707031,359.3280334472656,-31.791048049926758,-70.20346069335938,-99.99649047851562,47.51708984375,-2.11257004737854,-18.96592903137207,-1.4573413133621216,35.707122802734375,-9.298476219177246 +2404,7.256166,1381287,15,True,True,True,True,0,624369748,349.32098388671875,384.2029113769531,359.2567443847656,-30.176746368408203,-70.48661041259766,-101.8267822265625,47.93363952636719,-2.063171148300171,-18.98784065246582,-2.3425867557525635,35.7992057800293,-7.786869049072266 +2406,7.264162,1381288,15,True,True,True,True,0,624369756,347.24542236328125,386.5950012207031,359.22357177734375,-28.45026397705078,-70.7857894897461,-103.78361511230469,48.36308670043945,-2.0056800842285156,-19.001380920410156,-3.257744550704956,35.87436294555664,-6.2090349197387695 +2408,7.272173,1381289,15,True,True,True,True,0,624369764,345.1375732421875,389.1084289550781,359.2227478027344,-26.671680450439453,-71.08848571777344,-105.79942321777344,48.80579376220703,-1.9386783838272095,-19.0069522857666,-4.199317932128906,35.931365966796875,-4.594727039337158 +2411,7.280171,1381290,15,True,True,True,True,0,624369772,342.9806213378906,391.7345886230469,359.2421569824219,-24.822641372680664,-71.39716339111328,-107.8904800415039,49.26195526123047,-1.8638681173324585,-19.005929946899414,-5.164918899536133,35.97100067138672,-2.9412248134613037 +2414,7.288169,1381291,15,True,True,True,True,0,624369780,340.7879333496094,394.4794006347656,359.2860107421875,-22.84377098083496,-71.7145004272461,-110.11590576171875,49.73011779785156,-1.7790985107421875,-18.998308181762695,-6.1496686935424805,35.99650192260742,-1.2362645864486694 +2416,7.296176,1381292,15,True,True,True,True,0,624369788,338.5701599121094,397.3153991699219,359.3352355957031,-20.786386489868164,-72.03429412841797,-112.42211151123047,50.20683288574219,-1.6857317686080933,-18.985767364501953,-7.151377201080322,36.00514602661133,0.49750271439552307 +2420,7.304224,1381293,15,True,True,True,True,0,624369796,336.3267517089844,400.257568359375,359.39892578125,-18.636777877807617,-72.34917449951172,-114.81961822509766,50.69268798828125,-1.5818326473236084,-18.968191146850586,-8.16755199432373,36.003108978271484,2.257206439971924 +2422,7.312157,1381294,15,True,True,True,True,0,624369804,334.03692626953125,403.29998779296875,359.4634704589844,-16.416948318481445,-72.6574935913086,-117.28108215332031,51.188331604003906,-1.4693419933319092,-18.94681739807129,-9.194661140441895,35.98875427246094,4.030632972717285 +2424,7.320192,1381295,15,True,True,True,True,0,624369812,331.6938171386719,406.4498596191406,359.5396728515625,-14.079118728637695,-72.96665954589844,-119.85157012939453,51.693992614746094,-1.3487306833267212,-18.919126510620117,-10.229140281677246,35.95419692993164,5.828818321228027 +2428,7.328157,1381296,15,True,True,True,True,0,624369820,329.32098388671875,409.6839294433594,359.6737976074219,-11.623961448669434,-73.2635498046875,-122.53008270263672,52.20537567138672,-1.2208443880081177,-18.880250930786133,-11.26667308807373,35.903343200683594,7.642435550689697 +2430,7.3362,1381297,15,True,True,True,True,0,624369828,326.9328918457031,412.9740905761719,359.85601806640625,-9.023911476135254,-73.54349517822266,-125.34782409667969,52.719112396240234,-1.0866023302078247,-18.83202362060547,-12.307120323181152,35.840728759765625,9.47752571105957 +2432,7.344179,1381298,15,True,True,True,True,0,624369836,324.5093994140625,416.34051513671875,360.05841064453125,-6.308314800262451,-73.80452728271484,-128.26724243164062,53.237491607666016,-0.9448866844177246,-18.777719497680664,-13.345152854919434,35.765560150146484,11.318886756896973 +2435,7.352193,1381299,15,True,True,True,True,0,624369844,322.03466796875,419.7881164550781,360.27032470703125,-3.5102241039276123,-74.05075073242188,-131.24945068359375,53.761627197265625,-0.79671311378479,-18.717323303222656,-14.380476951599121,35.66954040527344,13.156034469604492 +2438,7.360161,1381300,15,True,True,True,True,0,624369852,319.5263366699219,423.29150390625,360.50579833984375,-0.6194142699241638,-74.2686996459961,-134.30609130859375,54.28779602050781,-0.6425299644470215,-18.651018142700195,-15.406579971313477,35.56364440917969,14.985151290893555 +2440,7.368157,1381301,15,True,True,True,True,0,624369860,317.0247497558594,426.8253173828125,360.761962890625,2.3999948501586914,-74.45838928222656,-137.4783935546875,54.810543060302734,-0.48188894987106323,-18.57868194580078,-16.423629760742188,35.44453811645508,16.817354202270508 +2444,7.376171,1381302,15,True,True,True,True,0,624369868,314.49432373046875,430.4120788574219,361.03021240234375,5.477982521057129,-74.61841583251953,-140.6857147216797,55.33373260498047,-0.3150795102119446,-18.501296997070312,-17.426738739013672,35.311012268066406,18.62932777404785 +2446,7.384166,1381303,15,True,True,True,True,0,624369876,311.9169921875,434.0587158203125,361.3042907714844,8.60736083984375,-74.7464370727539,-143.9171905517578,55.85896301269531,-0.1423853188753128,-18.419958114624023,-18.413105010986328,35.16435241699219,20.418485641479492 +2448,7.392179,1381304,15,True,True,True,True,0,624369884,309.3140869140625,437.7651672363281,361.5991516113281,11.802630424499512,-74.83715057373047,-147.18893432617188,56.383968353271484,0.0376860573887825,-18.33306884765625,-19.380624771118164,35.004878997802734,22.19040298461914 +2450,7.400216,1381305,15,True,True,True,True,0,624369892,306.71148681640625,441.4869689941406,361.8954772949219,15.06474781036377,-74.88874816894531,-150.5039825439453,56.90315628051758,0.22279660403728485,-18.243104934692383,-20.32378578186035,34.832210540771484,23.94295310974121 +2454,7.408169,1381306,15,True,True,True,True,0,624369900,304.1069641113281,445.2257080078125,362.2606506347656,18.317157745361328,-74.89368438720703,-153.78488159179688,57.416839599609375,0.4117092788219452,-18.142545700073242,-21.242618560791016,34.648529052734375,25.664159774780273 +2456,7.416151,1381307,15,True,True,True,True,0,624369908,301.4776916503906,449.00701904296875,362.6687927246094,21.54150390625,-74.8514404296875,-157.0070343017578,57.927940368652344,0.6062853336334229,-18.035198211669922,-22.12924575805664,34.45658493041992,27.347049713134766 +2458,7.424192,1381308,15,True,True,True,True,0,624369916,298.826416015625,452.8198547363281,363.0928955078125,24.679229736328125,-74.78199768066406,-160.10723876953125,58.435157775878906,0.8052365779876709,-17.922470092773438,-22.986478805541992,34.244136810302734,28.975296020507812 +2461,7.432149,1381309,15,True,True,True,True,0,624369924,296.1925048828125,456.6426086425781,363.5351867675781,27.811941146850586,-74.6631088256836,-163.1778564453125,58.93381118774414,1.0099307298660278,-17.806175231933594,-23.8085994720459,34.0231819152832,30.57765769958496 +2464,7.440166,1381310,15,True,True,True,True,0,624369932,293.5953369140625,460.46710205078125,363.98504638671875,30.867685317993164,-74.50836181640625,-166.1428985595703,59.421138763427734,1.2204926013946533,-17.686826705932617,-24.592140197753906,33.78734588623047,32.13086700439453 +2470,7.448189,1381311,15,True,True,True,True,0,624369940,291.0133972167969,464.2969665527344,364.47265625,33.790348052978516,-74.31729888916016,-168.94595336914062,59.8995361328125,1.4355828762054443,-17.562000274658203,-25.33574867248535,33.54304122924805,33.626075744628906 +2474,7.456199,1381312,15,True,True,True,True,0,624369948,288.4421081542969,468.14715576171875,364.97528076171875,36.54330825805664,-74.1039047241211,-171.54466247558594,60.369720458984375,1.656733751296997,-17.43426513671875,-26.033794403076172,33.28773498535156,35.04465866088867 +2476,7.464178,1381313,15,True,True,True,True,0,624369956,285.885986328125,471.99755859375,365.5514831542969,39.163795471191406,-73.85494232177734,-173.98138427734375,60.830230712890625,1.8808640241622925,-17.297021865844727,-26.684900283813477,33.02191925048828,36.40708923339844 +2478,7.472161,1381314,15,True,True,True,True,0,624369964,283.38763427734375,475.83343505859375,366.2123107910156,41.64269256591797,-73.56951141357422,-176.25189208984375,61.2763671875,2.1090176105499268,-17.14948081970215,-27.284595489501953,32.747379302978516,37.71215057373047 +2482,7.480179,1381315,15,True,True,True,True,0,624369972,280.9424743652344,479.64056396484375,366.9325866699219,43.966758728027344,-73.26493835449219,-178.33995056152344,61.707740783691406,2.339174509048462,-16.993757247924805,-27.834383010864258,32.457523345947266,38.948551177978516 +2484,7.488163,1381316,15,True,True,True,True,0,624369980,278.530517578125,483.4261474609375,367.6692199707031,46.143775939941406,-72.9401626586914,179.74864196777344,62.12649917602539,2.571998357772827,-16.83632469177246,-28.32512855529785,32.1593017578125,40.11117172241211 +2486,7.49619,1381317,15,True,True,True,True,0,624369988,276.1649169921875,487.19439697265625,368.43572998046875,48.12840270996094,-72.61817932128906,178.06163024902344,62.531620025634766,2.8081021308898926,-16.674991607666016,-28.75693130493164,31.848234176635742,41.17548751831055 +2489,7.50413,1381318,15,True,True,True,True,0,624369996,273.8583068847656,490.9425964355469,369.229248046875,49.9466552734375,-72.29344177246094,176.56893920898438,62.92204666137695,3.0481784343719482,-16.510791778564453,-29.126388549804688,31.528236389160156,42.148921966552734 +2492,7.512155,1381319,15,True,True,True,True,0,624370004,271.65533447265625,494.65069580078125,370.0950927734375,51.60677719116211,-71.96307373046875,175.25613403320312,63.292877197265625,3.291630983352661,-16.33829116821289,-29.43204689025879,31.197839736938477,43.03644943237305 +2494,7.520167,1381320,15,True,True,True,True,0,624370012,269.56256103515625,498.3053283691406,371.03094482421875,53.104000091552734,-71.62884521484375,174.1235809326172,63.643428802490234,3.537541151046753,-16.158687591552734,-29.67190933227539,30.86268424987793,43.83524703979492 +2496,7.528178,1381321,15,True,True,True,True,0,624370020,267.5627746582031,501.9047546386719,371.9813537597656,54.465843200683594,-71.30449676513672,173.1519775390625,63.97471618652344,3.7848470211029053,-15.977675437927246,-29.840124130249023,30.515182495117188,44.5320930480957 +2499,7.536202,1381322,15,True,True,True,True,0,624370028,265.6705017089844,505.4361572265625,372.9296569824219,55.715972900390625,-70.9825210571289,172.31488037109375,64.28498077392578,4.033202648162842,-15.7979097366333,-29.929718017578125,30.15846824645996,45.13090133666992 +2502,7.544122,1381323,15,True,True,True,True,10,624370036,263.87890625,508.9186096191406,373.9098815917969,56.82676696777344,-70.67494201660156,171.63999938964844,64.5761947631836,4.283629417419434,-15.615406036376953,-29.94143295288086,29.7938175201416,45.61851119995117 +2504,7.552191,1381324,15,True,True,True,True,10,624370044,262.2043151855469,512.35302734375,374.93280029296875,57.785804748535156,-70.40729522705078,171.1297607421875,64.84890747070312,4.536854267120361,-15.427546501159668,-29.903566360473633,29.42045021057129,45.998016357421875 +2508,7.560183,1381325,15,True,True,True,True,0,624370052,260.63836669921875,515.7471313476562,376.0523681640625,58.581085205078125,-70.16230010986328,170.80128479003906,65.10306549072266,4.792524814605713,-15.230101585388184,-29.784770965576172,29.0450382232666,46.254127502441406 +2510,7.568157,1381326,15,True,True,True,True,0,624370060,259.22015380859375,519.07861328125,377.1866149902344,59.31046676635742,-69.92729949951172,170.5583953857422,65.33367919921875,5.050663471221924,-15.030671119689941,-29.583017349243164,28.654245376586914,46.40974426269531 +2512,7.576203,1381327,15,True,True,True,True,0,624370068,257.9380187988281,522.342529296875,378.3334655761719,59.97019577026367,-69.70572662353516,170.40298461914062,65.54227447509766,5.310034275054932,-14.829830169677734,-29.299585342407227,28.251354217529297,46.462249755859375 +2515,7.584165,1381328,15,True,True,True,True,0,624370076,256.768798828125,525.5477905273438,379.49359130859375,60.586708068847656,-69.46874237060547,170.29965209960938,65.732666015625,5.570886135101318,-14.628716468811035,-28.93938636779785,27.847976684570312,46.44697952270508 +2518,7.592165,1381329,15,True,True,True,True,0,624370084,255.74014282226562,528.698974609375,380.7022705078125,61.134586334228516,-69.23860168457031,170.27560424804688,65.90257263183594,5.834228515625,-14.421961784362793,-28.502750396728516,27.437448501586914,46.340599060058594 +2520,7.600185,1381330,15,True,True,True,True,0,624370092,254.85780334472656,531.7951049804688,381.98089599609375,61.554988861083984,-69.04580688476562,170.38600158691406,66.05243682861328,6.100261211395264,-14.207757949829102,-27.993507385253906,27.02725601196289,46.110107421875 +2524,7.608219,1381331,15,True,True,True,True,0,624370100,254.0763397216797,534.8381958007812,383.26385498046875,61.9244499206543,-68.89622497558594,170.56680297851562,66.18602752685547,6.366384506225586,-13.990682601928711,-27.419042587280273,26.5975399017334,45.76666259765625 +2526,7.61615,1381332,15,True,True,True,True,0,624370108,253.41799926757812,537.8191528320312,384.5166931152344,62.25420379638672,-68.77420043945312,170.7945098876953,66.30237579345703,6.63397741317749,-13.77617073059082,-26.783811569213867,26.161935806274414,45.330013275146484 +2528,7.624161,1381333,15,True,True,True,True,0,624370116,252.8884735107422,540.73291015625,385.7758483886719,62.52457046508789,-68.6763687133789,171.08253479003906,66.40157318115234,6.902558326721191,-13.560864448547363,-26.088701248168945,25.728073120117188,44.80182647705078 +2532,7.632193,1381334,15,True,True,True,True,0,624370124,252.50057983398438,543.5873413085938,387.07977294921875,62.753013610839844,-68.58893585205078,171.41140747070312,66.48298645019531,7.172937393188477,-13.339550018310547,-25.334190368652344,25.29279136657715,44.19843673706055 +2534,7.640158,1381335,15,True,True,True,True,0,624370132,252.2313232421875,546.3787841796875,388.41021728515625,62.95038604736328,-68.51640319824219,171.77252197265625,66.5488510131836,7.443515300750732,-13.113945007324219,-24.522850036621094,24.854164123535156,43.51913070678711 +2536,7.648189,1381336,15,True,True,True,True,0,624370140,252.09378051757812,549.1101684570312,389.69873046875,63.151554107666016,-68.47723388671875,172.135986328125,66.5981674194336,7.715472221374512,-12.889671325683594,-23.66090965270996,24.399873733520508,42.75647735595703 +2538,7.65618,1381337,15,True,True,True,True,0,624370148,252.0937042236328,551.7689208984375,390.95758056640625,63.33567428588867,-68.46240997314453,172.51290893554688,66.63109588623047,7.98817253112793,-12.667193412780762,-22.74981117248535,23.94412612915039,41.91754913330078 +2542,7.664167,1381338,15,True,True,True,True,0,624370156,252.22564697265625,554.35546875,392.21038818359375,63.50316619873047,-68.46235656738281,172.89952087402344,66.64867401123047,8.261162757873535,-12.444103240966797,-21.791175842285156,23.490262985229492,41.01329803466797 +2544,7.672173,1381339,15,True,True,True,True,0,624370164,252.47589111328125,556.8897094726562,393.5083923339844,63.638736724853516,-68.47987365722656,173.3118438720703,66.65312957763672,8.535378456115723,-12.213996887207031,-20.786741256713867,23.037153244018555,40.04122543334961 +2546,7.680172,1381340,15,True,True,True,True,0,624370172,252.79061889648438,559.3942260742188,394.7857971191406,63.78225326538086,-68.51244354248047,173.716552734375,66.65019226074219,8.811103820800781,-11.983541488647461,-19.740060806274414,22.58043098449707,39.01193618774414 +2549,7.688169,1381341,15,True,True,True,True,0,624370180,253.27244567871094,561.8147583007812,395.99853515625,63.94942092895508,-68.57047271728516,174.08905029296875,66.62926483154297,9.08765697479248,-11.757081031799316,-18.65335464477539,22.115947723388672,37.919673919677734 +2552,7.696167,1381342,15,True,True,True,True,0,624370188,253.86029052734375,564.1822509765625,397.1537170410156,64.1104736328125,-68.6572036743164,174.4600067138672,66.59754943847656,9.365782737731934,-11.535148620605469,-17.528650283813477,21.654621124267578,36.75942611694336 +2554,7.704194,1381343,15,True,True,True,True,0,624370196,254.56146240234375,566.5008544921875,398.3323974609375,64.25020599365234,-68.7709732055664,174.84214782714844,66.5548324584961,9.64537239074707,-11.307740211486816,-16.36933135986328,21.193227767944336,35.534828186035156 +2558,7.712196,1381344,15,True,True,True,True,0,624370204,255.41317749023438,568.7327880859375,399.5425109863281,64.3790054321289,-68.8899917602539,175.2152862548828,66.49710845947266,9.924201965332031,-11.075204849243164,-15.176399230957031,20.738534927368164,34.268795013427734 +2560,7.720172,1381345,15,True,True,True,True,0,624370212,256.34814453125,570.923828125,400.6839599609375,64.54023742675781,-69.05303955078125,175.55458068847656,66.43141174316406,10.204057693481445,-10.845260620117188,-13.95257568359375,20.268184661865234,32.93296813964844 +2562,7.728178,1381346,15,True,True,True,True,0,624370220,257.4282531738281,573.0288696289062,401.75897216796875,64.74506378173828,-69.22100830078125,175.83126831054688,66.35177612304688,10.483516693115234,-10.620240211486816,-12.70055103302002,19.798372268676758,31.56833267211914 +2566,7.736154,1381347,15,True,True,True,True,0,624370228,258.6036376953125,575.0819091796875,402.77532958984375,64.95661926269531,-69.40583038330078,176.08493041992188,66.2641372680664,10.764150619506836,-10.400157928466797,-11.422486305236816,19.33704376220703,30.160934448242188 +2568,7.744179,1381348,15,True,True,True,True,0,624370236,259.8999938964844,577.0646362304688,403.8157653808594,65.1566390991211,-69.60014343261719,176.3284454345703,66.1658935546875,11.044517517089844,-10.175682067871094,-10.12009334564209,18.883981704711914,28.71739387512207 +2570,7.752208,1381349,15,True,True,True,True,0,624370244,261.3123474121094,578.9802856445312,404.8166809082031,65.39320373535156,-69.81520080566406,176.5189666748047,66.05755615234375,11.324641227722168,-9.951764106750488,-8.795741081237793,18.424304962158203,27.235671997070312 +2573,7.760182,1381350,15,True,True,True,True,0,624370252,262.8241882324219,580.833740234375,405.74853515625,65.65768432617188,-70.06965637207031,176.6668243408203,65.94100952148438,11.6044340133667,-9.731688499450684,-7.452455997467041,17.957355499267578,25.700069427490234 +2576,7.768165,1381351,15,True,True,True,True,0,624370260,264.4759216308594,582.615478515625,406.6223449707031,65.94440460205078,-70.33747100830078,176.767333984375,65.81309509277344,11.885357856750488,-9.516005516052246,-6.092081069946289,17.496227264404297,24.136024475097656 +2578,7.776245,1381352,15,True,True,True,True,0,624370268,266.21270751953125,584.3497314453125,407.49462890625,66.2191390991211,-70.6205062866211,176.85707092285156,65.67951965332031,12.167108535766602,-9.298956871032715,-4.719306945800781,17.045150756835938,22.542226791381836 +2580,7.78418,1381353,15,True,True,True,True,0,624370276,268.0313415527344,586.0310668945312,408.350341796875,66.52204895019531,-70.90613555908203,176.896728515625,65.54032135009766,12.448775291442871,-9.08142375946045,-3.3329203128814697,16.597198486328125,20.934274673461914 +2584,7.792213,1381354,15,True,True,True,True,0,624370284,269.9342956542969,587.6656494140625,409.1286926269531,66.87789916992188,-71.20951843261719,176.86383056640625,65.3956298828125,12.731344223022461,-8.869044303894043,-1.9341065883636475,16.144859313964844,19.30191421508789 +2586,7.800249,1381355,15,True,True,True,True,0,624370292,271.9242248535156,589.2398681640625,409.8482971191406,67.22319030761719,-71.5526351928711,176.81854248046875,65.24514770507812,13.013561248779297,-8.661736488342285,-0.5298406481742859,15.699080467224121,17.623685836791992 +2588,7.808172,1381356,15,True,True,True,True,0,624370300,273.9704284667969,590.7767944335938,410.51202392578125,67.61134338378906,-71.89130401611328,176.70761108398438,65.09220886230469,13.29648208618164,-8.459063529968262,0.8803195953369141,15.259894371032715,15.946736335754395 +2592,7.816179,1381357,15,True,True,True,True,0,624370308,276.10504150390625,592.2590942382812,411.1844787597656,67.99433898925781,-72.24036407470703,176.57518005371094,64.93372344970703,13.579562187194824,-8.254016876220703,2.293347120285034,14.828511238098145,14.254437446594238 +2594,7.824158,1381358,15,True,True,True,True,0,624370316,278.3008117675781,593.6865234375,411.8190612792969,68.42118072509766,-72.58236694335938,176.3743438720703,64.77185821533203,13.861398696899414,-8.051902770996094,3.7095141410827637,14.402119636535645,12.568227767944336 +2596,7.832191,1381359,15,True,True,True,True,0,624370324,280.57342529296875,595.059814453125,412.3802490234375,68.86151123046875,-72.96170043945312,176.13662719726562,64.6055908203125,14.143019676208496,-7.856064796447754,5.124806880950928,13.976752281188965,10.846839904785156 +2599,7.840165,1381360,15,True,True,True,True,0,624370332,282.9159240722656,596.36962890625,412.8721618652344,69.34321594238281,-73.34878540039062,175.8314666748047,64.43521881103516,14.422977447509766,-7.666982173919678,6.535747528076172,13.555536270141602,9.124156951904297 +2602,7.848169,1381361,15,True,True,True,True,0,624370340,285.3066711425781,597.6499633789062,413.31927490234375,69.8292465209961,-73.74528503417969,175.4982452392578,64.26365661621094,14.704218864440918,-7.482480049133301,7.939515113830566,13.14559268951416,7.397326469421387 +2604,7.856205,1381362,15,True,True,True,True,0,624370348,287.7511901855469,598.8712158203125,413.7699890136719,70.33341979980469,-74.1355972290039,175.1206817626953,64.08936309814453,14.983206748962402,-7.297251224517822,9.335857391357422,12.742627143859863,5.682426452636719 +2612,7.864192,1381363,15,True,True,True,True,0,624370356,290.2391052246094,600.0599975585938,414.1625061035156,70.86456298828125,-74.54801940917969,174.69329833984375,63.914302825927734,15.262665748596191,-7.116906642913818,10.721726417541504,12.340280532836914,3.956425189971924 +2614,7.872179,1381364,15,True,True,True,True,0,624370364,292.768310546875,601.21435546875,414.477294921875,71.45596313476562,-74.95657348632812,174.1819610595703,63.73849868774414,15.54233455657959,-6.944324970245361,12.095797538757324,11.941460609436035,2.2477312088012695 +2616,7.880216,1381365,15,True,True,True,True,0,624370372,295.3497314453125,602.3272094726562,414.7354736328125,72.03670501708984,-75.37562561035156,173.65802001953125,63.56052017211914,15.822162628173828,-6.7792649269104,13.454706192016602,11.559161186218262,0.5396347045898438 +2620,7.888194,1381366,15,True,True,True,True,0,624370380,297.9760437011719,603.4031982421875,414.9547424316406,72.6519546508789,-75.78484344482422,173.07568359375,63.38125228881836,16.10210418701172,-6.618462562561035,14.796148300170898,11.184623718261719,-1.1427079439163208 +2622,7.896176,1381367,15,True,True,True,True,0,624370388,300.6050109863281,604.4552001953125,415.1627197265625,73.27241516113281,-76.19116973876953,172.4689483642578,63.20424270629883,16.381132125854492,-6.45944356918335,16.11939239501953,10.819940567016602,-2.8077266216278076 +2624,7.904196,1381368,15,True,True,True,True,0,624370396,303.2520751953125,605.4784545898438,415.3035888671875,73.93793487548828,-76.61994934082031,171.7960205078125,63.028507232666016,16.65947914123535,-6.306788444519043,17.41960334777832,10.452662467956543,-4.472081184387207 +2626,7.912179,1381369,15,True,True,True,True,0,624370404,305.9220275878906,606.46484375,415.3896484375,74.62859344482422,-77.03589630126953,171.07821655273438,62.85271453857422,16.93667984008789,-6.16182279586792,18.696456909179688,10.099250793457031,-6.103414535522461 +2629,7.920172,1381370,15,True,True,True,True,0,624370412,308.57159423828125,607.4437255859375,415.4166259765625,75.31731414794922,-77.46442413330078,170.34825134277344,62.68116760253906,17.213579177856445,-6.024801731109619,19.945556640625,9.758810043334961,-7.723546504974365 +2632,7.928146,1381371,15,True,True,True,True,0,624370420,311.21636962890625,608.3948974609375,415.438720703125,76.0142822265625,-77.8761978149414,169.59361267089844,62.511741638183594,17.48853874206543,-5.889885902404785,21.167030334472656,9.430447578430176,-9.30383586883545 +2634,7.936192,1381372,15,True,True,True,True,0,624370428,313.8396301269531,609.347412109375,415.4054870605469,76.70145416259766,-78.30240631103516,168.8369140625,62.346649169921875,17.76405906677246,-5.762451171875,22.356334686279297,9.115252494812012,-10.87060260772705 +2638,7.944204,1381373,15,True,True,True,True,0,624370436,316.42144775390625,610.2926025390625,415.3024597167969,77.44905090332031,-78.715087890625,168.00787353515625,62.18712615966797,18.037689208984375,-5.6439104080200195,23.512447357177734,8.806780815124512,-12.392183303833008 +2640,7.952176,1381374,15,True,True,True,True,0,624370444,318.966552734375,611.2352294921875,415.1224060058594,78.22645568847656,-79.1338882446289,167.13888549804688,62.03271484375,18.310243606567383,-5.535480499267578,24.631284713745117,8.508469581604004,-13.885404586791992 +2642,7.960208,1381375,15,True,True,True,True,0,624370452,321.48199462890625,612.1712646484375,414.88134765625,79.03378295898438,-79.53990936279297,166.23074340820312,61.882381439208984,18.581615447998047,-5.43587589263916,25.712738037109375,8.223621368408203,-15.331913948059082 +2646,7.968177,1381376,15,True,True,True,True,0,624370460,323.9635925292969,613.1036987304688,414.58526611328125,79.86103820800781,-79.93679809570312,165.2949981689453,61.736366271972656,18.851823806762695,-5.34451961517334,26.753618240356445,7.952730655670166,-16.732526779174805 +2648,7.976175,1381377,15,True,True,True,True,0,624370468,326.3802185058594,614.0567016601562,414.25555419921875,80.6845703125,-80.3359375,164.3593292236328,61.597713470458984,19.121715545654297,-5.258659839630127,27.752260208129883,7.6951069831848145,-18.09754180908203 +2650,7.984206,1381378,15,True,True,True,True,0,624370476,328.74658203125,615.0150756835938,413.8914794921875,81.54792785644531,-80.71876525878906,163.37734985351562,61.464866638183594,19.390216827392578,-5.177992820739746,28.70602798461914,7.445599555969238,-19.404680252075195 +2653,7.992137,1381379,15,True,True,True,True,0,624370484,331.0888671875,615.9714965820312,413.4623718261719,82.44097900390625,-81.10113525390625,162.3596954345703,61.335289001464844,19.658123016357422,-5.106354713439941,29.612518310546875,7.207998752593994,-20.667312622070312 +2656,8.000176,1381380,15,True,True,True,True,0,624370492,333.338623046875,616.9547119140625,412.97186279296875,83.35102081298828,-81.48514556884766,161.32708740234375,61.21470260620117,19.92440414428711,-5.044046401977539,30.46930694580078,6.985236644744873,-21.885372161865234 +2658,8.00821,1381381,15,True,True,True,True,0,624370500,335.53814697265625,617.94775390625,412.4256286621094,84.29928588867188,-81.84204864501953,160.25515747070312,61.099239349365234,20.189647674560547,-4.990180969238281,31.274995803833008,6.77683687210083,-23.02846336364746 +2662,8.016226,1381382,15,True,True,True,True,0,624370508,337.6421813964844,618.9712524414062,411.80731201171875,85.2745132446289,-82.19889068603516,159.160888671875,60.992713928222656,20.45332145690918,-4.946951389312744,32.02622985839844,6.583977699279785,-24.12014389038086 +2664,8.024156,1381383,15,True,True,True,True,0,624370516,339.6268005371094,620.047607421875,411.1227111816406,86.28300476074219,-82.55364227294922,158.0407257080078,60.89759063720703,20.716407775878906,-4.913214206695557,32.72084045410156,6.405356407165527,-25.156103134155273 +2666,8.032172,1381384,15,True,True,True,True,0,624370524,341.488037109375,621.1693115234375,410.3923034667969,87.30615234375,-82.8888168334961,156.91494750976562,60.81351089477539,20.97763442993164,-4.887361526489258,33.35593795776367,6.243585109710693,-26.116146087646484 +2668,8.040199,1381385,15,True,True,True,True,0,624370532,343.241943359375,622.3283081054688,409.60906982421875,88.35722351074219,-83.2135238647461,155.768798828125,60.73896789550781,21.236963272094727,-4.869910717010498,33.93016052246094,6.096530437469482,-27.00763702392578 +2672,8.048173,1381386,15,True,True,True,True,0,624370540,344.8456726074219,623.5491943359375,408.7891540527344,89.40763854980469,-83.53919982910156,154.63644409179688,60.6776237487793,21.494457244873047,-4.859248161315918,34.44233703613281,5.966235637664795,-27.84078598022461 +2674,8.056169,1381387,15,True,True,True,True,0,624370548,346.35821533203125,624.7994995117188,407.89837646484375,90.47859191894531,-83.85711669921875,153.49293518066406,60.623783111572266,21.75004768371582,-4.859644889831543,34.887020111083984,5.8551554679870605,-28.60087013244629 +2676,8.064194,1381388,15,True,True,True,True,0,624370556,347.674560546875,626.130859375,406.9379577636719,91.60443115234375,-84.15430450439453,152.31161499023438,60.58696746826172,22.00283432006836,-4.870583534240723,35.26346969604492,5.7582478523254395,-29.2740535736084 +2679,8.072163,1381389,15,True,True,True,True,0,624370564,348.90155029296875,627.5003051757812,405.8854064941406,92.79014587402344,-84.43669128417969,151.0800018310547,60.5577278137207,22.254817962646484,-4.894270420074463,35.57087707519531,5.677819728851318,-29.865312576293945 +2682,8.080155,1381390,15,True,True,True,True,0,624370572,349.9820556640625,628.929443359375,404.74560546875,94.03864288330078,-84.70854949951172,149.80030822753906,60.540775299072266,22.504688262939453,-4.93058443069458,35.80663299560547,5.613887310028076,-30.376306533813477 +2684,8.088193,1381391,15,True,True,True,True,0,624370580,350.86785888671875,630.4450073242188,403.5296325683594,95.30496978759766,-84.96234893798828,148.52413940429688,60.54009246826172,22.752470016479492,-4.978704452514648,35.97303009033203,5.57013463973999,-30.80145835876465 +2687,8.096181,1381392,15,True,True,True,True,0,624370588,351.69586181640625,631.9666137695312,402.2525329589844,96.5627670288086,-85.1965560913086,147.26388549804688,60.54315185546875,22.99761962890625,-5.037043571472168,36.056732177734375,5.545217037200928,-31.12558937072754 +2690,8.104188,1381393,15,True,True,True,True,12,624370596,352.31182861328125,633.5813598632812,400.9059753417969,97.8343505859375,-85.4158706665039,146.01101684570312,60.56397247314453,23.240312576293945,-5.106226444244385,36.063961029052734,5.537356376647949,-31.359477996826172 +2692,8.112212,1381394,15,True,True,True,True,12,624370604,352.754150390625,635.2534790039062,399.5047912597656,99.13430786132812,-85.61375427246094,144.7469024658203,60.59841537475586,23.47883415222168,-5.18442440032959,36.02198791503906,5.542605400085449,-31.523942947387695 +2696,8.120176,1381395,15,True,True,True,True,0,624370612,353.0709228515625,636.9608764648438,398.0323791503906,100.4108657836914,-85.78936767578125,143.5186767578125,60.64229202270508,23.713775634765625,-5.274152755737305,35.897743225097656,5.564770221710205,-31.584426879882812 +2698,8.128177,1381396,15,True,True,True,True,0,624370620,353.1533508300781,638.7505493164062,396.5147705078125,101.66085815429688,-85.97904205322266,142.3407440185547,60.70450973510742,23.943607330322266,-5.373696327209473,35.691261291503906,5.608844757080078,-31.576446533203125 +2700,8.136219,1381397,15,True,True,True,True,0,624370628,353.1230163574219,640.56689453125,394.9515075683594,103.03057098388672,-86.19945526123047,141.05178833007812,60.77506637573242,24.16982078552246,-5.481561183929443,35.40328598022461,5.666563987731934,-31.518831253051758 +2703,8.144171,1381398,15,True,True,True,True,0,624370636,352.8955078125,642.4474487304688,393.3452453613281,104.3289794921875,-86.4025650024414,139.85206604003906,60.86089324951172,24.391429901123047,-5.598447322845459,35.03276443481445,5.74239444732666,-31.360393524169922 +2706,8.152202,1381399,15,True,True,True,True,0,624370644,352.5256042480469,644.3643188476562,391.6682434082031,105.4808349609375,-86.56615447998047,138.81222534179688,60.957130432128906,24.608898162841797,-5.72730827331543,34.58875274658203,5.835890769958496,-31.087223052978516 +2708,8.160175,1381400,15,True,True,True,True,0,624370652,352.0038146972656,646.3043823242188,389.9608459472656,106.49324798583984,-86.7054443359375,137.92245483398438,61.064212799072266,24.81999969482422,-5.863737106323242,34.076759338378906,5.9420928955078125,-30.720050811767578 +2710,8.168212,1381401,15,True,True,True,True,0,624370660,351.2969665527344,648.2877807617188,388.2323303222656,107.4257583618164,-86.84609985351562,137.12625122070312,61.18507385253906,25.02509880065918,-6.006772518157959,33.49967575073242,6.062385082244873,-30.287395477294922 +2713,8.176172,1381402,15,True,True,True,True,0,624370668,350.43310546875,650.2837524414062,386.486083984375,108.3182144165039,-87.00028991699219,136.38055419921875,61.31660842895508,25.222396850585938,-6.157066345214844,32.855796813964844,6.198596477508545,-29.79960060119629 +2716,8.184171,1381403,15,True,True,True,True,0,624370676,349.4266052246094,652.2788696289062,384.7318115234375,109.00373077392578,-87.14051055908203,135.84925842285156,61.45753860473633,25.411333084106445,-6.313468933105469,32.148624420166016,6.34820556640625,-29.231325149536133 +2718,8.192184,1381404,15,True,True,True,True,0,624370684,348.2738037109375,654.2769165039062,382.9707946777344,109.42308807373047,-87.25914001464844,135.59059143066406,61.6084098815918,25.592214584350586,-6.475367546081543,31.385791778564453,6.508476734161377,-28.582353591918945 +2722,8.200214,1381405,15,True,True,True,True,0,624370692,346.9767150878906,656.264892578125,381.2071838378906,109.65186309814453,-87.3768310546875,135.528076171875,61.76862335205078,25.763700485229492,-6.643178939819336,30.565298080444336,6.681354522705078,-27.871767044067383 +2724,8.208197,1381406,15,True,True,True,True,0,624370700,345.57220458984375,658.2167358398438,379.47198486328125,109.70653533935547,-87.49369049072266,135.6381378173828,61.9351806640625,25.924915313720703,-6.812827110290527,29.690006256103516,6.860074520111084,-27.10343360900879 +2726,8.216176,1381407,15,True,True,True,True,0,624370708,344.0166931152344,660.1622924804688,377.7146301269531,109.51488494873047,-87.59620666503906,135.99855041503906,62.11197280883789,26.07709503173828,-6.99020528793335,28.764406204223633,7.048558712005615,-26.26807975769043 +2730,8.224185,1381408,15,True,True,True,True,0,624370716,342.3459777832031,662.0743408203125,375.94793701171875,108.98065185546875,-87.6861343383789,136.70225524902344,62.29559326171875,26.21929931640625,-7.174673080444336,27.787626266479492,7.249543190002441,-25.365867614746094 +2732,8.232168,1381409,15,True,True,True,True,0,624370724,340.55670166015625,663.9374389648438,374.21136474609375,108.18981170654297,-87.77442169189453,137.66009521484375,62.486087799072266,26.349233627319336,-7.361826419830322,26.765106201171875,7.4574151039123535,-24.414770126342773 +2734,8.240217,1381410,15,True,True,True,True,0,624370732,338.6634216308594,665.7544555664062,372.47344970703125,107.21343994140625,-87.85643768310547,138.79859924316406,62.68282699584961,26.468477249145508,-7.55440092086792,25.69680404663086,7.669918537139893,-23.411781311035156 +2737,8.248164,1381411,15,True,True,True,True,0,624370740,336.6512145996094,667.5125122070312,370.7624206542969,105.87754821777344,-87.93504333496094,140.29388427734375,62.886207580566406,26.574424743652344,-7.751038551330566,24.58538246154785,7.893009185791016,-22.361120223999023 +2740,8.256148,1381412,15,True,True,True,True,0,624370748,334.5664978027344,669.1890258789062,369.0995178222656,104.13426971435547,-87.99913787841797,142.187255859375,63.09269332885742,26.667078018188477,-7.948537826538086,23.432674407958984,8.121150970458984,-21.25486946105957 +2742,8.264217,1381413,15,True,True,True,True,0,624370756,332.38812255859375,670.7994384765625,367.4645690917969,102.07186889648438,-88.04264831542969,144.3916015625,63.30443572998047,26.747215270996094,-8.148567199707031,22.243330001831055,8.352383613586426,-20.093442916870117 +2746,8.272191,1381414,15,True,True,True,True,0,624370764,330.12261962890625,672.32275390625,365.8691711425781,99.7522964477539,-88.08751678466797,146.84414672851562,63.520294189453125,26.812610626220703,-8.351280212402344,21.015897750854492,8.590652465820312,-18.898149490356445 +2748,8.280193,1381415,15,True,True,True,True,0,624370772,327.7756042480469,673.7615966796875,364.29827880859375,97.2387466430664,-88.12053680419922,149.4801788330078,63.74024200439453,26.86410903930664,-8.557531356811523,19.753870010375977,8.832711219787598,-17.660640716552734 +2750,8.288199,1381416,15,True,True,True,True,0,624370780,325.3394775390625,675.1138305664062,362.76873779296875,94.5492935180664,-88.13191986083984,152.28128051757812,63.96491241455078,26.900882720947266,-8.765410423278809,18.459487915039062,9.076912879943848,-16.374113082885742 +2752,8.296208,1381417,15,True,True,True,True,0,624370788,322.8150634765625,676.3641967773438,361.2864685058594,91.82511138916016,-88.14359283447266,155.10537719726562,64.19388580322266,26.921056747436523,-8.974822998046875,17.135183334350586,9.322654724121094,-15.063573837280273 +2756,8.304151,1381418,15,True,True,True,True,0,624370796,320.19134521484375,677.5216064453125,359.8537902832031,88.95880126953125,-88.13751220703125,158.0618896484375,64.42822265625,26.92519760131836,-9.185877799987793,15.783267974853516,9.57441234588623,-13.713507652282715 +2758,8.312195,1381419,15,True,True,True,True,0,624370804,317.4967956542969,678.5875244140625,358.4450378417969,86.23553466796875,-88.13687133789062,160.86146545410156,64.66641998291016,26.915477752685547,-9.399455070495605,14.402531623840332,9.82490062713623,-12.345985412597656 +2760,8.320135,1381420,15,True,True,True,True,0,624370812,314.72784423828125,679.5224609375,357.1445007324219,83.4607162475586,-88.11137390136719,163.69857788085938,64.90742492675781,26.885906219482422,-9.608546257019043,12.997991561889648,10.076855659484863,-10.936264038085938 +2763,8.328175,1381421,15,True,True,True,True,0,624370820,311.90771484375,680.3427734375,355.8848876953125,80.95955657958984,-88.06324005126953,166.24696350097656,65.15031433105469,26.84061050415039,-9.81887435913086,11.57230281829834,10.328917503356934,-9.487946510314941 +2766,8.336245,1381422,15,True,True,True,True,0,624370828,309.0834655761719,681.0121459960938,354.6909484863281,78.6601333618164,-88.01803588867188,168.5738067626953,65.39082336425781,26.777421951293945,-10.02799129486084,10.125931739807129,10.57897663116455,-8.026568412780762 +2768,8.344207,1381423,15,True,True,True,True,0,624370836,306.253173828125,681.5481567382812,353.5191345214844,76.85354614257812,-87.94775390625,170.38954162597656,65.62968444824219,26.698917388916016,-10.23971939086914,8.663692474365234,10.827249526977539,-6.5264434814453125 +2772,8.352265,1381424,15,True,True,True,True,0,624370844,303.40777587890625,681.9190063476562,352.42803955078125,75.32852935791016,-87.87543487548828,171.90496826171875,65.86665344238281,26.599790573120117,-10.448997497558594,7.1854352951049805,11.071969985961914,-5.010621070861816 +2774,8.360186,1381425,15,True,True,True,True,0,624370852,300.5409851074219,682.11474609375,351.4440002441406,73.97083282470703,-87.80143737792969,173.23529052734375,66.1019287109375,26.478118896484375,-10.653667449951172,5.693490505218506,11.315115928649902,-3.482187032699585 +2776,8.3682,1381426,15,True,True,True,True,0,624370860,297.6430969238281,682.14111328125,350.5477600097656,72.9624252319336,-87.70703887939453,174.19998168945312,66.33644104003906,26.334291458129883,-10.855673789978027,4.189964294433594,11.557479858398438,-1.9227057695388794 +2780,8.37614,1381427,15,True,True,True,True,0,624370868,294.704833984375,681.9963989257812,349.76312255859375,72.031494140625,-87.62291717529297,175.07211303710938,66.57090759277344,26.167255401611328,-11.052285194396973,2.6768312454223633,11.797957420349121,-0.36609533429145813 +2782,8.384182,1381428,15,True,True,True,True,0,624370876,291.70465087890625,681.680908203125,349.0690002441406,71.5666732788086,-87.53563690185547,175.4652099609375,66.80699157714844,25.97593116760254,-11.244336128234863,1.1556791067123413,12.025736808776855,1.2044706344604492 +2784,8.392204,1381429,15,True,True,True,True,0,624370884,288.669189453125,681.1951293945312,348.4482421875,71.29155731201172,-87.46257781982422,175.6554412841797,67.04280090332031,25.762264251708984,-11.433870315551758,-0.3711036741733551,12.247764587402344,2.767287254333496 +2787,8.400179,1381430,15,True,True,True,True,0,624370892,285.62249755859375,680.5188598632812,347.92919921875,71.34307861328125,-87.37312316894531,175.50437927246094,67.27606201171875,25.52462387084961,-11.618102073669434,-1.9013270139694214,12.464849472045898,4.351576328277588 +2790,8.408181,1381431,15,True,True,True,True,0,624370900,282.591796875,679.6431884765625,347.5001525878906,71.60720825195312,-87.27849578857422,175.1250457763672,67.50456237792969,25.26351547241211,-11.798137664794922,-3.432227373123169,12.678645133972168,5.942685604095459 +2792,8.416185,1381432,15,True,True,True,True,0,624370908,279.58209228515625,678.5633544921875,347.1644287109375,72.08023834228516,-87.17615509033203,174.521484375,67.72784423828125,24.97850799560547,-11.973362922668457,-4.962535381317139,12.888768196105957,7.54192590713501 +2794,8.4242,1381433,15,True,True,True,True,0,624370916,276.5665588378906,677.2601318359375,346.9933166503906,72.75144958496094,-87.06401824951172,173.70823669433594,67.94711303710938,24.664541244506836,-12.135866165161133,-6.487212657928467,13.087312698364258,9.146525382995605 +2797,8.432174,1381434,15,True,True,True,True,0,624370924,273.57989501953125,675.720703125,346.9285583496094,73.63752746582031,-86.97099304199219,172.66738891601562,68.15947723388672,24.322460174560547,-12.290755271911621,-8.009567260742188,13.269064903259277,10.732242584228516 +2800,8.44017,1381435,15,True,True,True,True,0,624370932,270.61932373046875,673.9556884765625,347.000732421875,74.65140533447266,-86.86747741699219,171.4873046875,68.36540985107422,23.953203201293945,-12.435270309448242,-9.524317741394043,13.443368911743164,12.32086181640625 +2802,8.448161,1381436,15,True,True,True,True,0,624370940,267.6692199707031,671.9829711914062,347.2158203125,75.67601776123047,-86.75873565673828,170.2874755859375,68.56660461425781,23.558189392089844,-12.568194389343262,-11.029977798461914,13.612868309020996,13.904255867004395 +2806,8.456163,1381437,15,True,True,True,True,0,624370948,264.73046875,669.7807006835938,347.604736328125,76.70698547363281,-86.64192199707031,169.07347106933594,68.76221466064453,23.13411521911621,-12.686224937438965,-12.523313522338867,13.77463150024414,15.482239723205566 +2808,8.464173,1381438,15,True,True,True,True,0,624370956,261.7732849121094,667.3601684570312,348.0635986328125,77.94929504394531,-86.55290985107422,167.64706420898438,68.9544677734375,22.682268142700195,-12.79859733581543,-14.003893852233887,13.91731071472168,17.021541595458984 +2810,8.472208,1381439,15,True,True,True,True,0,624370964,258.8519592285156,664.69482421875,348.5674743652344,79.5653076171875,-86.4517822265625,165.84292602539062,69.13842010498047,22.202428817749023,-12.907807350158691,-15.469873428344727,14.040997505187988,18.56021499633789 +2813,8.480182,1381440,15,True,True,True,True,0,624370972,255.9409637451172,661.7804565429688,349.1919250488281,81.20244598388672,-86.3451919555664,164.01455688476562,69.31597900390625,21.691972732543945,-13.00723648071289,-16.917316436767578,14.156765937805176,20.083436965942383 +2816,8.488186,1381441,15,True,True,True,True,0,624370980,253.03628540039062,658.6111450195312,350.0242614746094,82.6453857421875,-86.2420654296875,162.37738037109375,69.48731994628906,21.14862632751465,-13.087298393249512,-18.34564781188965,14.267104148864746,21.580610275268555 +2818,8.496211,1381442,15,True,True,True,True,0,624370988,250.1888427734375,655.1778564453125,350.98150634765625,84.22611999511719,-86.12220001220703,160.60096740722656,69.6476821899414,20.574674606323242,-13.153998374938965,-19.7523193359375,14.35794734954834,23.072053909301758 +2822,8.504254,1381443,15,True,True,True,True,0,624370996,247.40382385253906,651.4561157226562,351.9951477050781,85.99594116210938,-85.99494171142578,158.63491821289062,69.79572296142578,19.96845817565918,-13.21518325805664,-21.13527488708496,14.429707527160645,24.545446395874023 +2824,8.512189,1381444,15,True,True,True,True,0,624371004,244.61131286621094,647.477783203125,353.1033020019531,87.82209014892578,-85.87374877929688,156.61993408203125,69.93744659423828,19.330036163330078,-13.265899658203125,-22.489830017089844,14.483388900756836,25.981653213500977 +2826,8.520226,1381445,15,True,True,True,True,0,624371012,241.8838653564453,643.2293090820312,354.36553955078125,89.49937438964844,-85.76676177978516,154.75228881835938,70.06712341308594,18.66046142578125,-13.299482345581055,-23.817935943603516,14.5277681350708,27.374462127685547 +2830,8.528179,1381446,15,True,True,True,True,0,624371020,239.19651794433594,638.6937255859375,355.7246398925781,91.31389617919922,-85.6463623046875,152.7560272216797,70.18502044677734,17.95686912536621,-13.319738388061523,-25.116548538208008,14.543967247009277,28.748655319213867 +2832,8.53621,1381447,15,True,True,True,True,0,624371028,236.50474548339844,633.9122924804688,357.1268615722656,93.09033203125,-85.50487518310547,150.80633544921875,70.29633331298828,17.223709106445312,-13.333556175231934,-26.38032341003418,14.550106048583984,30.105674743652344 +2834,8.54419,1381448,15,True,True,True,True,0,624371036,233.845947265625,628.84814453125,358.657470703125,94.76414489746094,-85.38646697998047,148.9647674560547,70.3967514038086,16.456680297851562,-13.330730438232422,-27.611600875854492,14.542427062988281,31.405345916748047 +2836,8.552212,1381449,15,True,True,True,True,12,624371044,231.23104858398438,623.5244140625,360.21087646484375,96.506103515625,-85.26210021972656,147.0648956298828,70.4853286743164,15.660649299621582,-13.320188522338867,-28.808372497558594,14.51330852508545,32.67293930053711 +2840,8.560197,1381450,15,True,True,True,True,12,624371052,228.6023712158203,617.9434814453125,361.81195068359375,98.3009033203125,-85.13837432861328,145.12953186035156,70.56622314453125,14.832718849182129,-13.298298835754395,-29.96843147277832,14.458720207214355,33.89974594116211 +2843,8.568185,1381451,15,True,True,True,True,0,624371060,226.042236328125,612.1028442382812,363.4759521484375,99.97611999511719,-85.0260238647461,143.31640625,70.63339233398438,13.97652816772461,-13.263773918151855,-31.094411849975586,14.395315170288086,35.07990646362305 +2845,8.576221,1381452,15,True,True,True,True,0,624371068,223.45999145507812,606.0305786132812,365.1318359375,101.7231216430664,-84.89615631103516,141.45205688476562,70.69366455078125,13.091975212097168,-13.221089363098145,-32.18788146972656,14.303442001342773,36.24052047729492 +2848,8.584194,1381453,15,True,True,True,True,0,624371076,220.9379425048828,599.7279663085938,366.8589172363281,103.24604034423828,-84.75596618652344,139.81582641601562,70.74117279052734,12.182062149047852,-13.162301063537598,-33.24809646606445,14.20327091217041,37.37995910644531 +2851,8.592192,1381454,15,True,True,True,True,0,624371084,218.4435577392578,593.2174682617188,368.60870361328125,104.72952270507812,-84.62345886230469,138.23086547851562,70.77835845947266,11.248491287231445,-13.090217590332031,-34.27931213378906,14.084149360656738,38.48199462890625 +2853,8.600203,1381455,15,True,True,True,True,0,624371092,215.9293975830078,586.4955444335938,370.348388671875,106.21288299560547,-84.49083709716797,136.66366577148438,70.80864715576172,10.288986206054688,-13.007586479187012,-35.28282928466797,13.942639350891113,39.554443359375 +2857,8.608252,1381456,15,True,True,True,True,0,624371100,213.51075744628906,579.5974731445312,372.08038330078125,107.53427124023438,-84.36799621582031,135.25595092773438,70.82427215576172,9.313382148742676,-12.914522171020508,-36.25787353515625,13.799976348876953,40.591529846191406 +2859,8.616191,1381457,15,True,True,True,True,0,624371108,211.06556701660156,572.6018676757812,373.68292236328125,108.92838287353516,-84.2440185546875,133.7947998046875,70.83678436279297,8.327046394348145,-12.821362495422363,-37.20818328857422,13.638947486877441,41.599945068359375 +2861,8.624194,1381458,15,True,True,True,True,0,624371116,208.6533966064453,565.4277954101562,375.25823974609375,110.2482681274414,-84.1170654296875,132.41795349121094,70.83831787109375,7.3209919929504395,-12.717196464538574,-38.133541107177734,13.461652755737305,42.58720016479492 +2864,8.632196,1381459,15,True,True,True,True,0,624371124,206.20445251464844,558.1049194335938,376.8890686035156,111.31026458740234,-84.00005340576172,131.3047332763672,70.83729553222656,6.294650077819824,-12.593987464904785,-39.03678512573242,13.286591529846191,43.552284240722656 +2867,8.640188,1381460,15,True,True,True,True,0,624371132,203.75469970703125,550.7007446289062,378.41864013671875,112.43156433105469,-83.88390350341797,130.1519012451172,70.8302993774414,5.259341716766357,-12.464649200439453,-39.91852569580078,13.087543487548828,44.49226760864258 +2869,8.648251,1381461,15,True,True,True,True,0,624371140,201.39035034179688,543.2366333007812,379.836181640625,113.47010040283203,-83.76100158691406,129.08450317382812,70.81119537353516,4.221837520599365,-12.331697463989258,-40.78083419799805,12.881401062011719,45.42104721069336 +2873,8.656178,1381462,15,True,True,True,True,0,624371148,198.95237731933594,535.6732788085938,381.2108154296875,114.4379653930664,-83.6511001586914,128.10289001464844,70.79325103759766,3.168835163116455,-12.187612533569336,-41.62619400024414,12.667522430419922,46.324005126953125 +2875,8.664193,1381463,15,True,True,True,True,0,624371156,196.60357666015625,528.0469360351562,382.4800720214844,115.38753509521484,-83.54605865478516,127.14309692382812,70.76171112060547,2.112638235092163,-12.03805923461914,-42.4533805847168,12.44209098815918,47.204856872558594 +2877,8.672216,1381464,15,True,True,True,True,0,624371164,194.2574462890625,520.4078979492188,383.6888122558594,116.18023681640625,-83.45144653320312,126.3413314819336,70.72797393798828,1.0549201965332031,-11.879907608032227,-43.265254974365234,12.223787307739258,48.06834030151367 +2879,8.680193,1381465,15,True,True,True,True,0,624371172,191.92897033691406,512.7672729492188,384.7197265625,117.05833435058594,-83.35348510742188,125.47108459472656,70.68821716308594,-0.0015265094116330147,-11.72331714630127,-44.062896728515625,11.985106468200684,48.91574478149414 +2883,8.688225,1381466,15,True,True,True,True,0,624371180,189.6515655517578,505.1104736328125,385.7134704589844,117.78997039794922,-83.2663803100586,124.74485778808594,70.64068603515625,-1.0587393045425415,-11.554879188537598,-44.848731994628906,11.749342918395996,49.74993896484375 +2885,8.696177,1381467,15,True,True,True,True,0,624371188,187.3743133544922,497.50567626953125,386.5902099609375,118.4231185913086,-83.17322540283203,124.12577056884766,70.5921401977539,-2.10998797416687,-11.383380889892578,-45.62222671508789,11.511255264282227,50.58253860473633 +2887,8.704255,1381468,15,True,True,True,True,0,624371196,185.1455535888672,489.92547607421875,387.3625183105469,119.02236938476562,-83.07989501953125,123.54781341552734,70.5357437133789,-3.157020092010498,-11.206628799438477,-46.38595962524414,11.262513160705566,51.40818786621094 +2891,8.712193,1381469,15,True,True,True,True,0,624371204,182.936767578125,482.43707275390625,388.0144958496094,119.53546905517578,-82.99501037597656,123.0548324584961,70.47897338867188,-4.192481517791748,-11.029500007629395,-47.14128875732422,11.024030685424805,52.2224006652832 +2893,8.720191,1381470,15,True,True,True,True,0,624371212,180.7580108642578,475.044189453125,388.5055236816406,120.08565521240234,-82.90448760986328,122.53829193115234,70.41822814941406,-5.215646266937256,-10.854530334472656,-47.89057540893555,10.771317481994629,53.03329849243164 +2895,8.72821,1381471,15,True,True,True,True,0,624371220,178.60032653808594,467.74542236328125,388.9281311035156,120.55135345458984,-82.82514953613281,122.1073226928711,70.35652160644531,-6.228113174438477,-10.673681259155273,-48.634422302246094,10.523721694946289,53.83460235595703 +2898,8.736218,1381472,15,True,True,True,True,0,624371228,176.4803924560547,460.5989074707031,389.1737976074219,121.011962890625,-82.74093627929688,121.68621826171875,70.29388427734375,-7.220746994018555,-10.500494956970215,-49.374351501464844,10.277122497558594,54.63527297973633 +2901,8.744209,1381473,15,True,True,True,True,0,624371236,174.3898468017578,453.6165771484375,389.2540283203125,121.5510025024414,-82.66139221191406,121.19750213623047,70.23078155517578,-8.192890167236328,-10.333786010742188,-50.11173629760742,10.023445129394531,55.423439025878906 +2903,8.75219,1381474,15,True,True,True,True,0,624371244,172.3541259765625,446.79400634765625,389.2973327636719,121.9859619140625,-82.59315490722656,120.80828857421875,70.16649627685547,-9.1452054977417,-10.161486625671387,-50.848228454589844,9.780455589294434,56.207881927490234 +2907,8.760245,1381475,15,True,True,True,True,0,624371252,170.3446044921875,440.15887451171875,389.24835205078125,122.37921905517578,-82.51969909667969,120.46989440917969,70.10379791259766,-10.075156211853027,-9.98978042602539,-51.5866584777832,9.534638404846191,57.0014533996582 +2909,8.768195,1381476,15,True,True,True,True,0,624371260,168.4029998779297,433.6983337402344,389.08990478515625,122.70886993408203,-82.43899536132812,120.19398498535156,70.03850555419922,-10.981965065002441,-9.822587013244629,-52.32609176635742,9.293889999389648,57.805660247802734 +2911,8.776273,1381477,15,True,True,True,True,0,624371268,166.51199340820312,427.46392822265625,388.8595886230469,122.97124481201172,-82.364990234375,119.9801025390625,69.97640991210938,-11.860183715820312,-9.65864372253418,-53.066890716552734,9.069356918334961,58.609073638916016 +2915,8.784187,1381478,15,True,True,True,True,0,624371276,164.68028259277344,421.4647521972656,388.510009765625,123.29167938232422,-82.28912353515625,119.71669006347656,69.91425323486328,-12.707913398742676,-9.502718925476074,-53.81391525268555,8.83983039855957,59.41533660888672 +2917,8.792191,1381479,15,True,True,True,True,0,624371284,162.9178009033203,415.720947265625,388.026123046875,123.62590789794922,-82.20355987548828,119.44275665283203,69.85299682617188,-13.521656036376953,-9.359253883361816,-54.56795883178711,8.613386154174805,60.23386764526367 +2919,8.80021,1381480,15,True,True,True,True,0,624371292,161.23130798339844,410.22015380859375,387.5339050292969,123.83272552490234,-82.12177276611328,119.2836685180664,69.79434967041016,-14.303451538085938,-9.216487884521484,-55.32801818847656,8.410012245178223,61.063262939453125 +2921,8.808215,1381481,15,True,True,True,True,0,624371300,159.59690856933594,405.0098876953125,386.9866027832031,124.03868103027344,-82.03543090820312,119.1331558227539,69.7405014038086,-15.048023223876953,-9.080024719238281,-56.09623718261719,8.207990646362305,61.9039192199707 +2924,8.816182,1381482,15,True,True,True,True,0,624371308,158.0399627685547,400.0697021484375,386.39874267578125,124.2987060546875,-81.95531463623047,118.93297576904297,69.68739318847656,-15.756556510925293,-8.948978424072266,-56.875484466552734,8.005563735961914,62.74555969238281 +2927,8.824184,1381483,15,True,True,True,True,0,624371316,156.56358337402344,395.45611572265625,385.7972106933594,124.56953430175781,-81.8866958618164,118.71623229980469,69.64019012451172,-16.421302795410156,-8.82485294342041,-57.668033599853516,7.820178031921387,63.58806610107422 +2929,8.832199,1381484,15,True,True,True,True,0,624371324,155.1694793701172,391.1477966308594,385.2190856933594,124.75173950195312,-81.81023406982422,118.58586883544922,69.59716033935547,-17.044965744018555,-8.703485488891602,-58.473426818847656,7.6463942527771,64.45584869384766 +2933,8.840224,1381485,15,True,True,True,True,10,624371332,153.88232421875,387.1564636230469,384.5924987792969,124.90880584716797,-81.7176513671875,118.47869110107422,69.55546569824219,-17.623687744140625,-8.594968795776367,-59.29255294799805,7.481071472167969,65.3507080078125 +2935,8.848191,1381486,15,True,True,True,True,10,624371340,152.65740966796875,383.4938049316406,383.9320068359375,125.11517333984375,-81.62574005126953,118.32566833496094,69.52167510986328,-18.15800666809082,-8.49863338470459,-60.12504959106445,7.32443380355835,66.25235748291016 +2937,8.856246,1381487,15,True,True,True,True,0,624371348,151.5386505126953,380.1053161621094,383.2789001464844,125.26919555664062,-81.52912139892578,118.21263885498047,69.48954772949219,-18.652618408203125,-8.410237312316895,-60.97340774536133,7.187159538269043,67.17577362060547 +2941,8.864187,1381488,15,True,True,True,True,0,624371356,150.50242614746094,377.0392150878906,382.6568908691406,125.41004943847656,-81.42825317382812,118.11377716064453,69.46283721923828,-19.10249137878418,-8.328495025634766,-61.834842681884766,7.058257579803467,68.11555480957031 +2943,8.872191,1381489,15,True,True,True,True,0,624371364,149.55226135253906,374.2620849609375,382.0875549316406,125.51710510253906,-81.3261489868164,118.04464721679688,69.44032287597656,-19.51158332824707,-8.250943183898926,-62.707393646240234,6.94170618057251,69.06907653808594 +2945,8.880195,1381490,15,True,True,True,True,0,624371372,148.68043518066406,371.7423095703125,381.5323181152344,125.64486694335938,-81.22325134277344,117.95366668701172,69.4207763671875,-19.88384246826172,-8.181079864501953,-63.5936164855957,6.832603931427002,70.03376007080078 +2948,8.888206,1381491,15,True,True,True,True,0,624371380,147.8767547607422,369.5107116699219,380.9688415527344,125.85586547851562,-81.12419891357422,117.7826919555664,69.4068832397461,-20.215309143066406,-8.123098373413086,-64.49275970458984,6.72880744934082,70.99906921386719 +2951,8.896189,1381492,15,True,True,True,True,0,624371388,147.16326904296875,367.55029296875,380.41522216796875,126.01980590820312,-81.00968170166016,117.652587890625,69.39657592773438,-20.50684356689453,-8.075872421264648,-65.40377044677734,6.639455795288086,71.99066925048828 +2953,8.904259,1381493,15,True,True,True,True,0,624371396,146.53399658203125,365.83984375,379.89892578125,126.16033172607422,-80.89035034179688,117.53894805908203,69.38965606689453,-20.761568069458008,-8.035710334777832,-66.32418060302734,6.564301013946533,72.9957504272461 +2957,8.91222,1381494,15,True,True,True,True,0,624371404,145.9798583984375,364.3836975097656,379.4234313964844,126.32486724853516,-80.77226257324219,117.39940643310547,69.38667297363281,-20.97930908203125,-8.00222110748291,-67.25525665283203,6.497781276702881,74.00665283203125 +2959,8.920186,1381495,15,True,True,True,True,0,624371412,145.5078125,363.1642150878906,378.9923400878906,126.44940948486328,-80.64462280273438,117.29298400878906,69.38677978515625,-21.161813735961914,-7.9750237464904785,-68.19707489013672,6.444873809814453,75.03784942626953 +2961,8.928218,1381496,15,True,True,True,True,0,624371420,145.1157684326172,362.1652526855469,378.61090087890625,126.583251953125,-80.5147933959961,117.17630767822266,69.3877182006836,-21.311359405517578,-7.952480792999268,-69.14698791503906,6.396292686462402,76.07704162597656 +2963,8.936265,1381497,15,True,True,True,True,0,624371428,144.80369567871094,361.387939453125,378.2760314941406,126.73025512695312,-80.38481903076172,117.04144287109375,69.39058685302734,-21.42758560180664,-7.93552827835083,-70.10610961914062,6.357293605804443,77.12262725830078 +2967,8.944203,1381498,15,True,True,True,True,0,624371436,144.52598571777344,360.8314208984375,378.0187072753906,126.87928771972656,-80.25984954833984,116.90387725830078,69.40201568603516,-21.513206481933594,-7.919954299926758,-71.07321166992188,6.329682350158691,78.17048645019531 +2969,8.952182,1381499,15,True,True,True,True,0,624371444,144.32508850097656,360.49017333984375,377.81439208984375,127.01113891601562,-80.12559509277344,116.7830810546875,69.41450500488281,-21.566486358642578,-7.908787250518799,-72.04879760742188,6.3062896728515625,79.23497009277344 +2971,8.960204,1381500,15,True,True,True,True,0,624371452,144.19320678710938,360.3659362792969,377.65179443359375,127.17066192626953,-79.99390411376953,116.62765502929688,69.43096923828125,-21.587352752685547,-7.903855800628662,-73.03152465820312,6.296130180358887,80.29942321777344 +2974,8.968211,1381501,15,True,True,True,True,0,624371460,144.0995330810547,360.4163513183594,377.51318359375,127.36058807373047,-79.86276245117188,116.44113159179688,69.45240783691406,-21.582965850830078,-7.904521465301514,-74.02293395996094,6.293699741363525,81.36713409423828 +2977,8.9762,1381502,15,True,True,True,True,0,624371468,144.0663604736328,360.70306396484375,377.401123046875,127.48406219482422,-79.70338439941406,116.32585144042969,69.47804260253906,-21.544002532958984,-7.913334369659424,-75.01927185058594,6.29469633102417,82.4679183959961 +2979,8.984224,1381503,15,True,True,True,True,0,624371476,144.06283569335938,361.125732421875,377.34130859375,127.63243865966797,-79.5511474609375,116.1847152709961,69.5074691772461,-21.485689163208008,-7.922456741333008,-76.02066040039062,6.30198860168457,83.56425476074219 +2983,8.9922,1381504,15,True,True,True,True,0,624371484,144.1151885986328,361.6868591308594,377.34619140625,127.79229736328125,-79.40602111816406,116.02220916748047,69.53851318359375,-21.406339645385742,-7.931148529052734,-77.02875518798828,6.3223161697387695,84.65849304199219 +2985,9.000202,1381505,15,True,True,True,True,0,624371492,144.2053985595703,362.4089660644531,377.3814392089844,127.9746322631836,-79.26081848144531,115.83544921875,69.57376861572266,-21.303699493408203,-7.943701267242432,-78.0434341430664,6.350586414337158,85.75498962402344 +2987,9.008206,1381506,15,True,True,True,True,0,624371500,144.3424072265625,363.3102111816406,377.42340087890625,128.1396942138672,-79.09989166259766,115.66511535644531,69.61286163330078,-21.174667358398438,-7.963708877563477,-79.06233215332031,6.38674259185791,86.86791229248047 +2991,9.016211,1381507,15,True,True,True,True,0,624371508,144.54782104492188,364.376953125,377.4914245605469,128.33621215820312,-78.93952941894531,115.45674133300781,69.65222930908203,-21.020172119140625,-7.988478183746338,-80.08415985107422,6.431731700897217,87.97737884521484 +2993,9.024199,1381508,15,True,True,True,True,0,624371516,144.79379272460938,365.5999450683594,377.5959167480469,128.53199768066406,-78.7750473022461,115.24484252929688,69.69548034667969,-20.843046188354492,-8.01570987701416,-81.111083984375,6.487127780914307,89.09296417236328 +2995,9.0322,1381509,15,True,True,True,True,0,624371524,145.079833984375,366.9700927734375,377.72381591796875,128.69313049316406,-78.5955810546875,115.06639862060547,69.74148559570312,-20.644515991210938,-8.046245574951172,-82.139404296875,6.54965877532959,90.22395324707031 +2998,9.040208,1381510,15,True,True,True,True,0,624371532,145.41326904296875,368.4771423339844,377.8887023925781,128.87313842773438,-78.41690063476562,114.86680603027344,69.78820037841797,-20.42582130432129,-8.077849388122559,-83.17028045654297,6.617117881774902,91.35298156738281 +3001,9.048195,1381511,15,True,True,True,True,0,624371540,145.77987670898438,370.12652587890625,378.0583190917969,129.10386657714844,-78.24150848388672,114.6146469116211,69.83815002441406,-20.186697006225586,-8.114089965820312,-84.20421600341797,6.691473007202148,92.4736557006836 +3003,9.056255,1381512,15,True,True,True,True,0,624371548,146.17291259765625,371.924072265625,378.2252502441406,129.33819580078125,-78.0557632446289,114.36168670654297,69.89183807373047,-19.926788330078125,-8.155631065368652,-85.24034118652344,6.769792079925537,93.60186004638672 +3005,9.064214,1381513,15,True,True,True,True,0,624371556,146.608154296875,373.84552001953125,378.3738098144531,129.57125854492188,-77.8576889038086,114.10682678222656,69.94652557373047,-19.648345947265625,-8.203434944152832,-86.27616882324219,6.855600357055664,94.73662567138672 +3008,9.072199,1381514,15,True,True,True,True,0,624371564,147.0670166015625,375.89697265625,378.506103515625,129.9127655029297,-77.67586517333984,113.74369812011719,70.00415802001953,-19.35169219970703,-8.256802558898926,-87.31404876708984,6.945034980773926,95.84339904785156 +3011,9.080209,1381515,15,True,True,True,True,0,624371572,147.5554962158203,378.05255126953125,378.6236267089844,130.22543334960938,-77.4768295288086,113.40689849853516,70.06342315673828,-19.03993034362793,-8.314427375793457,-88.35455322265625,7.042453765869141,96.96661376953125 +3013,9.088202,1381516,15,True,True,True,True,0,624371580,148.0672149658203,380.30096435546875,378.74761962890625,130.51535034179688,-77.26737976074219,113.0924301147461,70.12396240234375,-18.71521759033203,-8.373076438903809,-89.3932876586914,7.144923210144043,98.09662628173828 +3017,9.096255,1381517,15,True,True,True,True,0,624371588,148.60011291503906,382.64434814453125,378.8795471191406,130.79412841796875,-77.05065155029297,112.7845687866211,70.18708038330078,-18.377363204956055,-8.43239688873291,-90.43093872070312,7.257699966430664,99.22936248779297 +3019,9.104244,1381518,15,True,True,True,True,0,624371596,149.1580047607422,385.07373046875,379.0440673828125,131.072998046875,-76.8343734741211,112.4763412475586,70.2503433227539,-18.0278263092041,-8.488900184631348,-91.46731567382812,7.373468399047852,100.35851287841797 +3021,9.112254,1381519,15,True,True,True,True,0,624371604,149.7153778076172,387.5986633300781,379.20172119140625,131.34344482421875,-76.60948944091797,112.18002319335938,70.31766510009766,-17.666259765625,-8.547037124633789,-92.50099182128906,7.4933905601501465,101.49015045166016 +3024,9.120206,1381520,15,True,True,True,True,0,624371612,150.30287170410156,390.17962646484375,379.32647705078125,131.6707305908203,-76.39055633544922,111.82279205322266,70.3832015991211,-17.29603385925293,-8.608685493469238,-93.53267669677734,7.617818355560303,102.60332489013672 +3027,9.128213,1381521,15,True,True,True,True,0,624371620,150.8744659423828,392.8426513671875,379.41339111328125,132.0149688720703,-76.16548156738281,111.45347595214844,70.45390319824219,-16.91602897644043,-8.674477577209473,-94.56453704833984,7.746103286743164,103.71520233154297 +3029,9.136268,1381522,15,True,True,True,True,0,624371628,151.46510314941406,395.5592346191406,379.4786682128906,132.3356475830078,-75.92778015136719,111.1063232421875,70.52413177490234,-16.528404235839844,-8.741926193237305,-95.59141540527344,7.879636764526367,104.8320083618164 +3033,9.144201,1381523,15,True,True,True,True,0,624371636,152.05978393554688,398.3399658203125,379.5084228515625,132.67135620117188,-75.68537139892578,110.7453842163086,70.59600067138672,-16.13239097595215,-8.812642097473145,-96.6142349243164,8.017240524291992,105.9422836303711 +3035,9.152215,1381524,15,True,True,True,True,0,624371644,152.64439392089844,401.1626892089844,379.54754638671875,133.01858520507812,-75.44667053222656,110.37496948242188,70.67000579833984,-15.732177734375,-8.880305290222168,-97.63384246826172,8.15772819519043,107.04198455810547 +3037,9.1602,1381525,15,True,True,True,True,0,624371652,153.2318572998047,404.0282897949219,379.5572509765625,133.3560333251953,-75.19876861572266,110.01589965820312,70.74459075927734,-15.32650089263916,-8.949569702148438,-98.64984130859375,8.301651000976562,108.14327239990234 +3041,9.168193,1381526,15,True,True,True,True,0,624371660,153.8234100341797,406.9244079589844,379.57257080078125,133.655029296875,-74.9403305053711,109.69367218017578,70.81961822509766,-14.91728401184082,-9.015880584716797,-99.65805053710938,8.452290534973145,109.2481689453125 +3043,9.17619,1381527,15,True,True,True,True,0,624371668,154.4049835205078,409.8343811035156,379.5614929199219,134.0131378173828,-74.69417572021484,109.31269073486328,70.8952407836914,-14.507065773010254,-9.08226490020752,-100.66279602050781,8.60518741607666,110.32685089111328 +3045,9.184226,1381528,15,True,True,True,True,0,624371676,154.95590209960938,412.75732421875,379.5002136230469,134.359375,-74.43678283691406,108.94733428955078,70.97438049316406,-14.096543312072754,-9.151300430297852,-101.66065979003906,8.762772560119629,111.4051742553711 +3047,9.192215,1381529,15,True,True,True,True,0,624371684,155.52125549316406,415.6878356933594,379.4022521972656,134.73196411132812,-74.18128204345703,108.55419158935547,71.05073547363281,-13.684465408325195,-9.221720695495605,-102.65272521972656,8.922375679016113,112.46788024902344 +3051,9.200197,1381530,15,True,True,True,True,0,624371692,156.0529327392578,418.6434631347656,379.26934814453125,135.10243225097656,-73.92040252685547,108.17037963867188,71.13070678710938,-13.270895004272461,-9.293339729309082,-103.63861083984375,9.08278751373291,113.52546691894531 +3053,9.208217,1381531,15,True,True,True,True,0,624371700,156.5829315185547,421.5855712890625,379.1524658203125,135.44789123535156,-73.65657806396484,107.81039428710938,71.20931243896484,-12.859837532043457,-9.35940170288086,-104.61658477783203,9.247294425964355,114.57941436767578 +3055,9.216205,1381532,15,True,True,True,True,0,624371708,157.08200073242188,424.5252685546875,379.0407409667969,135.7572021484375,-73.3850326538086,107.49177551269531,71.29022216796875,-12.45114803314209,-9.42129898071289,-105.58610534667969,9.413984298706055,115.63542175292969 +3058,9.224212,1381533,15,True,True,True,True,0,624371716,157.56483459472656,427.4441833496094,378.90863037109375,136.09454345703125,-73.12007141113281,107.14383697509766,71.37126159667969,-12.046032905578613,-9.481757164001465,-106.5457534790039,9.584991455078125,116.66781616210938 +3061,9.232184,1381534,15,True,True,True,True,0,624371724,158.02716064453125,430.3372497558594,378.7398681640625,136.44703674316406,-72.85615539550781,106.78436279296875,71.45195007324219,-11.645190238952637,-9.542665481567383,-107.49938201904297,9.755785942077637,117.68749237060547 +3063,9.240247,1381535,15,True,True,True,True,0,624371732,158.4397430419922,433.1893615722656,378.56121826171875,136.77098083496094,-72.58505249023438,106.4586181640625,71.53570556640625,-11.252134323120117,-9.600351333618164,-108.44426727294922,9.92966079711914,118.70693969726562 +3067,9.24822,1381536,15,True,True,True,True,0,624371740,158.85394287109375,435.99273681640625,378.3557434082031,137.10885620117188,-72.31766510009766,106.11885070800781,71.6156234741211,-10.865274429321289,-9.65739917755127,-109.37799835205078,10.103033065795898,119.70716857910156 +3069,9.256185,1381537,15,True,True,True,True,0,624371748,159.2215118408203,438.7630920410156,378.1459655761719,137.45054626464844,-72.0528335571289,105.77821350097656,71.69871520996094,-10.484987258911133,-9.71103572845459,-110.30178833007812,10.280294418334961,120.69187927246094 +3071,9.264225,1381538,15,True,True,True,True,0,624371756,159.5716094970703,441.4752502441406,377.9586486816406,137.77662658691406,-71.79011535644531,105.4532470703125,71.78034973144531,-10.11352825164795,-9.758052825927734,-111.21424102783203,10.459855079650879,121.66542053222656 +3075,9.272199,1381539,15,True,True,True,True,0,624371764,159.87567138671875,444.12158203125,377.79345703125,138.06570434570312,-71.5245590209961,105.1718978881836,71.86311340332031,-9.753243446350098,-9.798462867736816,-112.11488342285156,10.638550758361816,122.63687896728516 +3077,9.280209,1381540,15,True,True,True,True,0,624371772,160.15411376953125,446.6864318847656,377.6192626953125,138.36605834960938,-71.26459503173828,104.8797378540039,71.94438171386719,-9.40457534790039,-9.835866928100586,-113.00479125976562,10.818333625793457,123.58918762207031 +3079,9.288206,1381541,15,True,True,True,True,0,624371780,160.39512634277344,449.1764831542969,377.41754150390625,138.68490600585938,-71.00920104980469,104.57069396972656,72.02591705322266,-9.066864967346191,-9.872692108154297,-113.88316345214844,10.999723434448242,124.51982879638672 +3082,9.296206,1381542,15,True,True,True,True,0,624371788,160.58123779296875,451.58062744140625,377.2040100097656,138.97451782226562,-70.74847412109375,104.29881286621094,72.10883331298828,-8.742587089538574,-9.907217025756836,-114.74861145019531,11.179275512695312,125.44721984863281 +3085,9.304245,1381543,15,True,True,True,True,0,624371796,160.74925231933594,453.8807373046875,376.9756164550781,139.28854370117188,-70.49742889404297,104.00090026855469,72.18868255615234,-8.432211875915527,-9.939844131469727,-115.60031127929688,11.359091758728027,126.34620666503906 +3087,9.312213,1381544,15,True,True,True,True,0,624371804,160.86012268066406,456.087890625,376.724853515625,139.60621643066406,-70.24759674072266,103.70267486572266,72.2706069946289,-8.135836601257324,-9.971535682678223,-116.4386215209961,11.540934562683105,127.22758483886719 +3089,9.320245,1381545,15,True,True,True,True,0,624371812,160.95223999023438,458.1820373535156,376.482421875,139.91151428222656,-69.99842071533203,103.41484069824219,72.34973907470703,-7.854662895202637,-9.998761177062988,-117.26310729980469,11.72562026977539,128.09596252441406 +3093,9.328254,1381546,15,True,True,True,True,0,624371820,160.98922729492188,460.1587829589844,376.2567138671875,140.20947265625,-69.75555419921875,103.13949584960938,72.42935180664062,-7.591166973114014,-10.020819664001465,-118.07324981689453,11.90782642364502,128.9472198486328 +3095,9.33626,1381547,15,True,True,True,True,0,624371828,160.97628784179688,462.0148620605469,376.0550231933594,140.47776794433594,-69.51258850097656,102.89884185791016,72.50896453857422,-7.345552444458008,-10.037038803100586,-118.86885833740234,12.088556289672852,129.79173278808594 +3097,9.344214,1381548,15,True,True,True,True,0,624371836,160.93185424804688,463.7228088378906,375.8701477050781,140.7424774169922,-69.27539825439453,102.65921020507812,72.58623504638672,-7.120179653167725,-10.048051834106445,-119.64644622802734,12.271576881408691,130.61412048339844 +3101,9.3522,1381549,15,True,True,True,True,0,624371844,160.83152770996094,465.3074035644531,375.7000427246094,141.02439880371094,-69.05230712890625,102.40670013427734,72.6639175415039,-6.913153171539307,-10.054893493652344,-120.4089584350586,12.4505033493042,131.4073028564453 +3103,9.360177,1381550,15,True,True,True,True,0,624371852,160.6932830810547,466.7425537109375,375.5294189453125,141.2926483154297,-68.82830047607422,102.1675796508789,72.74002075195312,-6.726447582244873,-10.058869361877441,-121.156982421875,12.631415367126465,132.1890106201172 +3105,9.368202,1381551,15,True,True,True,True,0,624371860,160.5117950439453,468.023193359375,375.390869140625,141.54710388183594,-68.61109161376953,101.9439468383789,72.81459045410156,-6.561601638793945,-10.056642532348633,-121.8870620727539,12.81035041809082,132.9524383544922 +3108,9.376219,1381552,15,True,True,True,True,0,624371868,160.2694854736328,469.153564453125,375.2762145996094,141.791259765625,-68.3995361328125,101.73286437988281,72.89010620117188,-6.418609142303467,-10.049236297607422,-122.59823608398438,12.989243507385254,133.69589233398438 +3111,9.384204,1381553,15,True,True,True,True,0,624371876,160.00074768066406,470.10296630859375,375.1990051269531,142.03431701660156,-68.19959259033203,101.52120971679688,72.96144104003906,-6.3001484870910645,-10.035088539123535,-123.29313659667969,13.165532112121582,134.41644287109375 +3113,9.392257,1381554,15,True,True,True,True,0,624371884,159.66140747070312,470.8849182128906,375.1615295410156,142.28720092773438,-68.01655578613281,101.30311584472656,73.03388214111328,-6.206521034240723,-10.014301300048828,-123.96893310546875,13.337488174438477,135.1049346923828 +3117,9.400213,1381555,15,True,True,True,True,0,624371892,159.27671813964844,471.4819641113281,375.17950439453125,142.52639770507812,-67.84307098388672,101.097412109375,73.10470581054688,-6.138906002044678,-9.984742164611816,-124.62590026855469,13.509228706359863,135.77313232421875 +3119,9.408214,1381556,15,True,True,True,True,0,624371900,158.8550262451172,471.885498046875,375.2376708984375,142.7309112548828,-67.67266082763672,100.92852783203125,73.17227935791016,-6.097951412200928,-9.948522567749023,-125.2645263671875,13.676668167114258,136.4331512451172 +3121,9.416211,1381557,15,True,True,True,True,0,624371908,158.3689422607422,472.09075927734375,375.3251953125,142.93453979492188,-67.51223754882812,100.75994110107422,73.24019622802734,-6.085052967071533,-9.906550407409668,-125.8830795288086,13.844165802001953,137.06715393066406 +3125,9.424202,1381558,15,True,True,True,True,0,624371916,157.84547424316406,472.10955810546875,375.42584228515625,143.13710021972656,-67.36186981201172,100.59137725830078,73.3055648803711,-6.097474575042725,-9.861376762390137,-126.47901153564453,14.00804615020752,137.6730499267578 +3127,9.432212,1381559,15,True,True,True,True,0,624371924,157.26255798339844,471.8961181640625,375.5867919921875,143.34442138671875,-67.2304458618164,100.41668701171875,73.36936950683594,-6.142714023590088,-9.806724548339844,-127.05587768554688,14.168383598327637,138.2481231689453 +3129,9.440206,1381560,15,True,True,True,True,0,624371932,156.63372802734375,471.4966735839844,375.7895812988281,143.5265655517578,-67.10435485839844,100.26715850830078,73.43203735351562,-6.213983058929443,-9.745203971862793,-127.61212921142578,14.327230453491211,138.80828857421875 +3131,9.448248,1381561,15,True,True,True,True,0,624371940,155.97784423828125,470.89752197265625,375.96466064453125,143.70945739746094,-66.98079681396484,100.11212921142578,73.49142456054688,-6.311010360717773,-9.68470573425293,-128.14642333984375,14.486529350280762,139.34361267089844 +3134,9.456205,1381562,15,True,True,True,True,0,624371948,155.24623107910156,470.0747985839844,376.2091064453125,143.87451171875,-66.8765640258789,99.9801254272461,73.55095672607422,-6.441007614135742,-9.614203453063965,-128.65638732910156,14.635379791259766,139.8520965576172 +3137,9.464228,1381563,15,True,True,True,True,10,624371956,154.46865844726562,469.01263427734375,376.4976806640625,144.03623962402344,-66.78807067871094,99.84761810302734,73.6078109741211,-6.6040263175964355,-9.535770416259766,-129.1431884765625,14.781067848205566,140.3302764892578 +3139,9.4722,1381564,15,True,True,True,True,10,624371964,153.6336212158203,467.7277526855469,376.84033203125,144.18630981445312,-66.71443176269531,99.72549438476562,73.66385650634766,-6.798647880554199,-9.448187828063965,-129.60743713378906,14.923210144042969,140.78256225585938 +3143,9.480252,1381565,15,True,True,True,True,0,624371972,152.74859619140625,466.2361755371094,377.1689453125,144.34617614746094,-66.64913940429688,99.58770751953125,73.71965026855469,-7.021234512329102,-9.359004020690918,-130.04718017578125,15.0675630569458,141.20098876953125 +3145,9.488221,1381566,15,True,True,True,True,0,624371980,151.8197479248047,464.5311584472656,377.508544921875,144.5083770751953,-66.5987548828125,99.44380187988281,73.77369689941406,-7.273159503936768,-9.265576362609863,-130.45948791503906,15.2078275680542,141.58303833007812 +3147,9.496199,1381567,15,True,True,True,True,0,624371988,150.82025146484375,462.6455383300781,377.86102294921875,144.6504364013672,-66.55642700195312,99.3207778930664,73.8304672241211,-7.551455497741699,-9.1676607131958,-130.84146118164062,15.345423698425293,141.9373779296875 +3151,9.504263,1381568,15,True,True,True,True,0,624371996,149.7884979248047,460.5630187988281,378.243896484375,144.75543212890625,-66.52083587646484,99.23157501220703,73.88517761230469,-7.856967449188232,-9.06290340423584,-131.191650390625,15.479812622070312,142.269775390625 +3153,9.512207,1381569,15,True,True,True,True,0,624372004,148.67543029785156,458.3260498046875,378.6657409667969,144.84463500976562,-66.49905395507812,99.15906524658203,73.94505310058594,-8.18649959564209,-8.950249671936035,-131.5100555419922,15.612103462219238,142.5686492919922 +3155,9.520253,1381570,15,True,True,True,True,0,624372012,147.53294372558594,455.93792724609375,379.07843017578125,144.93321228027344,-66.48780822753906,99.08001708984375,74.00477600097656,-8.536701202392578,-8.835219383239746,-131.7945098876953,15.744630813598633,142.82693481445312 +3158,9.528198,1381571,15,True,True,True,True,0,624372020,146.3269805908203,453.4308776855469,379.4815368652344,145.006591796875,-66.48482513427734,99.01720428466797,74.06903076171875,-8.905139923095703,-8.718198776245117,-132.04539489746094,15.87398624420166,143.05224609375 +3161,9.536268,1381572,15,True,True,True,True,0,624372028,145.08078002929688,450.8199768066406,379.860595703125,145.06643676757812,-66.4874038696289,98.9640884399414,74.13629913330078,-9.288634300231934,-8.600861549377441,-132.25820922851562,16.00338363647461,143.23992919921875 +3163,9.544252,1381573,15,True,True,True,True,0,624372036,143.791748046875,448.1156921386719,380.2384948730469,145.09669494628906,-66.4964370727539,98.938720703125,74.20722961425781,-9.686513900756836,-8.48073959350586,-132.42979431152344,16.13065528869629,143.3927001953125 +3167,9.552256,1381574,15,True,True,True,True,0,624372044,142.45240783691406,445.3433532714844,380.614013671875,145.113037109375,-66.51660919189453,98.9261703491211,74.28361511230469,-10.095902442932129,-8.358195304870605,-132.56105041503906,16.255279541015625,143.5033416748047 +3169,9.56025,1381575,15,True,True,True,True,0,624372052,141.0839080810547,442.5118103027344,380.9593200683594,145.11862182617188,-66.54208374023438,98.91857147216797,74.36400604248047,-10.514228820800781,-8.236283302307129,-132.6507110595703,16.38176155090332,143.5712890625 +3171,9.568226,1381576,15,True,True,True,True,0,624372060,139.6652374267578,439.6409606933594,381.26507568359375,145.113037109375,-66.57044982910156,98.91935729980469,74.45182800292969,-10.93967056274414,-8.116127967834473,-132.69984436035156,16.511140823364258,143.59878540039062 +3174,9.57622,1381577,15,True,True,True,True,0,624372068,138.21746826171875,436.7510681152344,381.5173645019531,145.06626892089844,-66.58988189697266,98.9574203491211,74.54588317871094,-11.368500709533691,-7.999826908111572,-132.70343017578125,16.645679473876953,143.5972442626953 +3178,9.584223,1381578,15,True,True,True,True,0,624372076,136.71615600585938,433.8790283203125,381.6949768066406,144.9871368408203,-66.60382843017578,99.03874969482422,74.6483154296875,-11.797359466552734,-7.891026973724365,-132.68043518066406,16.77259635925293,143.58335876464844 +3180,9.592217,1381579,15,True,True,True,True,0,624372084,135.1876983642578,431.019287109375,381.83843994140625,144.9478302001953,-66.63911437988281,99.06819915771484,74.7583236694336,-12.225916862487793,-7.78499174118042,-132.60208129882812,16.903255462646484,143.48451232910156 +3182,9.60021,1381580,15,True,True,True,True,0,624372092,133.6491241455078,428.187744140625,381.9389953613281,144.8777618408203,-66.67346954345703,99.11958312988281,74.8748550415039,-12.651273727416992,-7.6834635734558105,-132.46287536621094,17.038658142089844,143.33233642578125 +3185,9.60822,1381581,15,True,True,True,True,0,624372100,132.04798889160156,425.4219665527344,381.9941711425781,144.77125549316406,-66.7114486694336,99.21331024169922,75.00436401367188,-13.071233749389648,-7.588272571563721,-132.26522827148438,17.167890548706055,143.13067626953125 +3188,9.61622,1381582,15,True,True,True,True,0,624372108,130.4186553955078,422.715576171875,381.9836730957031,144.63987731933594,-66.74681854248047,99.32758331298828,75.14381408691406,-13.484378814697266,-7.501262187957764,-132.009765625,17.30078125,142.8772430419922 +3190,9.624248,1381583,15,True,True,True,True,0,624372116,128.78427124023438,420.0675048828125,381.9172058105469,144.4735107421875,-66.77532196044922,99.46768188476562,75.29103088378906,-13.889789581298828,-7.421335220336914,-131.6954345703125,17.441638946533203,142.57620239257812 +3193,9.632208,1381584,15,True,True,True,True,0,624372124,127.11920166015625,417.5149841308594,381.7686767578125,144.2819366455078,-66.79857635498047,99.63053894042969,75.45003509521484,-14.283487319946289,-7.353067398071289,-131.32232666015625,17.585926055908203,142.2234649658203 +3196,9.640214,1381585,15,True,True,True,True,10,624372132,125.40902709960938,415.0857849121094,381.54461669921875,144.05487060546875,-66.8189926147461,99.83338928222656,75.62290954589844,-14.662798881530762,-7.297325611114502,-130.88812255859375,17.726865768432617,141.820068359375 +3198,9.648222,1381586,15,True,True,True,True,10,624372140,123.68729400634766,412.7767333984375,381.2567138671875,143.80137634277344,-66.83753967285156,100.05886840820312,75.80606079101562,-15.026534080505371,-7.252898693084717,-130.39268493652344,17.86966896057129,141.36119079589844 +3202,9.656216,1381587,15,True,True,True,True,0,624372148,121.95830535888672,410.5786437988281,380.91607666015625,143.51048278808594,-66.85021209716797,100.31658172607422,75.99901580810547,-15.375576972961426,-7.218266010284424,-129.83839416503906,18.01756477355957,140.8550262451172 +3204,9.664203,1381588,15,True,True,True,True,0,624372156,120.20433807373047,408.508544921875,380.5488586425781,143.1609649658203,-66.85517883300781,100.63294982910156,76.20439910888672,-15.708690643310547,-7.191256046295166,-129.2251434326172,18.168689727783203,140.3105926513672 +3206,9.672243,1381589,15,True,True,True,True,0,624372164,118.42765808105469,406.5547790527344,380.1415100097656,142.77392578125,-66.85686492919922,100.98545837402344,76.42141723632812,-16.027009963989258,-7.173375606536865,-128.55648803710938,18.322954177856445,139.72140502929688 +3209,9.680188,1381590,15,True,True,True,True,0,624372172,116.65035247802734,404.7080993652344,379.68841552734375,142.39944458007812,-66.86970520019531,101.31688690185547,76.64681243896484,-16.330671310424805,-7.165383338928223,-127.83607482910156,18.481199264526367,139.0651092529297 +3212,9.688212,1381591,15,True,True,True,True,0,624372180,114.8627700805664,402.9709167480469,379.1739807128906,142.00466918945312,-66.87996673583984,101.66539001464844,76.88177490234375,-16.619380950927734,-7.1692986488342285,-127.06795501708984,18.64378547668457,138.36489868164062 +3214,9.6962,1381592,15,True,True,True,True,0,624372188,113.06034851074219,401.338134765625,378.6170349121094,141.5557098388672,-66.88259887695312,102.0706558227539,77.1259994506836,-16.894386291503906,-7.183311939239502,-126.25247192382812,18.806116104125977,137.6370086669922 +3216,9.704252,1381593,15,True,True,True,True,0,624372196,111.25312042236328,399.8263244628906,378.028564453125,141.09161376953125,-66.89065551757812,102.4883804321289,77.3787612915039,-17.152956008911133,-7.207005023956299,-125.38971710205078,18.9699649810791,136.8600311279297 +3219,9.712217,1381594,15,True,True,True,True,0,624372204,109.42649841308594,398.4326171875,377.42919921875,140.58804321289062,-66.90116119384766,102.95076751708984,77.64083862304688,-17.39643669128418,-7.238145351409912,-124.48464965820312,19.1302433013916,136.0504913330078 +3222,9.72022,1381595,15,True,True,True,True,0,624372212,107.60835266113281,397.1470031738281,376.8443908691406,140.06265258789062,-66.92044830322266,103.43070220947266,77.90880584716797,-17.624942779541016,-7.273463249206543,-123.53858947753906,19.293027877807617,135.19915771484375 +3224,9.728221,1381596,15,True,True,True,True,0,624372220,105.78067016601562,395.962158203125,376.2601013183594,139.52720642089844,-66.94982147216797,103.91797637939453,78.18475341796875,-17.83976173400879,-7.314257621765137,-122.5552749633789,19.459537506103516,134.30445861816406 +3228,9.736259,1381597,15,True,True,True,True,0,624372228,103.94824981689453,394.87646484375,375.65679931640625,138.91986083984375,-66.96571350097656,104.48074340820312,78.46748352050781,-18.040719985961914,-7.3632659912109375,-121.53387451171875,19.627742767333984,133.400634765625 +3230,9.744218,1381598,15,True,True,True,True,0,624372236,102.13038635253906,393.87042236328125,375.053955078125,138.28939819335938,-66.98650360107422,105.06098175048828,78.75396728515625,-18.229690551757812,-7.417548179626465,-120.47813415527344,19.801082611083984,132.4635009765625 +3232,9.752204,1381599,15,True,True,True,True,0,624372244,100.32494354248047,392.9452819824219,374.437744140625,137.6129150390625,-67.00387573242188,105.68639373779297,79.04376220703125,-18.40655517578125,-7.479217052459717,-119.38755798339844,19.976137161254883,131.50555419921875 +3235,9.760162,1381600,15,True,True,True,True,0,624372252,98.52586364746094,392.12420654296875,373.81500244140625,136.947509765625,-67.0386962890625,106.29887390136719,79.3376235961914,-18.568416595458984,-7.548299312591553,-118.26834869384766,20.151470184326172,130.50125122070312 +3238,9.768214,1381601,15,True,True,True,True,0,624372260,96.72872924804688,391.3880310058594,373.17279052734375,136.2294464111328,-67.06526947021484,106.96726989746094,79.6355209350586,-18.717741012573242,-7.625756740570068,-117.11955261230469,20.328006744384766,129.48634338378906 +3240,9.776298,1381602,15,True,True,True,True,0,624372268,94.93136596679688,390.74090576171875,372.5195007324219,135.50250244140625,-67.10139465332031,107.64615631103516,79.937255859375,-18.85411834716797,-7.710776329040527,-115.94523620605469,20.504558563232422,128.43951416015625 +3243,9.784223,1381603,15,True,True,True,True,0,624372276,93.12510681152344,390.1948547363281,371.8423767089844,134.7509002685547,-67.14032745361328,108.35860443115234,80.24313354492188,-18.976215362548828,-7.805597305297852,-114.74637603759766,20.675987243652344,127.37163543701172 +3246,9.792219,1381604,15,True,True,True,True,0,624372284,91.31233978271484,389.72540283203125,371.1796875,134.00650024414062,-67.19869232177734,109.06636047363281,80.55248260498047,-19.087417602539062,-7.904299736022949,-113.5293197631836,20.846920013427734,126.26808166503906 +3248,9.800261,1381605,15,True,True,True,True,0,624372292,89.50543975830078,389.3376770019531,370.5341796875,133.18214416503906,-67.23968505859375,109.86088562011719,80.86373138427734,-19.18648910522461,-8.007132530212402,-112.29047393798828,21.018768310546875,125.17649841308594 +3252,9.808251,1381606,15,True,True,True,True,0,624372300,87.73019409179688,389.0134582519531,369.9299621582031,132.32887268066406,-67.28691101074219,110.68093872070312,81.17286682128906,-19.27520751953125,-8.110517501831055,-111.0341567993164,21.19442367553711,124.0692367553711 +3254,9.816173,1381607,15,True,True,True,True,0,624372308,85.97844696044922,388.7555847167969,369.3266906738281,131.43804931640625,-67.3298110961914,111.53753662109375,81.48095703125,-19.352859497070312,-8.219491958618164,-109.75907135009766,21.37447738647461,122.95336151123047 +3256,9.824176,1381608,15,True,True,True,True,0,624372316,84.23799133300781,388.58245849609375,368.71319580078125,130.54421997070312,-67.38007354736328,112.39989471435547,81.78900146484375,-19.417280197143555,-8.335874557495117,-108.47203826904297,21.555233001708984,121.8174057006836 +3258,9.832249,1381609,15,True,True,True,True,0,624372324,82.51824951171875,388.4728088378906,368.0925598144531,129.64047241210938,-67.43810272216797,113.27574157714844,82.09440612792969,-19.471342086791992,-8.458666801452637,-107.17201232910156,21.733739852905273,120.66265869140625 +3262,9.840221,1381610,15,True,True,True,True,0,624372332,80.8066635131836,388.44482421875,367.4778747558594,128.71563720703125,-67.49795532226562,114.18107604980469,82.39871215820312,-19.513071060180664,-8.586747169494629,-105.86317443847656,21.908784866333008,119.50104522705078 +3264,9.848197,1381611,15,True,True,True,True,0,624372340,79.08058166503906,388.4940185546875,366.8828430175781,127.78813171386719,-67.56993865966797,115.0998764038086,82.70463562011719,-19.543895721435547,-8.717835426330566,-104.54973602294922,22.0800724029541,118.32428741455078 +3266,9.85628,1381612,15,True,True,True,True,0,624372348,77.35963439941406,388.6097412109375,366.3186340332031,126.79972076416016,-67.6263656616211,116.09107208251953,83.00904846191406,-19.564807891845703,-8.850436210632324,-103.22887420654297,22.248220443725586,117.1661605834961 +3269,9.864205,1381613,15,True,True,True,True,0,624372356,75.67039489746094,388.7884521484375,365.7637939453125,125.81781005859375,-67.69437408447266,117.07598876953125,83.30853271484375,-19.57492446899414,-8.986845016479492,-101.90618133544922,22.417753219604492,115.99154663085938 +3272,9.872215,1381614,15,True,True,True,True,0,624372364,74.00950622558594,389.0209045410156,365.2460021972656,124.80306243896484,-67.7585678100586,118.09709930419922,83.60308074951172,-19.57610511779785,-9.12330150604248,-100.5813217163086,22.58809471130371,114.8231201171875 +3274,9.880253,1381615,15,True,True,True,True,0,624372372,72.36722564697266,389.2939147949219,364.7497863769531,123.74785614013672,-67.81072235107422,119.16522979736328,83.89346313476562,-19.570327758789062,-9.2609281539917,-99.25545501708984,22.75817108154297,113.66996765136719 +3278,9.888301,1381616,15,True,True,True,True,0,624372380,70.77875518798828,389.6443786621094,364.23785400390625,122.72212219238281,-67.8781509399414,120.20008850097656,84.1755142211914,-19.550704956054688,-9.40575885772705,-97.93350982666016,22.931156158447266,112.49530029296875 +3280,9.896219,1381617,15,True,True,True,True,0,624372388,69.19975280761719,390.0144348144531,363.7701416015625,121.65563201904297,-67.93639373779297,121.28809356689453,84.45289611816406,-19.528079986572266,-9.547611236572266,-96.61521911621094,23.097911834716797,111.34004974365234 +3282,9.904216,1381618,15,True,True,True,True,0,624372396,67.63304901123047,390.45892333984375,363.2980041503906,120.58830261230469,-67.99075317382812,122.38670349121094,84.72640991210938,-19.493661880493164,-9.69433879852295,-95.30561828613281,23.261611938476562,110.19219207763672 +3286,9.912248,1381619,15,True,True,True,True,0,624372404,66.09630584716797,390.9622802734375,362.84283447265625,119.51721954345703,-68.04354095458984,123.49578094482422,84.99309539794922,-19.449552536010742,-9.84266185760498,-94.00556945800781,23.42214584350586,109.05218505859375 +3288,9.920202,1381620,15,True,True,True,True,0,624372412,64.5866928100586,391.5146789550781,362.4120788574219,118.43763732910156,-68.09159088134766,124.6203384399414,85.253173828125,-19.3973331451416,-9.991011619567871,-92.71698760986328,23.579830169677734,107.92582702636719 +3290,9.92826,1381621,15,True,True,True,True,0,624372420,63.120460510253906,392.1165771484375,362.0095520019531,117.3536605834961,-68.13609313964844,125.75057220458984,85.50491333007812,-19.336414337158203,-10.138903617858887,-91.44182586669922,23.73839569091797,106.81240844726562 +3293,9.936254,1381622,15,True,True,True,True,0,624372428,61.714111328125,392.77294921875,361.6117248535156,116.27439880371094,-68.17658996582031,126.87728118896484,85.74543762207031,-19.265588760375977,-10.289359092712402,-90.17996215820312,23.895198822021484,105.7098388671875 +3296,9.944216,1381623,15,True,True,True,True,0,624372436,60.349849700927734,393.4686279296875,361.22088623046875,115.22047424316406,-68.22581481933594,127.98109436035156,85.97709655761719,-19.18749237060547,-10.440742492675781,-88.9385757446289,24.050128936767578,104.61038208007812 +3299,9.952258,1381624,15,True,True,True,True,0,624372444,59.03447341918945,394.2103271484375,360.8662414550781,114.14106750488281,-68.24974060058594,129.1140594482422,86.19908905029297,-19.101547241210938,-10.589920043945312,-87.71480560302734,24.20374298095703,103.55184936523438 +3301,9.960251,1381625,15,True,True,True,True,0,624372452,57.769798278808594,394.99188232421875,360.5167541503906,113.07850646972656,-68.27007293701172,130.23390197753906,86.41059875488281,-19.00833511352539,-10.739884376525879,-86.51300048828125,24.353130340576172,102.5108642578125 +3304,9.968228,1381626,15,True,True,True,True,0,624372460,56.5531120300293,395.8194580078125,360.2022399902344,112.03407287597656,-68.29395294189453,131.33895874023438,86.61209869384766,-18.907596588134766,-10.886996269226074,-85.33474731445312,24.498065948486328,101.48358154296875 +3307,9.976211,1381627,15,True,True,True,True,0,624372468,55.37614440917969,396.6832275390625,359.8883056640625,111.01273345947266,-68.31122589111328,132.42852783203125,86.80425262451172,-18.800609588623047,-11.034423828125,-84.18391418457031,24.635589599609375,100.48153686523438 +3309,9.984216,1381628,15,True,True,True,True,0,624372476,54.26457977294922,397.5814514160156,359.59991455078125,110.00241088867188,-68.319091796875,133.50454711914062,86.98478698730469,-18.687103271484375,-11.179450988769531,-83.0598373413086,24.772937774658203,99.50865173339844 +3313,9.992225,1381629,15,True,True,True,True,0,624372484,53.24472427368164,398.5126647949219,359.3192138671875,109.01468658447266,-68.3177490234375,134.5545196533203,87.1494140625,-18.56683349609375,-11.323851585388184,-81.96507263183594,24.90636444091797,98.56552124023438 +3315,10.000184,1381630,15,True,True,True,True,0,624372492,52.29481887817383,399.4752502441406,359.06201171875,108.05208587646484,-68.31422424316406,135.5746612548828,87.3021240234375,-18.4405574798584,-11.465413093566895,-80.90225219726562,25.039749145507812,97.64817810058594 +3317,10.008254,1381631,15,True,True,True,True,0,624372500,51.429725646972656,400.4577941894531,358.806884765625,107.11732482910156,-68.29776763916016,136.5639190673828,87.4402084350586,-18.30959701538086,-11.605825424194336,-79.87447357177734,25.168922424316406,96.76949310302734 +3320,10.016223,1381632,15,True,True,True,True,0,624372508,50.656246185302734,401.4714050292969,358.54608154296875,106.21509552001953,-68.26930236816406,137.51376342773438,87.56351470947266,-18.172046661376953,-11.746519088745117,-78.88217163085938,25.29690170288086,95.92788696289062 +3323,10.024215,1381633,15,True,True,True,True,0,624372516,49.97969055175781,402.5124206542969,358.27490234375,105.36061096191406,-68.23916625976562,138.40982055664062,87.6709976196289,-18.028520584106445,-11.887381553649902,-77.92927551269531,25.420400619506836,95.11620330810547 +3325,10.032214,1381634,15,True,True,True,True,0,624372524,49.39201736450195,403.5871276855469,358.02880859375,104.54234313964844,-68.2025375366211,139.2664794921875,87.76358795166016,-17.879182815551758,-12.023985862731934,-77.01593017578125,25.536605834960938,94.34284973144531 +3329,10.040275,1381635,15,True,True,True,True,0,624372532,48.891571044921875,404.6881103515625,357.7842712402344,103.77298736572266,-68.16290283203125,140.06907653808594,87.84214782714844,-17.724641799926758,-12.15857219696045,-76.14681243896484,25.647363662719727,93.606689453125 +3331,10.048205,1381636,15,True,True,True,True,0,624372540,48.51994705200195,405.8121643066406,357.5591125488281,103.03842163085938,-68.10606384277344,140.8241729736328,87.90142059326172,-17.564908981323242,-12.289027214050293,-75.32245635986328,25.754562377929688,92.92443084716797 +3333,10.056288,1381637,15,True,True,True,True,0,624372548,48.24958801269531,406.9616394042969,357.3499450683594,102.35932922363281,-68.05996704101562,141.5156707763672,87.94510650634766,-17.400331497192383,-12.41534423828125,-74.54397583007812,25.855194091796875,92.26859283447266 +3337,10.064208,1381638,15,True,True,True,True,0,624372556,48.09931564331055,408.1275329589844,357.17401123046875,101.72227478027344,-68.01166534423828,142.1504364013672,87.97161865234375,-17.231964111328125,-12.535117149353027,-73.8150405883789,25.9534854888916,91.65725708007812 +3339,10.072213,1381639,15,True,True,True,True,0,624372564,48.1047477722168,409.30975341796875,356.98974609375,101.13847351074219,-67.94947052001953,142.71575927734375,87.97575378417969,-17.05875015258789,-12.653133392333984,-73.13735961914062,26.0459041595459,91.1012191772461 +3341,10.080246,1381640,15,True,True,True,True,0,624372572,48.17418670654297,410.51849365234375,356.8227233886719,100.60389709472656,-67.88446807861328,143.22470092773438,87.9711685180664,-16.881078720092773,-12.766373634338379,-72.51128387451172,26.135446548461914,90.59160614013672 +3343,10.088243,1381641,15,True,True,True,True,0,624372580,48.4348030090332,411.7241516113281,356.68182373046875,100.11357879638672,-67.8087387084961,143.66419982910156,87.94032287597656,-16.701229095458984,-12.872947692871094,-71.93944549560547,26.221649169921875,90.1400375366211 +3347,10.096214,1381642,15,True,True,True,True,0,624372588,48.83818054199219,412.9461975097656,356.5220642089844,99.69750213623047,-67.73222351074219,144.01870727539062,87.88894653320312,-16.51703643798828,-12.977818489074707,-71.42557525634766,26.295114517211914,89.7369384765625 +3349,10.104215,1381643,15,True,True,True,True,0,624372596,49.38749313354492,414.1820373535156,356.4090576171875,99.3331069946289,-67.66421508789062,144.2993927001953,87.81878662109375,-16.329286575317383,-13.073507308959961,-70.97254180908203,26.366661071777344,89.37981414794922 +3351,10.112253,1381644,15,True,True,True,True,0,624372604,50.128318786621094,415.41082763671875,356.3125,99.02115631103516,-67.58089447021484,144.50498962402344,87.7228012084961,-16.139938354492188,-13.162764549255371,-70.57872772216797,26.430477142333984,89.09015655517578 +3354,10.120214,1381645,15,True,True,True,True,0,624372612,51.01435470581055,416.64434814453125,356.2255859375,98.77069091796875,-67.49978637695312,144.62985229492188,87.6082534790039,-15.94770622253418,-13.246700286865234,-70.24640655517578,26.489227294921875,88.8516845703125 +3357,10.128208,1381646,15,True,True,True,True,0,624372620,52.083457946777344,417.8816223144531,356.16986083984375,98.5747299194336,-67.41107940673828,144.6737518310547,87.47067260742188,-15.75224494934082,-13.322604179382324,-69.98064422607422,26.544572830200195,88.6801986694336 +3359,10.136286,1381647,15,True,True,True,True,0,624372628,53.36692428588867,419.10601806640625,356.1240539550781,98.44335174560547,-67.31851959228516,144.6250457763672,87.30553436279297,-15.555013656616211,-13.392265319824219,-69.78267669677734,26.593170166015625,88.57205963134766 +3363,10.144193,1381648,15,True,True,True,True,0,624372636,54.841453552246094,420.3251647949219,356.08001708984375,98.38268280029297,-67.22627258300781,144.48043823242188,87.11650848388672,-15.354955673217773,-13.456679344177246,-69.65544128417969,26.635284423828125,88.52499389648438 +3365,10.152226,1381649,15,True,True,True,True,0,624372644,56.501319885253906,421.53125,356.03369140625,98.39736938476562,-67.14768981933594,144.2360076904297,86.90455627441406,-15.152956008911133,-13.516073226928711,-69.59870147705078,26.67059898376465,88.52521514892578 +3367,10.160255,1381650,15,True,True,True,True,0,624372652,58.36298751831055,422.72589111328125,356.0167541503906,98.47077941894531,-67.08069610595703,143.90736389160156,86.66731262207031,-14.948970794677734,-13.567193984985352,-69.6041259765625,26.69806480407715,88.5696029663086 +3370,10.16821,1381651,15,True,True,True,True,0,624372660,60.43277359008789,423.9052429199219,356.0003967285156,98.60381317138672,-66.99151611328125,143.49534606933594,86.40408325195312,-14.742608070373535,-13.61310863494873,-69.67591094970703,26.716306686401367,88.69395446777344 +3373,10.17622,1381652,15,True,True,True,True,0,624372668,62.69190216064453,425.0520324707031,355.99346923828125,98.79569244384766,-66.8687515258789,142.99871826171875,86.11857604980469,-14.53605842590332,-13.651655197143555,-69.8272933959961,26.72992706298828,88.92174530029297 +3375,10.184223,1381653,15,True,True,True,True,10,624372676,65.18800354003906,426.17376708984375,355.9974365234375,99.05818939208984,-66.73738098144531,142.399169921875,85.80472564697266,-14.326932907104492,-13.682964324951172,-70.06130981445312,26.738636016845703,89.22982788085938 +3379,10.192245,1381654,15,True,True,True,True,10,624372684,67.89794158935547,427.24945068359375,356.036376953125,99.3909683227539,-66.6128921508789,141.7013702392578,85.46524810791016,-14.11877155303955,-13.703145980834961,-70.37910461425781,26.740102767944336,89.60523223876953 +3381,10.200221,1381655,15,True,True,True,True,0,624372692,70.81007385253906,428.2933044433594,356.0857849121094,99.79219818115234,-66.49137115478516,140.90769958496094,85.10235595703125,-13.908669471740723,-13.716291427612305,-70.7751235961914,26.737876892089844,90.04458618164062 +3383,10.208261,1381656,15,True,True,True,True,0,624372700,73.93500518798828,429.2813720703125,356.1429748535156,100.26338195800781,-66.37431335449219,140.0179901123047,84.7140121459961,-13.699287414550781,-13.721989631652832,-71.24866485595703,26.729209899902344,90.54545593261719 +3385,10.216243,1381657,15,True,True,True,True,0,624372708,77.25855255126953,430.2314453125,356.185546875,100.80693817138672,-66.25596618652344,139.0357666015625,84.30186462402344,-13.487676620483398,-13.72362995147705,-71.79505920410156,26.711790084838867,91.1072769165039 +3389,10.224254,1381658,15,True,True,True,True,0,624372716,80.72830200195312,431.14581298828125,356.23321533203125,101.41389465332031,-66.14092254638672,137.97824096679688,83.8725814819336,-13.274799346923828,-13.719056129455566,-72.40835571289062,26.68494415283203,91.7211685180664 +3391,10.232218,1381659,15,True,True,True,True,0,624372724,84.35954284667969,432.0051574707031,356.31927490234375,102.0634536743164,-66.02863311767578,136.8607177734375,83.4244155883789,-13.062851905822754,-13.704687118530273,-73.08108520507812,26.65335464477539,92.38321685791016 +3393,10.240249,1381660,15,True,True,True,True,0,624372732,88.15751647949219,432.8259582519531,356.4317626953125,102.76019287109375,-65.91329193115234,135.6843719482422,82.95603942871094,-12.84907341003418,-13.682271003723145,-73.81188201904297,26.611562728881836,93.09679412841797 +3397,10.248246,1381661,15,True,True,True,True,0,624372740,92.12848663330078,433.5839538574219,356.58233642578125,103.48795318603516,-65.79357147216797,134.45741271972656,82.46760559082031,-12.635673522949219,-13.650814056396484,-74.59638214111328,26.568740844726562,93.85986328125 +3399,10.25622,1381662,15,True,True,True,True,0,624372748,96.2513427734375,434.2973327636719,356.7410583496094,104.25630950927734,-65.67325592041016,133.17893981933594,81.96123504638672,-12.420004844665527,-13.614466667175293,-75.42919921875,26.520084381103516,94.66132354736328 +3401,10.264267,1381663,15,True,True,True,True,0,624372756,100.48151397705078,434.95489501953125,356.8987731933594,105.07768249511719,-65.56975555419922,131.84471130371094,81.44187927246094,-12.204477310180664,-13.57373332977295,-76.30928802490234,26.462366104125977,95.48157501220703 +3404,10.272209,1381664,15,True,True,True,True,0,624372764,104.85202026367188,435.56561279296875,357.0455322265625,105.93670654296875,-65.4617691040039,130.46327209472656,80.90631866455078,-11.985823631286621,-13.53100872039795,-77.23295593261719,26.401613235473633,96.33753967285156 +3407,10.280258,1381665,15,True,True,True,True,0,624372772,109.35726928710938,436.1214599609375,357.20953369140625,106.81672668457031,-65.34256744384766,129.05470275878906,80.35465240478516,-11.765608787536621,-13.482955932617188,-78.19474029541016,26.335603713989258,97.23389434814453 +3409,10.288223,1381666,15,True,True,True,True,0,624372780,113.9658432006836,436.5980224609375,357.4062194824219,107.7022933959961,-65.2098159790039,127.63506317138672,79.7908706665039,-11.547698974609375,-13.427617073059082,-79.18982696533203,26.268674850463867,98.17058563232422 +3413,10.296262,1381667,15,True,True,True,True,0,624372788,118.64193725585938,437.0450439453125,357.6138000488281,108.63345336914062,-65.10264587402344,126.17912292480469,79.21849822998047,-11.326825141906738,-13.368134498596191,-80.21637725830078,26.18813133239746,99.10338592529297 +3415,10.304247,1381668,15,True,True,True,True,0,624372796,123.41120910644531,437.4197998046875,357.8577575683594,109.57627868652344,-64.9955825805664,124.71038818359375,78.63471221923828,-11.107342720031738,-13.301426887512207,-81.27132415771484,26.103435516357422,100.05838012695312 +3417,10.312256,1381669,15,True,True,True,True,0,624372804,128.27499389648438,437.74346923828125,358.13543701171875,110.51943969726562,-64.88265228271484,123.23717498779297,78.04060363769531,-10.885647773742676,-13.229142189025879,-82.34819030761719,26.01992416381836,101.03562927246094 +3421,10.320256,1381670,15,True,True,True,True,0,624372812,133.19268798828125,438.0001220703125,358.4552307128906,111.46747589111328,-64.77175903320312,121.76673126220703,77.4393539428711,-10.665875434875488,-13.14941692352295,-83.44532775878906,25.928874969482422,102.02767944335938 +3423,10.328226,1381671,15,True,True,True,True,0,624372820,138.14878845214844,438.2229919433594,358.7643127441406,112.4593734741211,-64.69117736816406,120.2621078491211,76.83354949951172,-10.443089485168457,-13.069450378417969,-84.56145477294922,25.829313278198242,102.9978256225586 +3425,10.336278,1381672,15,True,True,True,True,0,624372828,143.181396484375,438.37445068359375,359.07305908203125,113.45496368408203,-64.60474395751953,118.75394439697266,76.21871185302734,-10.220117568969727,-12.988083839416504,-85.69317626953125,25.729324340820312,103.98257446289062 +3427,10.344254,1381673,15,True,True,True,True,0,624372836,148.28549194335938,438.4642028808594,359.4151611328125,114.43113708496094,-64.50714111328125,117.26580047607422,75.59584045410156,-9.996282577514648,-12.902170181274414,-86.83157348632812,25.629961013793945,104.98402404785156 +3430,10.352248,1381674,15,True,True,True,True,0,624372844,153.4009552001953,438.52154541015625,359.7765808105469,115.41838073730469,-64.4250259399414,115.77957916259766,74.97190856933594,-9.770465850830078,-12.813437461853027,-87.97576141357422,25.5235538482666,105.9721908569336 +3433,10.360218,1381675,15,True,True,True,True,0,624372852,158.51806640625,438.5401611328125,360.1959533691406,116.42816925048828,-64.37983703613281,114.28304290771484,74.34767150878906,-9.544815063476562,-12.71714973449707,-89.12491607666016,25.408700942993164,106.92697143554688 +3435,10.368227,1381676,15,True,True,True,True,0,624372860,163.68673706054688,438.5089416503906,360.66741943359375,117.40729522705078,-64.32452392578125,112.82046508789062,73.71821594238281,-9.317700386047363,-12.614922523498535,-90.2724380493164,25.294240951538086,107.89383697509766 +3439,10.376287,1381677,15,True,True,True,True,0,624372868,168.8644256591797,438.4252624511719,361.1950988769531,118.36809539794922,-64.27664947509766,111.38288879394531,73.08843231201172,-9.091459274291992,-12.506230354309082,-91.41422271728516,25.17943572998047,108.85143280029297 +3441,10.384216,1381678,15,True,True,True,True,0,624372876,173.99285888671875,438.3306579589844,361.7144470214844,119.36004638671875,-64.26072692871094,109.93820190429688,72.46448516845703,-8.863385200500488,-12.398909568786621,-92.55078887939453,25.050493240356445,109.76802062988281 +3443,10.392261,1381679,15,True,True,True,True,0,624372884,179.1262969970703,438.1837463378906,362.2779235839844,120.36650085449219,-64.27696228027344,108.48017883300781,71.84101867675781,-8.636116981506348,-12.286880493164062,-93.67918395996094,24.923320770263672,110.64356994628906 +3447,10.400222,1381680,15,True,True,True,True,0,624372892,184.2880096435547,437.9865417480469,362.8721008300781,121.32727813720703,-64.27781677246094,107.07198333740234,71.21551513671875,-8.4079008102417,-12.172564506530762,-94.79027557373047,24.79799461364746,111.52471923828125 +3449,10.408261,1381681,15,True,True,True,True,0,624372900,189.4221649169922,437.767333984375,363.5288391113281,122.27892303466797,-64.30255889892578,105.68340301513672,70.5947494506836,-8.17915153503418,-12.0523681640625,-95.88084411621094,24.669082641601562,112.3687515258789 +3451,10.416258,1381682,15,True,True,True,True,0,624372908,194.4849090576172,437.5504455566406,364.2301940917969,123.24859619140625,-64.36510467529297,104.29582977294922,69.98383331298828,-7.94942569732666,-11.928431510925293,-96.95211791992188,24.5306396484375,113.15941619873047 +3454,10.424223,1381683,15,True,True,True,True,0,624372916,199.55337524414062,437.2937316894531,365.0020751953125,124.20654296875,-64.44947814941406,102.91718292236328,69.37462615966797,-7.719427585601807,-11.797922134399414,-97.99937438964844,24.397262573242188,113.91152954101562 +3457,10.432257,1381684,15,True,True,True,True,0,624372924,204.5787811279297,437.014892578125,365.7926330566406,125.1313247680664,-64.53339385986328,101.58572387695312,68.77207946777344,-7.489384651184082,-11.667271614074707,-99.01721954345703,24.259807586669922,114.64466094970703 +3459,10.440245,1381685,15,True,True,True,True,0,624372932,209.52224731445312,436.7441101074219,366.58099365234375,126.071044921875,-64.64598083496094,100.2538833618164,68.18180084228516,-7.2577128410339355,-11.539225578308105,-100.00394439697266,24.11788558959961,115.31970977783203 +3463,10.448252,1381686,15,True,True,True,True,0,624372940,214.3939208984375,436.4573974609375,367.403076171875,127.02318572998047,-64.79237365722656,98.9198226928711,67.60171508789062,-7.027709484100342,-11.409388542175293,-100.95658874511719,23.971893310546875,115.93236541748047 +3465,10.456213,1381687,15,True,True,True,True,0,624372948,219.24798583984375,436.1600646972656,368.2453308105469,127.9545669555664,-64.94933319091797,97.6069107055664,67.02738952636719,-6.795004367828369,-11.280117988586426,-101.86851501464844,23.829551696777344,116.50200653076172 +3467,10.464254,1381688,15,True,True,True,True,0,624372956,224.02139282226562,435.8642883300781,369.1487121582031,128.8739013671875,-65.13174438476562,96.31803894042969,66.46508026123047,-6.56342887878418,-11.14638614654541,-102.73793029785156,23.682464599609375,117.01697540283203 +3469,10.472191,1381689,15,True,True,True,True,0,624372964,228.69407653808594,435.596435546875,370.1109924316406,129.79220581054688,-65.34494018554688,95.0420150756836,65.91849517822266,-6.331206798553467,-11.008773803710938,-103.56172943115234,23.532543182373047,117.46694946289062 +3473,10.480207,1381690,15,True,True,True,True,0,624372972,233.27943420410156,435.34454345703125,371.08197021484375,130.73477172851562,-65.5919418334961,93.74726867675781,65.38607025146484,-6.0981950759887695,-10.873205184936523,-104.33751678466797,23.382619857788086,117.83795928955078 +3475,10.488245,1381691,15,True,True,True,True,0,624372980,237.78533935546875,435.110595703125,372.0409851074219,131.66403198242188,-65.84833526611328,92.47235107421875,64.86737823486328,-5.8635101318359375,-10.74227237701416,-105.06135559082031,23.23455238342285,118.15510559082031 +3477,10.496189,1381692,15,True,True,True,True,0,624372988,242.18832397460938,434.8955383300781,373.04229736328125,132.59449768066406,-66.13346099853516,91.20574188232422,64.36420440673828,-5.6301984786987305,-10.609704971313477,-105.72850036621094,23.082998275756836,118.39892578125 +3480,10.504227,1381693,15,True,True,True,True,0,624372996,246.5011749267578,434.7232971191406,374.0643310546875,133.5518798828125,-66.45390319824219,89.91726684570312,63.87673568725586,-5.3944573402404785,-10.478395462036133,-106.33584594726562,22.93022346496582,118.55329132080078 +3483,10.512227,1381694,15,True,True,True,True,0,624373004,250.68072509765625,434.603759765625,375.155029296875,134.51458740234375,-66.80648803710938,88.63372802734375,63.40993881225586,-5.159176826477051,-10.342638969421387,-106.87947082519531,22.776206970214844,118.6260986328125 +3485,10.520264,1381695,15,True,True,True,True,0,624373012,254.74899291992188,434.5425720214844,376.31170654296875,135.45806884765625,-67.17884826660156,87.37833404541016,62.96200942993164,-4.922499656677246,-10.202940940856934,-107.35621643066406,22.622413635253906,118.63097381591797 +3489,10.528256,1381696,15,True,True,True,True,0,624373020,258.6773986816406,434.5411071777344,377.5142517089844,136.36386108398438,-67.5606689453125,86.17729187011719,62.535301208496094,-4.686412811279297,-10.06149959564209,-107.76644134521484,22.465272903442383,118.58189392089844 +3491,10.536279,1381697,15,True,True,True,True,0,624373028,262.4906311035156,434.5896301269531,378.7068786621094,137.32559204101562,-67.9793930053711,84.91844177246094,62.12812423706055,-4.449230670928955,-9.924949645996094,-108.1026611328125,22.314401626586914,118.42080688476562 +3493,10.544283,1381698,15,True,True,True,True,0,624373036,266.1688232421875,434.69439697265625,379.9190979003906,138.3646697998047,-68.44463348388672,83.58586120605469,61.7418098449707,-4.212831497192383,-9.789913177490234,-108.36312103271484,22.163673400878906,118.13783264160156 +3497,10.552224,1381699,15,True,True,True,True,10,624373044,269.6601867675781,434.9031982421875,381.15087890625,139.4336700439453,-68.93804931640625,82.24263763427734,61.38317108154297,-3.9761486053466797,-9.656874656677246,-108.54230499267578,22.00774383544922,117.75735473632812 +3499,10.560262,1381700,15,True,True,True,True,10,624373052,273.00775146484375,435.1961364746094,382.4444885253906,140.55709838867188,-69.46781158447266,80.85490417480469,61.04707717895508,-3.7392501831054688,-9.520499229431152,-108.64332580566406,21.850223541259766,117.27278137207031 +3501,10.568274,1381701,15,True,True,True,True,0,624373060,276.24755859375,435.55072021484375,383.79864501953125,141.7231903076172,-70.0232925415039,79.43119049072266,60.72868728637695,-3.502243995666504,-9.380512237548828,-108.67100524902344,21.69264030456543,116.70121765136719 +3504,10.576228,1381702,15,True,True,True,True,0,624373068,279.30517578125,436.00152587890625,385.14849853515625,142.98313903808594,-70.59545135498047,77.92716979980469,60.43716049194336,-3.2654080390930176,-9.242905616760254,-108.64790344238281,21.540246963500977,116.0548324584961 +3507,10.584252,1381703,15,True,True,True,True,0,624373076,282.1925048828125,436.53070068359375,386.4562072753906,144.3542938232422,-71.18424224853516,76.32172393798828,60.1702995300293,-3.0297751426696777,-9.112833023071289,-108.55866241455078,21.392887115478516,115.31505584716797 +3509,10.592228,1381704,15,True,True,True,True,0,624373084,284.95318603515625,437.1251525878906,387.7234191894531,145.80413818359375,-71.78311920166016,74.648193359375,59.92237091064453,-2.7944204807281494,-8.991050720214844,-108.38970947265625,21.24497413635254,114.48564147949219 +3512,10.600256,1381705,15,True,True,True,True,0,624373092,287.52459716796875,437.8186950683594,388.9936828613281,147.3406219482422,-72.3940658569336,72.90655517578125,59.70093536376953,-2.560915470123291,-8.872112274169922,-108.14469909667969,21.097375869750977,113.57124328613281 +3515,10.608258,1381706,15,True,True,True,True,0,624373100,289.9150695800781,438.62445068359375,390.260498046875,148.93038940429688,-73.00425720214844,71.1315689086914,59.505802154541016,-2.3272573947906494,-8.75654411315918,-107.82685089111328,20.952299118041992,112.5928726196289 +3518,10.616254,1381707,15,True,True,True,True,0,624373108,292.1515808105469,439.5239562988281,391.56378173828125,150.59677124023438,-73.62060546875,69.29894256591797,59.33256912231445,-2.0944221019744873,-8.639535903930664,-107.43962860107422,20.806636810302734,111.54881286621094 +3520,10.624255,1381708,15,True,True,True,True,0,624373116,294.22344970703125,440.5173034667969,392.8617858886719,152.3677978515625,-74.23619842529297,67.37821960449219,59.182518005371094,-1.8624720573425293,-8.525407791137695,-106.9889144897461,20.665573120117188,110.44132232666016 +3524,10.632264,1381709,15,True,True,True,True,0,624373124,296.135986328125,441.5961608886719,394.1246643066406,154.26869201660156,-74.84591674804688,65.34223937988281,59.05454635620117,-1.6315946578979492,-8.417314529418945,-106.47832489013672,20.531042098999023,109.27164459228516 +3526,10.640221,1381710,15,True,True,True,True,0,624373132,297.87744140625,442.7782287597656,395.3355407714844,156.2887725830078,-75.44285583496094,63.205650329589844,58.95030975341797,-1.4007171392440796,-8.317239761352539,-105.90816497802734,20.402509689331055,108.04817962646484 +3528,10.648263,1381711,15,True,True,True,True,0,624373140,299.4313659667969,444.053955078125,396.516357421875,158.43896484375,-76.02748107910156,60.95833206176758,58.870540618896484,-1.1724387407302856,-8.222426414489746,-105.28040313720703,20.280908584594727,106.7734146118164 +3531,10.656198,1381712,15,True,True,True,True,0,624373148,300.84063720703125,445.4036865234375,397.6720886230469,160.7965545654297,-76.60332489013672,58.51945114135742,58.809425354003906,-0.9460329413414001,-8.132179260253906,-104.59951782226562,20.163400650024414,105.43685150146484 +3534,10.664222,1381713,15,True,True,True,True,0,624373156,302.016357421875,446.88916015625,398.8154602050781,163.3616180419922,-77.1654281616211,55.90127182006836,58.77822494506836,-0.7214999198913574,-8.044858932495117,-103.86836242675781,20.050731658935547,104.04792785644531 +3536,10.672258,1381714,15,True,True,True,True,0,624373164,303.0362243652344,448.4438171386719,399.9588623046875,166.09439086914062,-77.70832061767578,53.13722229003906,58.765464782714844,-0.5006497502326965,-7.95902681350708,-103.08831024169922,19.940309524536133,102.62321472167969 +3539,10.68022,1381715,15,True,True,True,True,0,624373172,303.8891296386719,450.08056640625,401.0555114746094,169.12142944335938,-78.2274398803711,50.100746154785156,58.77249526977539,-0.2823702096939087,-7.879765510559082,-102.26608276367188,19.83211326599121,101.14534759521484 +3542,10.688258,1381716,15,True,True,True,True,0,624373180,304.504638671875,451.8419189453125,402.0873107910156,172.33401489257812,-78.7101821899414,46.905391693115234,58.80876922607422,-0.06658191978931427,-7.80902099609375,-101.4016342163086,19.734092712402344,99.6401138305664 +3544,10.696251,1381717,15,True,True,True,True,0,624373188,304.94287109375,453.69647216796875,403.0463562011719,175.77857971191406,-79.15505981445312,43.50004959106445,58.8663330078125,0.14760041236877441,-7.747790336608887,-100.49543762207031,19.642698287963867,98.10001373291016 +3548,10.70425,1381718,15,True,True,True,True,0,624373196,305.18585205078125,455.6388244628906,403.9493408203125,179.4696502685547,-79.5592041015625,39.87208557128906,58.946136474609375,0.35818493366241455,-7.693917751312256,-99.55284881591797,19.55767059326172,96.52901458740234 +3550,10.712261,1381719,15,True,True,True,True,0,624373204,305.2181701660156,457.6895446777344,404.7956848144531,-176.6760711669922,-79.91960144042969,36.10353088378906,59.051212310791016,0.5666981935501099,-7.64747953414917,-98.5726318359375,19.485004425048828,94.93958282470703 +3552,10.720243,1381720,15,True,True,True,True,0,624373212,305.09600830078125,459.801025390625,405.573974609375,-172.67938232421875,-80.23432922363281,32.209678649902344,59.17339324951172,0.772169828414917,-7.609799861907959,-97.55695343017578,19.4224910736084,93.33453369140625 +3554,10.728276,1381721,15,True,True,True,True,0,624373220,304.7278137207031,462.02362060546875,406.2914123535156,-168.55885314941406,-80.50342559814453,28.222599029541016,59.32288360595703,0.9733909964561462,-7.58001184463501,-96.509033203125,19.367326736450195,91.71705627441406 +3558,10.736267,1381722,15,True,True,True,True,0,624373228,304.14093017578125,464.33941650390625,406.9478454589844,-164.18170166015625,-80.71887969970703,24.004894256591797,59.495872497558594,1.1705716848373413,-7.558006763458252,-95.43242645263672,19.31852912902832,90.06327056884766 +3560,10.744259,1381723,15,True,True,True,True,0,624373236,303.3414001464844,466.7367858886719,407.5361022949219,-159.79901123046875,-80.88710021972656,19.805978775024414,59.69150924682617,1.3634113073349,-7.544619560241699,-94.3282470703125,19.28012466430664,88.40970611572266 +3562,10.75225,1381724,15,True,True,True,True,0,624373244,302.2953796386719,469.2449645996094,408.0157775878906,-155.3362579345703,-80.99729919433594,15.556007385253906,59.91373825073242,1.5532149076461792,-7.54433012008667,-93.20150756835938,19.249595642089844,86.737548828125 +3565,10.76026,1381725,15,True,True,True,True,0,624373252,301.0139465332031,471.83978271484375,408.4118957519531,-150.97869873046875,-81.0648193359375,11.436864852905273,60.160579681396484,1.7384333610534668,-7.554434299468994,-92.05056762695312,19.230579376220703,85.07085418701172 +3568,10.768247,1381726,15,True,True,True,True,0,624373260,299.5150451660156,474.5157165527344,408.71942138671875,-146.695068359375,-81.0868148803711,7.41769552230835,60.429443359375,1.919963002204895,-7.575383186340332,-90.87934112548828,19.21929931640625,83.40033721923828 +3570,10.776325,1381727,15,True,True,True,True,0,624373268,297.78692626953125,477.2756652832031,408.94940185546875,-142.56211853027344,-81.07272338867188,3.573960065841675,60.721797943115234,2.0977871417999268,-7.605897903442383,-89.68988037109375,19.219207763671875,81.73612213134766 +3574,10.784287,1381728,15,True,True,True,True,0,624373276,295.8445739746094,480.0986633300781,409.07183837890625,-138.654296875,-81.03009033203125,-0.01908591203391552,61.03461837768555,2.271139621734619,-7.649444103240967,-88.48387908935547,19.226755142211914,80.08654022216797 +3576,10.792221,1381729,15,True,True,True,True,0,624373284,293.6992492675781,482.97674560546875,409.09423828125,-134.9620361328125,-80.9642105102539,-3.3778066635131836,61.36717224121094,2.4405596256256104,-7.705253601074219,-87.26224517822266,19.24789810180664,78.44861602783203 +3578,10.800278,1381730,15,True,True,True,True,0,624373292,291.3208923339844,485.9117126464844,409.02374267578125,-131.525146484375,-80.8891372680664,-6.463948726654053,61.72285079956055,2.604917526245117,-7.772674083709717,-86.02691650390625,19.28821563720703,76.83380889892578 +3582,10.808254,1381731,15,True,True,True,True,0,624373300,288.6981201171875,488.9107666015625,408.8468933105469,-128.24017333984375,-80.78627014160156,-9.36968994140625,62.1010856628418,2.7642476558685303,-7.852856636047363,-84.7836685180664,19.33400535583496,75.21991729736328 +3584,10.81626,1381732,15,True,True,True,True,0,624373308,285.7994384765625,491.9901428222656,408.5599670410156,-125.26837921142578,-80.69537353515625,-11.93206787109375,62.505958557128906,2.9191856384277344,-7.946513652801514,-83.53018951416016,19.389976501464844,73.6479721069336 +3586,10.824247,1381733,15,True,True,True,True,0,624373316,282.6331787109375,495.1255798339844,408.1630859375,-122.41092681884766,-80.5776596069336,-14.35763168334961,62.93596649169922,3.0685224533081055,-8.053510665893555,-82.27330780029297,19.4594783782959,72.07088470458984 +3589,10.832255,1381734,15,True,True,True,True,0,624373324,279.2257385253906,498.2994689941406,407.6748352050781,-119.82623291015625,-80.48377990722656,-16.487985610961914,63.38779830932617,3.212496757507324,-8.171870231628418,-81.01215362548828,19.5419921875,70.54309844970703 +3592,10.840263,1381735,15,True,True,True,True,0,624373332,275.5985107421875,501.4741516113281,407.087646484375,-117.41405487060547,-80.39696502685547,-18.434309005737305,63.85875701904297,3.3493149280548096,-8.302722930908203,-79.75061798095703,19.644519805908203,69.0428695678711 +3594,10.84825,1381736,15,True,True,True,True,0,624373340,271.7253723144531,504.6831970214844,406.4099426269531,-115.19760131835938,-80.33554077148438,-20.163516998291016,64.35188293457031,3.481468677520752,-8.44494342803955,-78.48886108398438,19.760847091674805,67.5880355834961 +3596,10.856218,1381737,15,True,True,True,True,0,624373348,267.57275390625,507.9202880859375,405.6024475097656,-113.17815399169922,-80.303955078125,-21.67474937438965,64.87034606933594,3.607510805130005,-8.603297233581543,-77.22884368896484,19.894142150878906,66.18156433105469 +3600,10.864244,1381738,15,True,True,True,True,0,624373356,263.1490783691406,511.17755126953125,404.6737060546875,-111.27861785888672,-80.27843475341797,-23.04166030883789,65.41220092773438,3.727588653564453,-8.776423454284668,-75.9749984741211,20.037399291992188,64.8001708984375 +3602,10.87226,1381739,15,True,True,True,True,0,624373364,258.5020751953125,514.4091186523438,403.6387939453125,-109.552490234375,-80.30380249023438,-24.223752975463867,65.97260284423828,3.8405275344848633,-8.963253021240234,-74.7270278930664,20.200883865356445,63.487457275390625 +3604,10.880254,1381740,15,True,True,True,True,0,624373372,253.6049041748047,517.6338500976562,402.4915771484375,-107.9247817993164,-80.34978485107422,-25.290447235107422,66.5543212890625,3.9476213455200195,-9.164109230041504,-73.48998260498047,20.37989044189453,62.21474075317383 +3608,10.888183,1381741,15,True,True,True,True,0,624373380,248.4084930419922,520.8610229492188,401.199951171875,-106.4473876953125,-80.44095611572266,-26.18335723876953,67.162109375,4.04838228225708,-9.38278579711914,-72.26410675048828,20.572824478149414,61.00631332397461 +3610,10.896252,1381742,15,True,True,True,True,0,624373388,242.91651916503906,524.0755615234375,399.7734069824219,-105.0875015258789,-80.57488250732422,-26.93726348876953,67.79534912109375,4.142458438873291,-9.618293762207031,-71.05140686035156,20.780723571777344,59.85951232910156 +3612,10.904245,1381743,15,True,True,True,True,0,624373396,237.21580505371094,527.214599609375,398.2366027832031,-103.79364013671875,-80.7472915649414,-27.61859130859375,68.44463348388672,4.2283406257629395,-9.868266105651855,-69.85385131835938,21.00946617126465,58.76980209350586 +3615,10.912258,1381744,15,True,True,True,True,0,624373404,231.24400329589844,530.3094482421875,396.5665283203125,-102.5760726928711,-80.95735931396484,-28.208158493041992,69.11679077148438,4.307463645935059,-10.135022163391113,-68.67572784423828,21.25578498840332,57.7397346496582 +3618,10.920249,1381745,15,True,True,True,True,0,624373412,224.9415283203125,533.3780517578125,394.7366638183594,-101.46833801269531,-81.21885681152344,-28.66318702697754,69.81774139404297,4.380276203155518,-10.421429634094238,-67.51834106445312,21.515666961669922,56.783592224121094 +3620,10.928281,1381746,15,True,True,True,True,0,624373420,218.40980529785156,536.3494262695312,392.7808532714844,-100.42671203613281,-81.54075622558594,-29.04484748840332,70.53688049316406,4.444843769073486,-10.72425651550293,-66.38346099853516,21.796791076660156,55.910667419433594 +3624,10.936284,1381747,15,True,True,True,True,0,624373428,211.65725708007812,539.2188720703125,390.6874084472656,-99.43984985351562,-81.91800689697266,-29.363800048828125,71.27338409423828,4.502102375030518,-11.044784545898438,-65.27171325683594,22.09708023071289,55.11558532714844 +3626,10.944251,1381748,15,True,True,True,True,0,624373436,204.55853271484375,542.0195922851562,388.43450927734375,-98.51780700683594,-82.3392105102539,-29.594919204711914,72.04009246826172,4.5515007972717285,-11.385177612304688,-64.18782043457031,22.412456512451172,54.39056396484375 +3628,10.952262,1381749,15,True,True,True,True,0,624373444,197.20684814453125,544.7032470703125,386.0733642578125,-97.60317993164062,-82.82211303710938,-29.810199737548828,72.8280029296875,4.592818737030029,-11.740168571472168,-63.132080078125,22.749889373779297,53.7535400390625 +3632,10.960254,1381750,15,True,True,True,True,0,624373452,189.663330078125,547.23046875,383.5552673339844,-96.73822784423828,-83.3832015991211,-29.9746036529541,73.63072967529297,4.6258063316345215,-12.115423202514648,-62.1075325012207,23.109224319458008,53.22231674194336 +3634,10.968256,1381751,15,True,True,True,True,0,624373460,181.7731170654297,549.6415405273438,380.8443603515625,-95.96180725097656,-84.00450897216797,-30.026758193969727,74.46366882324219,4.650224685668945,-12.514416694641113,-61.11561965942383,23.480960845947266,52.779178619384766 +3636,10.976259,1381752,15,True,True,True,True,0,624373468,173.64720153808594,551.8848266601562,377.9688415527344,-95.19196319580078,-84.68146514892578,-30.069812774658203,75.31698608398438,4.666403293609619,-12.93477725982666,-60.16087341308594,23.87751579284668,52.422821044921875 +3638,10.984265,1381753,15,True,True,True,True,0,624373476,165.3243408203125,553.9340209960938,374.9032897949219,-94.472900390625,-85.43934631347656,-30.061264038085938,76.18669891357422,4.6742401123046875,-13.379408836364746,-59.24324417114258,24.295988082885742,52.17733383178711 +3641,10.992245,1381754,15,True,True,True,True,0,624373484,156.68055725097656,555.8238525390625,371.6632385253906,-93.78153991699219,-86.25102996826172,-30.007610321044922,77.0858154296875,4.674478530883789,-13.845965385437012,-58.36565399169922,24.73093032836914,52.018409729003906 +3644,11.000228,1381755,15,True,True,True,True,0,624373492,147.8558349609375,557.5018920898438,368.2860412597656,-92.90991973876953,-87.1446533203125,-30.141796112060547,78.0020980834961,4.667583465576172,-14.331485748291016,-57.529972076416016,25.197481155395508,51.97566604614258 +3647,11.008291,1381756,15,True,True,True,True,0,624373500,138.7716064453125,558.9540405273438,364.7646484375,-91.70641326904297,-88.07775115966797,-30.595600128173828,78.94197082519531,4.649759292602539,-14.835755348205566,-56.73505783081055,25.678955078125,52.00581741333008 +3651,11.016226,1381757,15,True,True,True,True,0,624373508,129.4751434326172,560.160400390625,360.9957275390625,-89.66031646728516,-89.09188079833984,-31.89193344116211,79.90262603759766,4.62429666519165,-15.370849609375,-55.99199676513672,26.182939529418945,52.15596008300781 +3653,11.024262,1381758,15,True,True,True,True,12,624373516,120.00172424316406,561.1016235351562,357.0689697265625,77.00520324707031,-89.76040649414062,162.192138671875,80.88097381591797,4.589572906494141,-15.926804542541504,-55.29413986206055,26.707860946655273,52.468406677246094 +3655,11.032253,1381759,15,True,True,True,True,12,624373524,110.30044555664062,561.791259765625,353.04241943359375,86.29427337646484,-88.56632995605469,153.66116333007812,81.88298797607422,4.545973777770996,-16.496524810791016,-54.64131164550781,27.24983787536621,52.872520446777344 +3659,11.04025,1381760,15,True,True,True,True,0,624373532,100.56083679199219,562.1729736328125,348.9328918457031,87.23255157470703,-87.2770004272461,153.46194458007812,82.89144897460938,4.492840766906738,-17.079736709594727,-54.03079605102539,27.822254180908203,53.401798248291016 +3661,11.04821,1381761,15,True,True,True,True,0,624373540,90.60687255859375,562.29443359375,344.66497802734375,87.94454956054688,-85.93634033203125,153.50782775878906,83.9229507446289,4.433080196380615,-17.682043075561523,-53.46634292602539,28.396522521972656,54.01695251464844 +3663,11.056275,1381762,15,True,True,True,True,0,624373548,80.6003646850586,562.1068115234375,340.2654724121094,88.41999053955078,-84.53704833984375,153.77841186523438,84.96412658691406,4.366186141967773,-18.30288314819336,-52.946109771728516,28.994726181030273,54.72249221801758 +3666,11.064249,1381763,15,True,True,True,True,0,624373556,70.57587432861328,561.6232299804688,335.8110656738281,88.75113677978516,-83.0787353515625,154.1881561279297,86.0113754272461,4.292346000671387,-18.932607650756836,-52.46199417114258,29.60586929321289,55.51470184326172 +3669,11.072228,1381764,15,True,True,True,True,0,624373564,60.55935287475586,560.8402709960938,331.296630859375,89.02103424072266,-81.56719970703125,154.65200805664062,87.0632095336914,4.211974620819092,-19.571565628051758,-52.0169563293457,30.228836059570312,56.38978576660156 +3671,11.080264,1381765,15,True,True,True,True,0,624373572,50.49296188354492,559.765625,326.6986389160156,89.28397369384766,-79.99800109863281,155.12765502929688,88.12594604492188,4.125916481018066,-20.22209930419922,-51.607608795166016,30.857446670532227,57.34779739379883 +3675,11.088285,1381766,15,True,True,True,True,0,624373580,40.49235916137695,558.3939208984375,322.1257019042969,89.45511627197266,-78.40243530273438,155.6823272705078,89.1896743774414,4.032595157623291,-20.872417449951172,-51.23280334472656,31.50008201599121,58.360450744628906 +3677,11.096249,1381767,15,True,True,True,True,0,624373588,30.546728134155273,556.7265014648438,317.50537109375,89.62515258789062,-76.76715850830078,156.2354278564453,90.25552368164062,3.9333722591400146,-21.530115127563477,-50.89015579223633,32.1464729309082,59.435665130615234 +3679,11.104259,1381768,15,True,True,True,True,0,624373596,20.684724807739258,554.7731323242188,312.8437805175781,89.80545043945312,-75.11473846435547,156.7762908935547,91.32124328613281,3.8290929794311523,-22.19374656677246,-50.5814323425293,32.79161071777344,60.55238723754883 +3681,11.112284,1381769,15,True,True,True,True,0,624373604,11.000368118286133,552.5509033203125,308.2319641113281,89.92733001708984,-73.40977478027344,157.3572998046875,92.37876892089844,3.719723701477051,-22.854156494140625,-50.29922866821289,33.44676208496094,61.743099212646484 +3685,11.120278,1381770,15,True,True,True,True,0,624373612,1.4547499418258667,550.0953369140625,303.6421203613281,90.05300903320312,-71.70957946777344,157.93272399902344,93.43122100830078,3.608316421508789,-23.512191772460938,-50.045894622802734,34.09380340576172,62.951820373535156 +3687,11.128197,1381771,15,True,True,True,True,0,624373620,-7.881171226501465,547.3804931640625,299.0361633300781,90.17399597167969,-69.98644256591797,158.49688720703125,94.47320556640625,3.493147373199463,-24.174013137817383,-49.82060241699219,34.744422912597656,64.2024917602539 +3689,11.136304,1381772,15,True,True,True,True,0,624373628,-17.035959243774414,544.4619750976562,294.4920959472656,90.27861022949219,-68.27284240722656,159.07093811035156,95.5068359375,3.376650333404541,-24.829282760620117,-49.61746597290039,35.389564514160156,65.46196746826172 +3693,11.14425,1381773,15,True,True,True,True,0,624373636,-25.88409996032715,541.3392333984375,290.01495361328125,90.36033630371094,-66.53620910644531,159.64239501953125,96.52025604248047,3.2573726177215576,-25.478147506713867,-49.43840408325195,36.03642272949219,66.76409912109375 +3695,11.152212,1381774,15,True,True,True,True,0,624373644,-34.547752380371094,538.0197143554688,285.5568542480469,90.46893310546875,-64.8105239868164,160.19068908691406,97.52484893798828,3.135575294494629,-26.12368392944336,-49.284034729003906,36.66353988647461,68.07369232177734 +3697,11.160288,1381775,15,True,True,True,True,0,624373652,-42.86652755737305,534.54638671875,281.2247314453125,90.53258514404297,-63.070465087890625,160.75128173828125,98.50542449951172,3.0118260383605957,-26.756397247314453,-49.14627456665039,37.296749114990234,69.41384887695312 +3700,11.168258,1381776,15,True,True,True,True,0,624373660,-50.93680191040039,530.9312133789062,276.97039794921875,90.61334228515625,-61.37033462524414,161.29635620117188,99.46935272216797,2.8858237266540527,-27.37804412841797,-49.025142669677734,37.90403747558594,70.72933197021484 +3703,11.17625,1381777,15,True,True,True,True,0,624373668,-58.64113235473633,527.20556640625,272.8262634277344,90.68195343017578,-59.6995849609375,161.82635498046875,100.40567016601562,2.7586863040924072,-27.986465454101562,-48.924293518066406,38.50379943847656,72.03459167480469 +3705,11.184274,1381778,15,True,True,True,True,0,624373676,-66.02427673339844,523.4119873046875,268.8348083496094,90.73241424560547,-58.033756256103516,162.35552978515625,101.31739807128906,2.6317193508148193,-28.57600212097168,-48.836097717285156,39.09101486206055,73.35084533691406 +3709,11.19225,1381779,15,True,True,True,True,0,624373684,-73.11619567871094,519.5580444335938,264.97222900390625,90.78561401367188,-56.417171478271484,162.87228393554688,102.20675659179688,2.5045764446258545,-29.148040771484375,-48.76271438598633,39.65629959106445,74.63484191894531 +3711,11.200227,1381780,15,True,True,True,True,0,624373692,-79.75762176513672,515.6775512695312,261.205810546875,90.83629608154297,-54.817955017089844,163.3598175048828,103.05618286132812,2.378960132598877,-29.707744598388672,-48.700355529785156,40.20761489868164,75.91316223144531 +3713,11.208248,1381781,15,True,True,True,True,0,624373700,-86.10227966308594,511.80413818359375,257.62725830078125,90.87882232666016,-53.28343200683594,163.84524536132812,103.87969970703125,2.2539849281311035,-30.242158889770508,-48.64472961425781,40.7337532043457,77.14173889160156 +3716,11.216256,1381782,15,True,True,True,True,0,624373708,-92.02748107910156,507.9815368652344,254.2470245361328,90.90129852294922,-51.797096252441406,164.31524658203125,104.6635971069336,2.130939245223999,-30.75098991394043,-48.59754180908203,41.24620056152344,78.33887481689453 +3719,11.224261,1381783,15,True,True,True,True,0,624373716,-97.54132843017578,504.2008972167969,250.9920196533203,90.93324279785156,-50.35350799560547,164.7550811767578,105.40702056884766,2.008704900741577,-31.24140167236328,-48.55771255493164,41.73001480102539,79.50578308105469 +3721,11.232256,1381784,15,True,True,True,True,0,624373724,-102.72859954833984,500.4934997558594,247.90277099609375,90.96719360351562,-49.00454330444336,165.17764282226562,106.11759948730469,1.888927936553955,-31.70801544189453,-48.524208068847656,42.18572998046875,80.5927505493164 +3723,11.240277,1381785,15,True,True,True,True,0,624373732,-107.43453979492188,496.9489440917969,245.08546447753906,90.97389221191406,-47.71528244018555,165.58018493652344,106.7757568359375,1.771817922592163,-32.13844299316406,-48.49419403076172,42.623416900634766,81.63935852050781 +3726,11.248253,1381786,15,True,True,True,True,0,624373740,-111.77057647705078,493.5506896972656,242.45986938476562,90.9850082397461,-46.489322662353516,165.9605255126953,107.39143371582031,1.6582149267196655,-32.54023361206055,-48.465999603271484,43.024749755859375,82.63723754882812 +3729,11.256249,1381787,15,True,True,True,True,0,624373748,-115.78190612792969,490.3055114746094,240.00294494628906,91.00740051269531,-45.36320114135742,166.31881713867188,107.96822357177734,1.5492368936538696,-32.91563034057617,-48.43925476074219,43.38670349121094,83.54871368408203 +3731,11.26425,1381788,15,True,True,True,True,0,624373756,-119.30037689208984,487.28424072265625,237.7772674560547,91.01702880859375,-44.32522964477539,166.64462280273438,108.4854736328125,1.4446568489074707,-33.25833511352539,-48.41107940673828,43.72262191772461,84.38704681396484 +3735,11.272261,1381789,15,True,True,True,True,10,624373764,-122.40604400634766,484.5147399902344,235.8321075439453,91.00779724121094,-43.36939239501953,166.9524688720703,108.94917297363281,1.3447811603546143,-33.561458587646484,-48.376487731933594,44.02882766723633,85.1587142944336 +3737,11.280265,1381790,15,True,True,True,True,10,624373772,-125.2439956665039,481.9543762207031,234.12745666503906,90.99652099609375,-42.51974868774414,167.24974060058594,109.37525939941406,1.2484577894210815,-33.82844543457031,-48.33838653564453,44.29641342163086,85.84024810791016 +3739,11.288253,1381791,15,True,True,True,True,0,624373780,-127.69287109375,479.6208190917969,232.6150665283203,90.98884582519531,-41.764766693115234,167.51553344726562,109.7494888305664,1.155806064605713,-34.06560134887695,-48.298587799072266,44.528076171875,86.44134521484375 +3743,11.296247,1381792,15,True,True,True,True,0,624373788,-129.76451110839844,477.5034484863281,231.28602600097656,90.98661804199219,-41.10340881347656,167.74929809570312,110.07290649414062,1.0650326013565063,-34.273826599121094,-48.25787353515625,44.72357940673828,86.96329498291016 +3745,11.304262,1381793,15,True,True,True,True,0,624373796,-131.5585479736328,475.5968322753906,230.15846252441406,90.98026275634766,-40.533203125,167.96466064453125,110.35652160644531,0.9774143695831299,-34.45165252685547,-48.21495819091797,44.889923095703125,87.40860748291016 +3747,11.312287,1381794,15,True,True,True,True,0,624373804,-133.02772521972656,473.9058532714844,229.23623657226562,90.96552276611328,-40.03916549682617,168.15557861328125,110.59493255615234,0.8913509845733643,-34.59903335571289,-48.17073059082031,45.03170394897461,87.79302978515625 +3750,11.320263,1381795,15,True,True,True,True,0,624373812,-134.19967651367188,472.42767333984375,228.5204315185547,90.94589233398438,-39.62334442138672,168.3230743408203,110.7906723022461,0.8062524199485779,-34.715126037597656,-48.126800537109375,45.14583206176758,88.11617279052734 +3753,11.328254,1381796,15,True,True,True,True,0,624373820,-135.13243103027344,471.11431884765625,227.96246337890625,90.92899322509766,-39.28504180908203,168.46853637695312,110.95198059082031,0.7207735180854797,-34.80617141723633,-48.08566665649414,45.2330436706543,88.37716674804688 +3755,11.336324,1381797,15,True,True,True,True,0,624373828,-135.83299255371094,469.9675598144531,227.5611572265625,90.91416931152344,-39.022212982177734,168.59124755859375,111.07952880859375,0.6352719664573669,-34.872379302978516,-48.04876708984375,45.29494857788086,88.57894134521484 +3759,11.344268,1381798,15,True,True,True,True,0,624373836,-136.30958557128906,468.9697265625,227.3128204345703,90.90068054199219,-38.831939697265625,168.6924591064453,111.17485046386719,0.5479317903518677,-34.914390563964844,-48.015987396240234,45.33286666870117,88.72400665283203 +3761,11.352261,1381799,15,True,True,True,True,0,624373844,-136.54092407226562,468.1160583496094,227.2064208984375,90.88463592529297,-38.70589065551758,168.7696075439453,111.23606872558594,0.45856571197509766,-34.93454360961914,-47.98667526245117,45.35264587402344,88.81897735595703 +3763,11.360284,1381800,15,True,True,True,True,0,624373852,-136.60157775878906,467.3836669921875,227.22654724121094,90.8684310913086,-38.63581085205078,168.82797241210938,111.2725601196289,0.3680817186832428,-34.934791564941406,-47.96406936645508,45.35565948486328,88.873291015625 +3765,11.368269,1381801,15,True,True,True,True,0,624373860,-136.47242736816406,466.7590026855469,227.38771057128906,90.8454818725586,-38.61207580566406,168.86614990234375,111.28335571289062,0.27386942505836487,-34.913978576660156,-47.9499397277832,45.34798812866211,88.89840698242188 +3769,11.376256,1381802,15,True,True,True,True,0,624373868,-136.23995971679688,466.2154846191406,227.6738739013672,90.82469940185547,-38.633201599121094,168.8920440673828,111.278564453125,0.17596852779388428,-34.87306594848633,-47.94588088989258,45.32268524169922,88.8963851928711 +3771,11.384245,1381803,15,True,True,True,True,0,624373876,-135.8328857421875,465.74639892578125,228.06739807128906,90.80480194091797,-38.67848587036133,168.90084838867188,111.25025177001953,0.0722169503569603,-34.815006256103516,-47.946781158447266,45.28108215332031,88.88279724121094 +3773,11.392253,1381804,15,True,True,True,True,0,624373884,-135.29942321777344,465.3265380859375,228.56996154785156,90.78132629394531,-38.77671432495117,168.90045166015625,111.20491027832031,-0.038361355662345886,-34.74034118652344,-47.95035171508789,45.22716522216797,88.82695770263672 +3776,11.400253,1381805,15,True,True,True,True,0,624373892,-134.6845703125,464.9328308105469,229.16439819335938,90.75504302978516,-38.936248779296875,168.88853454589844,111.14973449707031,-0.15495489537715912,-34.65163803100586,-47.96526336669922,45.167667388916016,88.72513580322266 +3779,11.408264,1381806,15,True,True,True,True,0,624373900,-133.9307098388672,464.556884765625,229.84059143066406,90.74295043945312,-39.13532638549805,168.8443603515625,111.0793228149414,-0.280866414308548,-34.5474967956543,-48.009727478027344,45.09407043457031,88.612548828125 +3781,11.41625,1381807,15,True,True,True,True,0,624373908,-133.12429809570312,464.1668701171875,230.57762145996094,90.73585510253906,-39.3765754699707,168.7886505126953,111.00391387939453,-0.4149438738822937,-34.432769775390625,-48.06874465942383,45.0124626159668,88.47359466552734 +3785,11.42433,1381808,15,True,True,True,True,0,624373916,-132.22642517089844,463.75634765625,231.37942504882812,90.72697448730469,-39.636627197265625,168.7201385498047,110.91970825195312,-0.5589635372161865,-34.30788803100586,-48.13994216918945,44.92839431762695,88.32978057861328 +3787,11.43227,1381809,15,True,True,True,True,0,624373924,-131.288818359375,463.2942199707031,232.21881103515625,90.722900390625,-39.90848159790039,168.64199829101562,110.83368682861328,-0.7134587168693542,-34.1759033203125,-48.225196838378906,44.839698791503906,88.1878433227539 +3789,11.440278,1381810,15,True,True,True,True,0,624373932,-130.31808471679688,462.772705078125,233.0937042236328,90.72066497802734,-40.199562072753906,168.5529022216797,110.74756622314453,-0.8783558011054993,-34.0376091003418,-48.327388763427734,44.751869201660156,88.04199981689453 +3793,11.448247,1381811,15,True,True,True,True,0,624373940,-129.30447387695312,462.17938232421875,234.00128173828125,90.72075653076172,-40.50407409667969,168.45236206054688,110.6606674194336,-1.0554763078689575,-33.89320755004883,-48.44621276855469,44.66448211669922,87.89778137207031 +3795,11.456253,1381812,15,True,True,True,True,0,624373948,-128.31077575683594,461.4835510253906,234.9216766357422,90.72479248046875,-40.82008361816406,168.34381103515625,110.58193969726562,-1.244791865348816,-33.74542999267578,-48.585723876953125,44.58126449584961,87.75857543945312 +3797,11.464267,1381813,15,True,True,True,True,0,624373956,-127.33269500732422,460.67608642578125,235.83705139160156,90.73685455322266,-41.1307258605957,168.2264404296875,110.5109634399414,-1.4468926191329956,-33.59620666503906,-48.74489212036133,44.49980163574219,87.63954162597656 +3800,11.472262,1381814,15,True,True,True,True,0,624373964,-126.38243865966797,459.7425231933594,236.744140625,90.7518310546875,-41.44779586791992,168.10157775878906,110.45038604736328,-1.6622893810272217,-33.44673156738281,-48.924224853515625,44.42698287963867,87.5286865234375 +3803,11.480256,1381815,15,True,True,True,True,0,624373972,-125.50845336914062,458.6513977050781,237.64410400390625,90.77359008789062,-41.76137924194336,167.97528076171875,110.4067611694336,-1.8939896821975708,-33.295902252197266,-49.124916076660156,44.358638763427734,87.4373779296875 +3805,11.488275,1381816,15,True,True,True,True,0,624373980,-124.67044067382812,457.394287109375,238.50302124023438,90.80250549316406,-42.06098556518555,167.83944702148438,110.37660217285156,-2.1418631076812744,-33.1486701965332,-49.34829330444336,44.30076599121094,87.37442779541016 +3807,11.496254,1381817,15,True,True,True,True,0,624373988,-123.93122100830078,455.9487609863281,239.34207153320312,90.83470916748047,-42.35144805908203,167.7034454345703,110.36833953857422,-2.4078786373138428,-33.00204086303711,-49.59557342529297,44.25486755371094,87.3375015258789 +3810,11.504214,1381818,15,True,True,True,True,0,624373996,-123.21343994140625,454.333740234375,240.12545776367188,90.87440490722656,-42.62623596191406,167.5542755126953,110.37262725830078,-2.690180778503418,-32.860660552978516,-49.86674880981445,44.2222900390625,87.33060455322266 +3813,11.512218,1381819,15,True,True,True,True,0,624374004,-122.6336441040039,452.4944763183594,240.8370819091797,90.92330169677734,-42.89973068237305,167.40403747558594,110.40576171875,-2.9906818866729736,-32.726356506347656,-50.16424560546875,44.20546340942383,87.34000396728516 +3815,11.52027,1381820,15,True,True,True,True,0,624374012,-122.16973876953125,450.4262390136719,241.46080017089844,90.98423767089844,-43.145652770996094,167.24905395507812,110.46576690673828,-3.3101024627685547,-32.600704193115234,-50.48862075805664,44.203857421875,87.39146423339844 +3819,11.528272,1381821,15,True,True,True,True,0,624374020,-121.80500030517578,448.1246337890625,241.99681091308594,91.06076049804688,-43.34814453125,167.0855712890625,110.55133056640625,-3.650071382522583,-32.48257064819336,-50.843265533447266,44.21509552001953,87.50369262695312 +3821,11.536287,1381822,15,True,True,True,True,10,624374028,-121.60588073730469,445.55804443359375,242.43377685546875,91.14433288574219,-43.516387939453125,166.9240264892578,110.67292022705078,-4.010702133178711,-32.37457275390625,-51.22603225708008,44.249629974365234,87.66435241699219 +3823,11.544299,1381823,15,True,True,True,True,10,624374036,-121.499267578125,442.7596435546875,242.76808166503906,91.23653411865234,-43.638065338134766,166.75462341308594,110.8211441040039,-4.390354633331299,-32.27663803100586,-51.63726043701172,44.306392669677734,87.88581085205078 +3826,11.552259,1381824,15,True,True,True,True,0,624374044,-121.52310180664062,439.717529296875,242.977783203125,91.3454818725586,-43.724124908447266,166.5818634033203,111.0008316040039,-4.788870334625244,-32.19041061401367,-52.07487106323242,44.378780364990234,88.15438842773438 +3829,11.560276,1381825,15,True,True,True,True,0,624374052,-121.68196105957031,436.4603271484375,243.10467529296875,91.46098327636719,-43.78707504272461,166.4139862060547,111.2118148803711,-5.204806804656982,-32.11078643798828,-52.5338249206543,44.468502044677734,88.45663452148438 +3831,11.568271,1381826,15,True,True,True,True,0,624374060,-121.920166015625,433.0035400390625,243.1195831298828,91.58525085449219,-43.82175827026367,166.2449493408203,111.44721984863281,-5.636565208435059,-32.041568756103516,-53.00989532470703,44.57529067993164,88.79228973388672 +3835,11.576314,1381827,15,True,True,True,True,0,624374068,-122.22986602783203,429.3705749511719,243.04396057128906,91.71588134765625,-43.830413818359375,166.07760620117188,111.70533752441406,-6.083077907562256,-31.979516983032227,-53.50068283081055,44.6966438293457,89.15896606445312 +3837,11.584256,1381828,15,True,True,True,True,0,624374076,-122.5905532836914,425.57586669921875,242.88656616210938,91.84347534179688,-43.8081169128418,165.91526794433594,111.98392486572266,-6.543147563934326,-31.924461364746094,-54.000762939453125,44.838199615478516,89.55768585205078 +3839,11.592255,1381829,15,True,True,True,True,0,624374084,-122.99597930908203,421.6470031738281,242.68223571777344,91.96343994140625,-43.761138916015625,165.7646484375,112.28068542480469,-7.016155242919922,-31.871477127075195,-54.504356384277344,44.99555587768555,89.98088836669922 +3843,11.600253,1381830,15,True,True,True,True,0,624374092,-123.41390991210938,417.59002685546875,242.39732360839844,92.08382415771484,-43.702789306640625,165.62026977539062,112.59132385253906,-7.501245021820068,-31.82427215576172,-55.00882339477539,45.16359329223633,90.41110229492188 +3845,11.608253,1381831,15,True,True,True,True,0,624374100,-123.82173919677734,413.4075927734375,242.02427673339844,92.20549011230469,-43.62963104248047,165.4803924560547,112.91332244873047,-7.998795986175537,-31.783418655395508,-55.51297378540039,45.34125518798828,90.85045623779297 +3847,11.616265,1381832,15,True,True,True,True,0,624374108,-124.22664642333984,409.1127624511719,241.5313720703125,92.33235931396484,-43.54001998901367,165.34515380859375,113.24705505371094,-8.505154609680176,-31.753385543823242,-56.01359939575195,45.5272102355957,91.2955551147461 +3849,11.624283,1381833,15,True,True,True,True,0,624374116,-124.63447570800781,404.74365234375,240.9571075439453,92.45974731445312,-43.44171142578125,165.2222137451172,113.59078979492188,-9.018073081970215,-31.72919273376465,-56.50495529174805,45.71656036376953,91.7376480102539 +3853,11.632255,1381834,15,True,True,True,True,0,624374124,-125.0178451538086,400.34033203125,240.36317443847656,92.57192993164062,-43.3426513671875,165.11961364746094,113.93885040283203,-9.537023544311523,-31.703811645507812,-56.978267669677734,45.91123962402344,92.16735076904297 +3855,11.64026,1381835,15,True,True,True,True,0,624374132,-125.39781951904297,395.9119873046875,239.72401428222656,92.67306518554688,-43.2382698059082,165.03890991210938,114.29325866699219,-10.058652877807617,-31.680973052978516,-57.431419372558594,46.10922622680664,92.5856704711914 +3857,11.648256,1381836,15,True,True,True,True,0,624374140,-125.72560119628906,391.46600341796875,239.03443908691406,92.76171112060547,-43.131141662597656,164.97364807128906,114.64778900146484,-10.58360767364502,-31.6614990234375,-57.8638801574707,46.31233215332031,92.98931121826172 +3860,11.656258,1381837,15,True,True,True,True,0,624374148,-125.95838165283203,387.021240234375,238.30714416503906,92.8348388671875,-43.0244140625,164.92080688476562,114.99560546875,-11.11229133605957,-31.64378547668457,-58.27290725708008,46.51969528198242,93.37450408935547 +3863,11.664253,1381838,15,True,True,True,True,0,624374156,-126.11780548095703,382.62957763671875,237.59347534179688,92.88227081298828,-42.92613983154297,164.88967895507812,115.335693359375,-11.64013957977295,-31.622600555419922,-58.651641845703125,46.73095703125,93.73233795166016 +3865,11.672288,1381839,15,True,True,True,True,0,624374164,-126.21953582763672,378.3041687011719,236.9196014404297,92.90099334716797,-42.842689514160156,164.88743591308594,115.66815185546875,-12.167000770568848,-31.594600677490234,-58.99541091918945,46.94194030761719,94.05595397949219 +3869,11.680263,1381840,15,True,True,True,True,0,624374172,-126.23942565917969,374.03515625,236.2565155029297,92.89351654052734,-42.77190017700195,164.91171264648438,115.98970031738281,-12.693913459777832,-31.563575744628906,-59.300933837890625,47.15116500854492,94.34331512451172 +3871,11.688267,1381841,15,True,True,True,True,0,624374180,-126.19998168945312,369.83575439453125,235.54833984375,92.8738021850586,-42.723419189453125,164.95986938476562,116.30099487304688,-13.215492248535156,-31.53740119934082,-59.56606674194336,47.350772857666016,94.57852935791016 +3873,11.696271,1381842,15,True,True,True,True,0,624374188,-126.09178161621094,365.73004150390625,234.84046936035156,92.8355941772461,-42.70685577392578,165.03244018554688,116.59884643554688,-13.731775283813477,-31.510364532470703,-59.79119110107422,47.54104232788086,94.75626373291016 +3876,11.704258,1381843,15,True,True,True,True,0,624374196,-125.89244842529297,361.7691955566406,234.25314331054688,92.74527740478516,-42.707088470458984,165.1471405029297,116.87609100341797,-14.243485450744629,-31.469280242919922,-59.96164321899414,47.73031234741211,94.89134216308594 +3879,11.712271,1381844,15,True,True,True,True,0,624374204,-125.60099792480469,357.96514892578125,233.73654174804688,92.61199951171875,-42.72716522216797,165.30023193359375,117.13009643554688,-14.74638843536377,-31.42171859741211,-60.07387161254883,47.91381072998047,94.97370147705078 +3881,11.720265,1381845,15,True,True,True,True,0,624374212,-125.21913146972656,354.3076477050781,233.27230834960938,92.44229888916016,-42.77708053588867,165.48854064941406,117.36062622070312,-15.241125106811523,-31.369586944580078,-60.129947662353516,48.08794021606445,94.99357604980469 +3885,11.728257,1381846,15,True,True,True,True,0,624374220,-124.72697448730469,350.8006896972656,232.84683227539062,92.23941802978516,-42.829341888427734,165.70681762695312,117.56382751464844,-15.727336883544922,-31.314607620239258,-60.13251495361328,48.251319885253906,94.97904205322266 +3887,11.736331,1381847,15,True,True,True,True,0,624374228,-124.12940216064453,347.48040771484375,232.46533203125,92.01495361328125,-42.937828063964844,165.93894958496094,117.73758697509766,-16.19986343383789,-31.256393432617188,-60.093814849853516,48.4023551940918,94.88419342041016 +3889,11.744291,1381848,15,True,True,True,True,0,624374236,-123.4677963256836,344.3819274902344,232.24502563476562,91.72811126708984,-43.12271499633789,166.22958374023438,117.88127899169922,-16.659122467041016,-31.183679580688477,-59.9778938293457,48.542518615722656,94.6732177734375 +3891,11.752265,1381849,15,True,True,True,True,0,624374244,-122.72911834716797,341.5069885253906,232.17848205566406,91.38155364990234,-43.36231231689453,166.5791015625,117.99114990234375,-17.10546875,-31.09714126586914,-59.7841682434082,48.666629791259766,94.36649322509766 +3895,11.760326,1381850,15,True,True,True,True,10,624374252,-121.90605163574219,338.8580627441406,232.2240447998047,90.97972869873047,-43.650177001953125,166.98806762695312,118.06391906738281,-17.536325454711914,-31.00341796875,-59.507286071777344,48.77106475830078,93.9618148803711 +3897,11.768275,1381851,15,True,True,True,True,10,624374260,-121.01898193359375,336.4232177734375,232.37217712402344,90.52175903320312,-43.97385025024414,167.4577178955078,118.10304260253906,-17.950679779052734,-30.90458106994629,-59.150360107421875,48.85980987548828,93.47119140625 +3899,11.776329,1381852,15,True,True,True,True,0,624374268,-120.0373764038086,334.2149963378906,232.6699981689453,89.9971694946289,-44.328819274902344,167.98919677734375,118.10247039794922,-18.35061264038086,-30.79446792602539,-58.71333694458008,48.9354248046875,92.90172576904297 +3903,11.784267,1381853,15,True,True,True,True,0,624374276,-119.04951477050781,332.2264709472656,233.14747619628906,89.40137481689453,-44.725555419921875,168.5984344482422,118.07475280761719,-18.733613967895508,-30.670204162597656,-58.19801330566406,48.99909973144531,92.24420166015625 +3905,11.792255,1381854,15,True,True,True,True,0,624374284,-117.93579864501953,330.4782409667969,233.76498413085938,88.74179077148438,-45.15715026855469,169.26565551757812,117.99946594238281,-19.100486755371094,-30.53636932373047,-57.60423278808594,49.046058654785156,91.50035858154297 +3907,11.800287,1381855,15,True,True,True,True,0,624374292,-116.83290100097656,328.92669677734375,234.554931640625,88.01510620117188,-45.64715576171875,170.01821899414062,117.89906311035156,-19.452247619628906,-30.38800048828125,-56.9355354309082,49.07415008544922,90.64971923828125 +3910,11.808256,1381856,15,True,True,True,True,0,624374300,-115.67511749267578,327.5589294433594,235.4903564453125,87.21878051757812,-46.15645980834961,170.84461975097656,117.76558685302734,-19.790786743164062,-30.228797912597656,-56.1950569152832,49.091278076171875,89.7284927368164 +3913,11.816266,1381857,15,True,True,True,True,0,624374308,-114.41806030273438,326.3883056640625,236.56089782714844,86.35859680175781,-46.6778564453125,171.73590087890625,117.59046173095703,-20.115787506103516,-30.059499740600586,-55.38541030883789,49.09316635131836,88.74234771728516 +3915,11.824324,1381858,15,True,True,True,True,0,624374316,-113.11241912841797,325.394287109375,237.76075744628906,85.42993927001953,-47.24531173706055,172.70358276367188,117.38314056396484,-20.425838470458984,-29.881664276123047,-54.51032638549805,49.084686279296875,87.65589141845703 +3919,11.832243,1381859,15,True,True,True,True,0,624374324,-111.74691772460938,324.5742492675781,239.07449340820312,84.43966674804688,-47.829833984375,173.74325561523438,117.14146423339844,-20.72081184387207,-29.696651458740234,-53.57245635986328,49.0622673034668,86.49604034423828 +3921,11.84027,1381860,15,True,True,True,True,0,624374332,-110.3230209350586,323.9205017089844,240.49728393554688,83.39007568359375,-48.40657043457031,174.85501098632812,116.86566925048828,-21.001008987426758,-29.504945755004883,-52.57125473022461,49.02517318725586,85.2838363647461 +3923,11.848262,1381861,15,True,True,True,True,0,624374340,-108.85269165039062,323.4179992675781,242.04833984375,82.27206420898438,-49.01449203491211,176.05072021484375,116.55902099609375,-21.268413543701172,-29.30316162109375,-51.510738372802734,48.97512435913086,83.98129272460938 +3926,11.856249,1381862,15,True,True,True,True,0,624374348,-107.33623504638672,323.0527648925781,243.71920776367188,81.08605194091797,-49.630496978759766,177.33009338378906,116.22281646728516,-21.52364730834961,-29.09227180480957,-50.392425537109375,48.914512634277344,82.60841369628906 +3929,11.864253,1381863,15,True,True,True,True,0,624374356,-105.78987884521484,322.8253479003906,245.50411987304688,79.84213256835938,-50.241275787353516,178.69320678710938,115.85832977294922,-21.76600456237793,-28.87208366394043,-49.216392517089844,48.835262298583984,81.17510223388672 +3931,11.872279,1381864,15,True,True,True,True,0,624374364,-104.21399688720703,322.7200622558594,247.3854217529297,78.53192901611328,-50.8608512878418,-179.85198974609375,115.46749114990234,-21.99526023864746,-28.64574432373047,-47.98221206665039,48.744510650634766,79.66117858886719 +3933,11.880285,1381865,15,True,True,True,True,0,624374372,-102.60032653808594,322.73309326171875,249.3417510986328,77.16954803466797,-51.46653747558594,-178.31768798828125,115.0492935180664,-22.211137771606445,-28.41510772705078,-46.69349670410156,48.63897705078125,78.0881118774414 +3936,11.888253,1381866,15,True,True,True,True,0,624374380,-100.9320297241211,322.8638916015625,251.3873291015625,75.75325775146484,-52.03291702270508,-176.71156311035156,114.60208129882812,-22.413734436035156,-28.179134368896484,-45.350528717041016,48.526485443115234,76.47882843017578 +3939,11.896261,1381867,15,True,True,True,True,0,624374388,-99.20100402832031,323.0946044921875,253.5155029296875,74.27923583984375,-52.587806701660156,-175.02890014648438,114.12687683105469,-22.6051082611084,-27.93730354309082,-43.960086822509766,48.40852737426758,74.80707550048828 +3941,11.904278,1381868,15,True,True,True,True,0,624374396,-97.41834259033203,323.4264831542969,255.74887084960938,72.75130462646484,-53.125553131103516,-173.26898193359375,113.62511444091797,-22.78585433959961,-27.68562126159668,-42.523704528808594,48.28089904785156,73.0781021118164 +3945,11.912331,1381869,15,True,True,True,True,0,624374404,-95.60326385498047,323.834228515625,258.02508544921875,71.18264770507812,-53.65730667114258,-171.4304656982422,113.10099792480469,-22.95597267150879,-27.43102264404297,-41.040260314941406,48.13847732543945,71.27426147460938 +3947,11.920252,1381870,15,True,True,True,True,0,624374412,-93.76256561279297,324.3349304199219,260.39501953125,69.56796264648438,-54.16923904418945,-169.515380859375,112.55585479736328,-23.1138973236084,-27.167890548706055,-39.51389694213867,47.987178802490234,69.41187286376953 +3949,11.928333,1381871,15,True,True,True,True,0,624374420,-91.87643432617188,324.9239501953125,262.840087890625,67.92597198486328,-54.64672088623047,-167.5404510498047,111.98637390136719,-23.260618209838867,-26.897340774536133,-37.942386627197266,47.82198715209961,67.50222778320312 +3953,11.936313,1381872,15,True,True,True,True,0,624374428,-89.93981170654297,325.5944519042969,265.34747314453125,66.2537841796875,-55.089237213134766,-165.50779724121094,111.39358520507812,-23.394968032836914,-26.62241554260254,-36.32621765136719,47.652809143066406,65.54241180419922 +3955,11.944259,1381873,15,True,True,True,True,0,624374436,-87.98443603515625,326.3197937011719,267.8904724121094,64.57321166992188,-55.50721740722656,-163.42518615722656,110.78450012207031,-23.5196533203125,-26.343059539794922,-34.67241287231445,47.468971252441406,63.528526306152344 +3957,11.952328,1381874,15,True,True,True,True,0,624374444,-85.95758819580078,327.1045837402344,270.4881591796875,62.87792205810547,-55.88331604003906,-161.30682373046875,110.15226745605469,-23.634708404541016,-26.058712005615234,-32.98112106323242,47.28586959838867,61.47627258300781 +3960,11.960267,1381875,15,True,True,True,True,0,624374452,-83.83343505859375,327.9516296386719,273.1360168457031,61.19216537475586,-56.21064376831055,-159.17794799804688,109.492431640625,-23.74103546142578,-25.76825523376465,-31.253101348876953,47.09696578979492,59.39582443237305 +3963,11.968274,1381876,15,True,True,True,True,0,624374460,-81.68270874023438,328.8439636230469,275.8304443359375,59.509376525878906,-56.51408386230469,-157.01577758789062,108.81807708740234,-23.838056564331055,-25.471717834472656,-29.49117660522461,46.90141677856445,57.26285934448242 +3965,11.976271,1381877,15,True,True,True,True,0,624374468,-79.5346450805664,329.7799377441406,278.5563049316406,57.856075286865234,-56.79039764404297,-154.83663940429688,108.13360595703125,-23.9251708984375,-25.169696807861328,-27.693687438964844,46.69103240966797,55.08340835571289 +3969,11.98433,1381878,15,True,True,True,True,0,624374476,-77.27816772460938,330.7681579589844,281.2845764160156,56.22297668457031,-57.05495834350586,-152.6531524658203,107.42281341552734,-24.002670288085938,-24.866222381591797,-25.866239547729492,46.47443389892578,52.8453369140625 +3971,11.992252,1381879,15,True,True,True,True,0,624374484,-74.94338989257812,331.78985595703125,284.01263427734375,54.65742492675781,-57.264644622802734,-150.51788330078125,106.69229888916016,-24.071443557739258,-24.561565399169922,-24.008575439453125,46.2559928894043,50.60211944580078 +3973,12.000264,1381880,15,True,True,True,True,0,624374492,-72.62871551513672,332.8229064941406,286.7192077636719,53.15365982055664,-57.45124053955078,-148.39877319335938,105.95886993408203,-24.131328582763672,-24.257204055786133,-22.11896324157715,46.029815673828125,48.321632385253906 +3975,12.008285,1381881,15,True,True,True,True,0,624374500,-70.2340316772461,333.88592529296875,289.4297790527344,51.69032287597656,-57.6389274597168,-146.29135131835938,105.20691680908203,-24.184019088745117,-23.948583602905273,-20.203458786010742,45.794918060302734,45.98439407348633 +3979,12.01627,1381882,15,True,True,True,True,0,624374508,-67.7084732055664,334.9880676269531,292.1375427246094,50.32843780517578,-57.77904510498047,-144.2830047607422,104.4294662475586,-24.228174209594727,-23.637775421142578,-18.265949249267578,45.559261322021484,43.65858840942383 +3981,12.024271,1381883,15,True,True,True,True,0,624374516,-65.18048858642578,336.0957336425781,294.8180847167969,49.04400634765625,-57.91043472290039,-142.3155517578125,103.64832305908203,-24.264497756958008,-23.32660484313965,-16.30111312866211,45.317935943603516,41.299129486083984 +3983,12.032265,1381884,15,True,True,True,True,0,624374524,-62.580223083496094,337.21337890625,297.5026550292969,47.798282623291016,-58.057613372802734,-140.36517333984375,102.85425567626953,-24.295137405395508,-23.010372161865234,-14.318857192993164,45.074344635009766,38.885772705078125 +3986,12.040285,1381885,15,True,True,True,True,0,624374532,-59.86770248413086,338.3587646484375,300.18634033203125,46.67607116699219,-58.1701545715332,-138.53721618652344,102.04021453857422,-24.318147659301758,-22.690113067626953,-12.316446304321289,44.82754135131836,36.49052047729492 +3989,12.048263,1381886,15,True,True,True,True,0,624374540,-57.149169921875,339.5032043457031,302.86114501953125,45.626468658447266,-58.28974533081055,-136.75416564941406,101.2251205444336,-24.33454132080078,-22.366268157958984,-10.293203353881836,44.578125,34.06315994262695 +3991,12.056255,1381887,15,True,True,True,True,0,624374548,-54.34042739868164,340.6604309082031,305.53070068359375,44.61415481567383,-58.445247650146484,-134.99330139160156,100.39647674560547,-24.34473419189453,-22.037534713745117,-8.256256103515625,44.32380294799805,31.577625274658203 +3995,12.064324,1381888,15,True,True,True,True,0,624374556,-51.42537307739258,341.8189392089844,308.1561279296875,43.74842834472656,-58.57406234741211,-133.3863983154297,99.55245208740234,-24.348825454711914,-21.709190368652344,-6.20344877243042,44.06959915161133,29.126699447631836 +3997,12.07227,1381889,15,True,True,True,True,0,624374564,-48.52066421508789,342.9499816894531,310.7291564941406,42.947486877441406,-58.7319221496582,-131.8196563720703,98.71354675292969,-24.348337173461914,-21.38132095336914,-4.13572359085083,43.814579010009766,26.639850616455078 +3999,12.080272,1381890,15,True,True,True,True,0,624374572,-45.473785400390625,344.0833740234375,313.25921630859375,42.208763122558594,-58.927696228027344,-130.3203125,97.85551452636719,-24.342973709106445,-21.051881790161133,-2.059227466583252,43.55326461791992,24.11951446533203 +4003,12.088255,1381891,15,True,True,True,True,0,624374580,-42.325408935546875,345.2196960449219,315.7299499511719,41.59897232055664,-59.12478256225586,-128.96409606933594,96.98585510253906,-24.33037757873535,-20.724443435668945,0.02518394961953163,43.29084396362305,21.62527847290039 +4005,12.096281,1381892,15,True,True,True,True,0,624374588,-39.173919677734375,346.3179626464844,318.17633056640625,41.06978988647461,-59.344112396240234,-127.6817855834961,96.1217041015625,-24.315961837768555,-20.392602920532227,2.1196162700653076,43.026519775390625,19.123844146728516 +4007,12.104276,1381893,15,True,True,True,True,0,624374596,-35.88883590698242,347.4147033691406,320.59283447265625,40.597633361816406,-59.599910736083984,-126.47406768798828,95.24244689941406,-24.29595947265625,-20.058719635009766,4.218278884887695,42.762020111083984,16.604354858398438 +4010,12.112287,1381894,15,True,True,True,True,0,624374604,-32.558021545410156,348.49566650390625,322.9479064941406,40.18016815185547,-59.896400451660156,-125.32471466064453,94.36289978027344,-24.269777297973633,-19.727140426635742,6.323051452636719,42.499698638916016,14.064419746398926 +4013,12.120273,1381895,15,True,True,True,True,0,624374612,-29.16278076171875,349.549072265625,325.2265625,39.863922119140625,-60.21913528442383,-124.29351806640625,93.48117065429688,-24.239795684814453,-19.397920608520508,8.431142807006836,42.23369216918945,11.539570808410645 +4015,12.128282,1381896,15,True,True,True,True,0,624374620,-25.65663719177246,350.5804443359375,327.4317932128906,39.62992477416992,-60.57651138305664,-123.3714828491211,92.59050750732422,-24.20560073852539,-19.071077346801758,10.53984546661377,41.96474075317383,9.021772384643555 +4017,12.136327,1381897,15,True,True,True,True,0,624374628,-22.12195587158203,351.580322265625,329.53985595703125,39.42491912841797,-60.98957443237305,-122.4870834350586,91.70457458496094,-24.166250228881836,-18.750925064086914,12.649534225463867,41.69965744018555,6.4774088859558105 +4020,12.144272,1381898,15,True,True,True,True,0,624374636,-18.464771270751953,352.552734375,331.5780944824219,39.312950134277344,-61.43048858642578,-121.73477935791016,90.80927276611328,-24.123445510864258,-18.43267250061035,14.756149291992188,41.43109130859375,3.9580345153808594 +4023,12.152268,1381899,15,True,True,True,True,0,624374644,-14.756808280944824,353.478759765625,333.5526123046875,39.25162124633789,-61.9055290222168,-121.05917358398438,89.91647338867188,-24.07831573486328,-18.11680030822754,16.858131408691406,41.16841506958008,1.4450992345809937 +4025,12.160279,1381900,15,True,True,True,True,0,624374652,-10.977298736572266,354.3654479980469,335.4512939453125,39.23499298095703,-62.42605209350586,-120.4592514038086,89.02347564697266,-24.030357360839844,-17.804096221923828,18.9543399810791,40.90439987182617,-1.068416953086853 +4029,12.168287,1381901,15,True,True,True,True,0,624374660,-7.099353790283203,355.2104797363281,337.259521484375,39.31477737426758,-62.97128677368164,-120.00101470947266,88.12653350830078,-23.9796199798584,-17.496484756469727,21.042783737182617,40.638427734375,-3.545161008834839 +4031,12.176286,1381902,15,True,True,True,True,0,624374668,-3.2058908939361572,355.9938049316406,338.9814453125,39.39383316040039,-63.56818771362305,-119.5633773803711,87.23826599121094,-23.92766761779785,-17.19463348388672,23.124347686767578,40.38117599487305,-6.041220664978027 +4033,12.184301,1381903,15,True,True,True,True,0,624374676,0.7943246364593506,356.7378845214844,340.62939453125,39.546817779541016,-64.19318389892578,-119.25102996826172,86.34576416015625,-23.87262725830078,-16.89670753479004,25.192533493041992,40.121524810791016,-8.505319595336914 +4036,12.192283,1381904,15,True,True,True,True,0,624374684,4.829203128814697,357.4217224121094,342.2249755859375,39.73344421386719,-64.84707641601562,-119.00675964355469,85.45931243896484,-23.816753387451172,-16.600923538208008,27.250669479370117,39.869808197021484,-10.957869529724121 +4039,12.200265,1381905,15,True,True,True,True,0,624374692,8.911667823791504,358.0639953613281,343.7649841308594,39.95722961425781,-65.53228759765625,-118.8405532836914,84.5780258178711,-23.75762367248535,-16.30832862854004,29.293813705444336,39.6208610534668,-13.394024848937988 +4041,12.208287,1381906,15,True,True,True,True,0,624374700,13.081026077270508,358.6374816894531,345.2853088378906,40.236637115478516,-66.23283386230469,-118.7829360961914,83.69562530517578,-23.69977569580078,-16.013330459594727,31.319293975830078,39.37357711791992,-15.796792984008789 +4044,12.21627,1381907,15,True,True,True,True,0,624374708,17.2440185546875,359.14801025390625,346.735595703125,40.515533447265625,-66.9698715209961,-118.75444793701172,82.82597351074219,-23.640932083129883,-15.723977088928223,33.328582763671875,39.13554763793945,-18.199811935424805 +4047,12.224214,1381908,15,True,True,True,True,0,624374716,21.495676040649414,359.58819580078125,348.10321044921875,40.86940383911133,-67.72543334960938,-118.85812377929688,81.95520782470703,-23.58257484436035,-15.440187454223633,35.317665100097656,38.894798278808594,-20.56273651123047 +4049,12.232278,1381909,15,True,True,True,True,0,624374724,25.769033432006836,359.95660400390625,349.4153747558594,41.24824523925781,-68.50090789794922,-119.02629089355469,81.09244537353516,-23.52451515197754,-15.160717010498047,37.28662872314453,38.66331100463867,-22.905982971191406 +4053,12.240273,1381910,15,True,True,True,True,0,624374732,30.044464111328125,360.2693786621094,350.6841735839844,41.64479446411133,-69.29669952392578,-119.25045776367188,80.24148559570312,-23.465015411376953,-14.88501262664795,39.23122787475586,38.438720703125,-25.22911834716797 +4055,12.248318,1381911,15,True,True,True,True,0,624374740,34.3908805847168,360.50994873046875,351.92144775390625,42.14444351196289,-70.08621215820312,-119.63566589355469,79.39156341552734,-23.406543731689453,-14.61032485961914,41.15082931518555,38.21414566040039,-27.492298126220703 +4057,12.256279,1381912,15,True,True,True,True,0,624374748,38.694671630859375,360.696533203125,353.1018981933594,42.61786651611328,-70.9039077758789,-120.01884460449219,78.55889129638672,-23.346044540405273,-14.342774391174316,43.045867919921875,38.00398254394531,-29.75619125366211 +4059,12.264324,1381913,15,True,True,True,True,0,624374756,43.036014556884766,360.7868957519531,354.2334289550781,43.16702651977539,-71.72472381591797,-120.53128051757812,77.73170471191406,-23.290584564208984,-14.077410697937012,44.911651611328125,37.790401458740234,-31.974302291870117 +4063,12.272257,1381914,15,True,True,True,True,0,624374764,47.39338302612305,360.81884765625,355.2763977050781,43.830894470214844,-72.5350341796875,-121.20182037353516,76.91255187988281,-23.232980728149414,-13.823307991027832,46.7480583190918,37.586177825927734,-34.1298713684082 +4065,12.280226,1381915,15,True,True,True,True,0,624374772,51.676902770996094,360.7638854980469,356.2452697753906,44.4726676940918,-73.36970520019531,-121.87236785888672,76.11347961425781,-23.17938804626465,-13.577096939086914,48.5543098449707,37.39323806762695,-36.28230285644531 +4067,12.288245,1381916,15,True,True,True,True,0,624374780,56.02234649658203,360.6475524902344,357.1147766113281,45.266204833984375,-74.19303894042969,-122.75132751464844,75.31607818603516,-23.1243953704834,-13.342732429504395,50.32675552368164,37.19765853881836,-38.36497497558594 +4070,12.296255,1381917,15,True,True,True,True,0,624374788,60.3067626953125,360.4399719238281,357.9480285644531,46.09854507446289,-75.01058197021484,-123.69226837158203,74.53472900390625,-23.074115753173828,-13.113016128540039,52.065181732177734,37.01937484741211,-40.414737701416016 +4073,12.304279,1381918,15,True,True,True,True,0,624374796,64.51933288574219,360.18243408203125,358.7062683105469,47.00604248046875,-75.82634735107422,-124.73387145996094,73.77355194091797,-23.02351951599121,-12.893815040588379,53.76571273803711,36.84886169433594,-42.42283630371094 +4075,12.31233,1381919,15,True,True,True,True,0,624374804,68.76235961914062,359.8354187011719,359.40966796875,48.05302047729492,-76.62728881835938,-125.9635238647461,73.01603698730469,-22.97715187072754,-12.680418968200684,55.428916931152344,36.676307678222656,-44.37430191040039 +4079,12.320298,1381920,15,True,True,True,True,0,624374812,72.88900756835938,359.4199523925781,360.07611083984375,49.18098068237305,-77.41265106201172,-127.283935546875,72.28141784667969,-22.934335708618164,-12.473212242126465,57.054874420166016,36.52292251586914,-46.2846794128418 +4081,12.32826,1381921,15,True,True,True,True,0,624374820,76.93367004394531,358.9466857910156,360.673828125,50.42153549194336,-78.18467712402344,-128.73753356933594,71.56597137451172,-22.893522262573242,-12.276359558105469,58.63665008544922,36.37968063354492,-48.145023345947266 +4083,12.336325,1381922,15,True,True,True,True,0,624374828,80.97869873046875,358.3875427246094,361.24017333984375,51.886539459228516,-78.9180908203125,-130.45570373535156,70.85653686523438,-22.857833862304688,-12.083677291870117,60.17523956298828,36.237491607666016,-49.9287223815918 +4086,12.34429,1381923,15,True,True,True,True,0,624374836,84.88142395019531,357.78289794921875,361.7310485839844,53.49702453613281,-79.63530731201172,-132.32164001464844,70.173095703125,-22.82424545288086,-11.902813911437988,61.672271728515625,36.11186599731445,-51.66756820678711 +4089,12.352241,1381924,15,True,True,True,True,0,624374844,88.67981719970703,357.1235656738281,362.18280029296875,55.20924758911133,-80.344482421875,-134.30233764648438,69.5102767944336,-22.79462242126465,-11.728914260864258,63.12063980102539,35.9953727722168,-53.37199783325195 +4091,12.360328,1381925,15,True,True,True,True,0,624374852,92.4459228515625,356.38995361328125,362.57928466796875,57.21701431274414,-81.00675201416016,-136.60647583007812,68.85618591308594,-22.769664764404297,-11.563624382019043,64.51937866210938,35.88462829589844,-54.993255615234375 +4094,12.368276,1381926,15,True,True,True,True,0,624374860,96.05433654785156,355.6086120605469,362.9294738769531,59.50339126586914,-81.6247329711914,-139.18771362304688,68.22820281982422,-22.749752044677734,-11.40637493133545,65.8708724975586,35.78897476196289,-56.543827056884766 +4097,12.376286,1381927,15,True,True,True,True,0,624374868,99.50727844238281,354.7942199707031,363.20855712890625,61.974979400634766,-82.22991180419922,-141.95147705078125,67.62655639648438,-22.73326301574707,-11.261079788208008,67.16948699951172,35.70661926269531,-58.055118560791016 +4099,12.384276,1381928,15,True,True,True,True,0,624374876,102.89176177978516,353.9079895019531,363.4580383300781,64.80662536621094,-82.77730560302734,-145.0926971435547,67.03668212890625,-22.72393226623535,-11.121232986450195,68.41433715820312,35.63066482543945,-59.4821662902832 +4101,12.392331,1381929,15,True,True,True,True,0,624374884,106.14049530029297,352.9867248535156,363.6713562011719,67.92698669433594,-83.2845458984375,-148.52044677734375,66.46855926513672,-22.718887329101562,-10.989352226257324,69.60376739501953,35.56863784790039,-60.846466064453125 +4105,12.400266,1381930,15,True,True,True,True,0,624374892,109.16609954833984,352.0522155761719,363.8081359863281,71.35563659667969,-83.75470733642578,-152.2365264892578,65.93485260009766,-22.71855354309082,-10.87050724029541,70.73515319824219,35.523868560791016,-62.1476936340332 +4107,12.408283,1381931,15,True,True,True,True,0,624374900,112.0675277709961,351.0825500488281,363.8821105957031,75.17972564697266,-84.16401672363281,-156.3471221923828,65.42029571533203,-22.723188400268555,-10.762609481811523,71.8088150024414,35.48991394042969,-63.36391830444336 +4109,12.416305,1381932,15,True,True,True,True,0,624374908,114.84362030029297,350.0626220703125,363.8871765136719,79.33200073242188,-84.52690887451172,-160.784912109375,64.92359161376953,-22.73541259765625,-10.665629386901855,72.82276153564453,35.46583557128906,-64.5113525390625 +4113,12.424266,1381933,15,True,True,True,True,0,624374916,117.42918395996094,349.0406494140625,363.7983703613281,83.82897186279297,-84.83070373535156,-165.5512237548828,64.45620727539062,-22.751432418823242,-10.584120750427246,73.7755355834961,35.453609466552734,-65.57466125488281 +4115,12.432283,1381934,15,True,True,True,True,0,624374924,119.81390380859375,348.0002746582031,363.64312744140625,88.52701568603516,-85.09394073486328,-170.49981689453125,64.01766967773438,-22.77486228942871,-10.513976097106934,74.66410064697266,35.45493698120117,-66.57367706298828 +4117,12.440309,1381935,15,True,True,True,True,0,624374932,122.02006530761719,346.93212890625,363.4110107421875,93.37092590332031,-85.31330871582031,-175.57943725585938,63.60384750366211,-22.80650520324707,-10.456289291381836,75.48624420166016,35.4683723449707,-67.50128173828125 +4120,12.448285,1381936,15,True,True,True,True,0,624374940,124.04480743408203,345.8480529785156,363.1095886230469,98.21336364746094,-85.46744537353516,179.36293029785156,63.21468734741211,-22.845048904418945,-10.410901069641113,76.24134063720703,35.49801254272461,-68.33277130126953 +4123,12.456277,1381937,15,True,True,True,True,0,624374948,125.8478775024414,344.76348876953125,362.7751770019531,102.84491729736328,-85.56120300292969,174.54405212402344,62.85651779174805,-22.89141082763672,-10.373025894165039,76.93074798583984,35.542789459228516,-69.07113647460938 +4125,12.46431,1381938,15,True,True,True,True,0,624374956,127.41783905029297,343.6907653808594,362.36151123046875,107.22881317138672,-85.62158966064453,170.00262451171875,62.531776428222656,-22.944583892822266,-10.348733901977539,77.55094909667969,35.60102081298828,-69.73173522949219 +4129,12.472358,1381939,15,True,True,True,True,0,624374964,128.8418426513672,342.6196594238281,361.90234375,111.1922836303711,-85.65381622314453,165.8975830078125,62.22752380371094,-23.00251579284668,-10.335468292236328,78.09407806396484,35.671791076660156,-70.30623626708984 +4131,12.480252,1381940,15,True,True,True,True,0,624374972,130.0984649658203,341.5738830566406,361.4136047363281,114.65035247802734,-85.65718841552734,162.3179168701172,61.94861602783203,-23.06381607055664,-10.331310272216797,78.56475067138672,35.75197982788086,-70.791259765625 +4133,12.488293,1381941,15,True,True,True,True,0,624374980,131.1835479736328,340.5936584472656,360.9158020019531,117.51100158691406,-85.64498901367188,159.35321044921875,61.699867248535156,-23.124462127685547,-10.336174011230469,78.95074462890625,35.83509826660156,-71.18113708496094 +4137,12.496271,1381942,15,True,True,True,True,0,624374988,132.11526489257812,339.6944580078125,360.44781494140625,119.75656127929688,-85.6323471069336,157.01791381835938,61.48033142089844,-23.1823787689209,-10.346473693847656,79.24906921386719,35.91868591308594,-71.48294067382812 +4139,12.504287,1381943,15,True,True,True,True,0,624374996,132.9951171875,338.8822937011719,360.02618408203125,121.39691162109375,-85.62540435791016,155.28216552734375,61.277835845947266,-23.232572555541992,-10.362120628356934,79.45616149902344,35.99454116821289,-71.69501495361328 +4141,12.512295,1381944,15,True,True,True,True,0,624375004,133.83262634277344,338.1937561035156,359.7105407714844,122.30352783203125,-85.61783599853516,154.2693328857422,61.09550476074219,-23.271240234375,-10.377250671386719,79.5709228515625,36.05375671386719,-71.80611419677734 +4143,12.520291,1381945,15,True,True,True,True,0,624375012,134.6541748046875,337.6537780761719,359.5055847167969,122.51000213623047,-85.61222839355469,153.93629455566406,60.93353271484375,-23.294658660888672,-10.392427444458008,79.5921401977539,36.086238861083984,-71.8167495727539 +4146,12.528287,1381946,15,True,True,True,True,0,624375020,135.48265075683594,337.2818298339844,359.44561767578125,122.2609634399414,-85.62187957763672,154.0414276123047,60.78950500488281,-23.299036026000977,-10.402854919433594,79.5434799194336,36.094181060791016,-71.76934051513672 +4149,12.536347,1381947,15,True,True,True,True,0,624375028,136.3490753173828,337.0858459472656,359.5403137207031,121.17422485351562,-85.64049530029297,154.9602508544922,60.6604118347168,-23.281761169433594,-10.412374496459961,79.38308715820312,36.08058166503906,-71.60807800292969 +4151,12.54429,1381948,15,True,True,True,True,0,624375036,137.3082733154297,337.1169128417969,359.8665466308594,118.98812866210938,-85.6368408203125,156.93832397460938,60.5457763671875,-23.235340118408203,-10.414129257202148,79.10812377929688,36.029850006103516,-71.30184173583984 +4155,12.552311,1381949,15,True,True,True,True,0,624375044,138.36656188964844,337.3583679199219,360.46124267578125,115.90125274658203,-85.6364517211914,159.763427734375,60.446014404296875,-23.16338539123535,-10.402337074279785,78.71235656738281,35.93022155761719,-70.88548278808594 +4157,12.560288,1381950,15,True,True,True,True,0,624375052,139.5797119140625,337.8329772949219,361.2862854003906,112.06852722167969,-85.64249420166016,163.2713623046875,60.356292724609375,-23.059350967407227,-10.383715629577637,78.19216918945312,35.77861022949219,-70.36888885498047 +4159,12.568313,1381951,15,True,True,True,True,0,624375060,140.97320556640625,338.5785827636719,362.4510498046875,107.31207275390625,-85.63872528076172,167.63674926757812,60.27695846557617,-22.918088912963867,-10.347146987915039,77.54158782958984,35.57386016845703,-69.74390411376953 +4163,12.576284,1381952,15,True,True,True,True,12,624375068,142.58596801757812,339.5946350097656,363.93798828125,101.76063537597656,-85.60619354248047,172.72059631347656,60.204280853271484,-22.737590789794922,-10.29414176940918,76.76364135742188,35.309669494628906,-69.00985717773438 +4165,12.584298,1381953,15,True,True,True,True,12,624375076,144.43028259277344,340.8885498046875,365.73876953125,95.61538696289062,-85.52423095703125,178.3198699951172,60.13789749145508,-22.51589012145996,-10.224837303161621,75.86434173583984,34.984535217285156,-68.16619873046875 +4167,12.592274,1381954,15,True,True,True,True,0,624375084,146.47926330566406,342.4411926269531,367.8445739746094,89.11468505859375,-85.37870788574219,-175.80018615722656,60.08014678955078,-22.256513595581055,-10.137887954711914,74.84568786621094,34.60066223144531,-67.2130126953125 +4170,12.600322,1381955,15,True,True,True,True,0,624375092,148.74473571777344,344.22906494140625,370.2304992675781,82.55409240722656,-85.17904663085938,-169.9334716796875,60.027008056640625,-21.960758209228516,-10.034200668334961,73.71505737304688,34.16362762451172,-66.17277526855469 +4173,12.608284,1381956,15,True,True,True,True,0,624375100,151.2110137939453,346.25128173828125,372.8800048828125,76.14720916748047,-84.92337036132812,-164.2871856689453,59.98005294799805,-21.628686904907227,-9.914199829101562,72.47760772705078,33.678184509277344,-65.04972839355469 +4175,12.61626,1381957,15,True,True,True,True,0,624375108,153.87155151367188,348.4990539550781,375.8046569824219,70.17119598388672,-84.58763885498047,-159.1402130126953,59.94023895263672,-21.261903762817383,-9.773606300354004,71.14222717285156,33.140411376953125,-63.824825286865234 +4179,12.624326,1381958,15,True,True,True,True,0,624375116,156.72027587890625,350.9496154785156,378.96307373046875,64.67875671386719,-84.2020034790039,-154.5360870361328,59.904788970947266,-20.861949920654297,-9.615623474121094,69.71330261230469,32.56216049194336,-62.523250579833984 +4181,12.632284,1381959,15,True,True,True,True,0,624375124,159.71783447265625,353.5682373046875,382.2313232421875,59.72076416015625,-83.79851531982422,-150.52281188964844,59.87655258178711,-20.43443489074707,-9.450610160827637,68.19942474365234,31.94040298461914,-61.16535568237305 +4183,12.640285,1381960,15,True,True,True,True,0,624375132,162.87515258789062,356.33538818359375,385.5758972167969,55.31468200683594,-83.38270568847656,-147.11746215820312,59.85215759277344,-19.980653762817383,-9.279845237731934,66.61038208007812,31.27599334716797,-59.74960708618164 +4185,12.648298,1381961,15,True,True,True,True,0,624375140,166.1754608154297,359.2493896484375,389.07568359375,51.38776397705078,-82.94474029541016,-144.2388153076172,59.832191467285156,-19.501548767089844,-9.092860221862793,64.94649505615234,30.57720375061035,-58.26451873779297 +4189,12.656324,1381962,15,True,True,True,True,0,624375148,169.5989990234375,362.3050842285156,392.7273864746094,48.02124786376953,-82.47901916503906,-141.96572875976562,59.818538665771484,-18.998722076416016,-8.887443542480469,63.21498107910156,29.8421630859375,-56.693519592285156 +4191,12.664293,1381963,15,True,True,True,True,0,624375156,173.1362762451172,365.4642333984375,396.4214172363281,44.99806594848633,-82.03485107421875,-140.07359313964844,59.8079719543457,-18.476116180419922,-8.673556327819824,61.4251708984375,29.076398849487305,-55.08243179321289 +4193,12.672274,1381964,15,True,True,True,True,0,624375164,176.7738494873047,368.741455078125,400.09808349609375,42.302982330322266,-81.61937713623047,-138.537841796875,59.80168914794922,-17.931798934936523,-8.457429885864258,59.58220672607422,28.28535270690918,-53.42765426635742 +4197,12.680276,1381965,15,True,True,True,True,0,624375172,180.49818420410156,372.1131591796875,403.845703125,39.883277893066406,-81.22427368164062,-137.30397033691406,59.79904556274414,-17.369930267333984,-8.226266860961914,57.68794250488281,27.468923568725586,-51.72475051879883 +4199,12.68827,1381966,15,True,True,True,True,0,624375180,184.28945922851562,375.5506286621094,407.55316162109375,37.64576721191406,-80.87947082519531,-136.27279663085938,59.79935073852539,-16.7945613861084,-7.990058898925781,55.752593994140625,26.626625061035156,-49.999942779541016 +4201,12.696306,1381967,15,True,True,True,True,0,624375188,188.14759826660156,379.0621337890625,411.18603515625,35.65293502807617,-80.57669830322266,-135.49803161621094,59.80105209350586,-16.203903198242188,-7.752835273742676,53.779605865478516,25.767465591430664,-48.23427963256836 +4204,12.704262,1381968,15,True,True,True,True,0,624375196,192.0356903076172,382.6184387207031,414.8074035644531,33.82727813720703,-80.31423950195312,-134.89358520507812,59.80502700805664,-15.60373592376709,-7.505049705505371,51.77424621582031,24.895050048828125,-46.438419342041016 +4207,12.712277,1381969,15,True,True,True,True,0,624375204,195.91441345214844,386.2422180175781,418.3200988769531,32.17952346801758,-80.11325073242188,-134.46499633789062,59.81732940673828,-14.993978500366211,-7.255697250366211,49.74714660644531,23.9991512298584,-44.62277603149414 +4209,12.720323,1381970,15,True,True,True,True,0,624375212,199.8300018310547,389.8947448730469,421.7528076171875,30.58125114440918,-79.97402954101562,-134.078369140625,59.827205657958984,-14.375741958618164,-7.001964092254639,47.70003890991211,23.093990325927734,-42.80534362792969 +4213,12.728297,1381971,15,True,True,True,True,0,624375220,203.6997833251953,393.5796813964844,425.0732116699219,29.154157638549805,-79.88682556152344,-133.8422393798828,59.842933654785156,-13.753583908081055,-6.745957851409912,45.6414680480957,22.180845260620117,-40.96695327758789 +4215,12.73633,1381972,15,True,True,True,True,0,624375228,207.54067993164062,397.3072814941406,428.240966796875,27.870527267456055,-79.85944366455078,-133.72592163085938,59.86212921142578,-13.125286102294922,-6.492568492889404,43.57445526123047,21.260671615600586,-39.11442184448242 +4217,12.744325,1381973,15,True,True,True,True,0,624375236,211.33477783203125,401.0332336425781,431.3249816894531,26.702556610107422,-79.87815856933594,-133.6925048828125,59.881526947021484,-12.496835708618164,-6.23345422744751,41.508056640625,20.343915939331055,-37.255653381347656 +4220,12.752294,1381974,15,True,True,True,True,0,624375244,215.05027770996094,404.79473876953125,434.1980895996094,25.534170150756836,-79.97383880615234,-133.61947631835938,59.9070930480957,-11.866397857666016,-5.982702255249023,39.4447135925293,19.42215347290039,-35.42018127441406 +4223,12.760261,1381975,15,True,True,True,True,0,624375252,218.71058654785156,408.53985595703125,436.9498291015625,24.49859619140625,-80.11614227294922,-133.640625,59.93109130859375,-11.238372802734375,-5.730126857757568,37.392852783203125,18.503347396850586,-33.58371353149414 +4225,12.768337,1381976,15,True,True,True,True,0,624375260,222.25167846679688,412.2729797363281,439.5760803222656,23.502151489257812,-80.30287170410156,-133.64646911621094,59.95893096923828,-10.615954399108887,-5.477423667907715,35.35686492919922,17.59833526611328,-31.76503562927246 +4227,12.776358,1381977,15,True,True,True,True,0,624375268,225.7161865234375,416.0008544921875,441.9900207519531,22.578781127929688,-80.5549545288086,-133.68177795410156,59.9876594543457,-9.996758460998535,-5.232880592346191,33.34233856201172,16.687471389770508,-29.9694881439209 +4230,12.784293,1381978,15,True,True,True,True,0,624375276,229.0684051513672,419.67071533203125,444.3096923828125,21.6545467376709,-80.8414077758789,-133.65855407714844,60.01493453979492,-9.388203620910645,-4.985452175140381,31.358219146728516,15.794910430908203,-28.20877456665039 +4233,12.792291,1381979,15,True,True,True,True,0,624375284,232.27145385742188,423.3253479003906,446.45947265625,20.79479217529297,-81.16161346435547,-133.63539123535156,60.04676055908203,-8.787995338439941,-4.745456695556641,29.406457901000977,14.923367500305176,-26.473495483398438 +4235,12.800327,1381980,15,True,True,True,True,0,624375292,235.38758850097656,426.9034118652344,448.4461669921875,20.02294921875,-81.52420806884766,-133.65164184570312,60.0741081237793,-8.199966430664062,-4.509968280792236,27.494951248168945,14.055309295654297,-24.771106719970703 +4239,12.808341,1381981,15,True,True,True,True,0,624375300,238.33016967773438,430.42608642578125,450.3211364746094,19.319124221801758,-81.8936767578125,-133.66439819335938,60.104339599609375,-7.626265525817871,-4.278657913208008,25.630605697631836,13.225600242614746,-23.10223960876465 +4241,12.816326,1381982,15,True,True,True,True,0,624375308,241.15676879882812,433.8782653808594,451.9712829589844,18.63854217529297,-82.30494689941406,-133.64541625976562,60.132816314697266,-7.0663371086120605,-4.061716556549072,23.817276000976562,12.40644645690918,-21.486703872680664 +4243,12.824246,1381983,15,True,True,True,True,0,624375316,243.82705688476562,437.223388671875,453.52728271484375,18.112773895263672,-82.72659301757812,-133.72315979003906,60.16086196899414,-6.526406764984131,-3.8456573486328125,22.06441307067871,11.60610294342041,-19.909517288208008 +4247,12.832274,1381984,15,True,True,True,True,0,624375324,246.32102966308594,440.4744873046875,454.98968505859375,17.42146110534668,-83.14739227294922,-133.5667724609375,60.190067291259766,-6.005974769592285,-3.6355509757995605,20.373485565185547,10.850674629211426,-18.40904426574707 +4249,12.840285,1381985,15,True,True,True,True,0,624375332,248.6848602294922,443.60101318359375,456.2638244628906,16.79395294189453,-83.59032440185547,-133.4239959716797,60.21621322631836,-5.506772041320801,-3.4387919902801514,18.750253677368164,10.110193252563477,-16.96802520751953 +4251,12.848325,1381986,15,True,True,True,True,0,624375340,250.87403869628906,446.6043395996094,457.45721435546875,16.266305923461914,-84.02154541015625,-133.32276916503906,60.242897033691406,-5.030858516693115,-3.2471859455108643,17.20528221130371,9.406123161315918,-15.585817337036133 +4254,12.856294,1381987,15,True,True,True,True,0,624375348,252.90687561035156,449.47454833984375,458.5881652832031,15.738029479980469,-84.43175506591797,-133.16607666015625,60.26810073852539,-4.57821798324585,-3.061250925064087,15.738860130310059,8.747891426086426,-14.271860122680664 +4257,12.864331,1381988,15,True,True,True,True,0,624375356,254.8082275390625,452.21612548828125,459.5511169433594,15.043581008911133,-84.8504867553711,-132.79739379882812,60.29118728637695,-4.147775650024414,-2.891414165496826,14.351035118103027,8.117122650146484,-13.04774284362793 +4259,12.872294,1381989,15,True,True,True,True,0,624375364,256.5745849609375,454.82220458984375,460.3800048828125,14.464632034301758,-85.25872802734375,-132.50144958496094,60.31245803833008,-3.740504503250122,-2.7356443405151367,13.045248031616211,7.520259380340576,-11.885969161987305 +4263,12.880325,1381990,15,True,True,True,True,0,624375372,258.19903564453125,457.29718017578125,461.169189453125,14.075912475585938,-85.63521575927734,-132.354736328125,60.33326721191406,-3.3559772968292236,-2.5855050086975098,11.817278861999512,6.966548919677734,-10.774357795715332 +4265,12.888283,1381991,15,True,True,True,True,0,624375380,259.69671630859375,459.628662109375,461.904541015625,13.596856117248535,-85.99529266357422,-132.08309936523438,60.352542877197266,-2.994971752166748,-2.4416961669921875,10.666150093078613,6.445277690887451,-9.738365173339844 +4267,12.896288,1381992,15,True,True,True,True,0,624375388,261.0810546875,461.8225402832031,462.55584716796875,13.052007675170898,-86.34468078613281,-131.71617126464844,60.3701171875,-2.65631365776062,-2.30767560005188,9.587376594543457,5.951510906219482,-8.771668434143066 +4269,12.904267,1381993,15,True,True,True,True,0,624375396,262.344970703125,463.8892517089844,463.12310791015625,12.45673942565918,-86.67851257324219,-131.2703857421875,60.38763427734375,-2.339406967163086,-2.184687614440918,8.579849243164062,5.489294052124023,-7.870203495025635 +4273,12.912289,1381994,15,True,True,True,True,0,624375404,263.5157470703125,465.8286437988281,463.6451110839844,11.848966598510742,-86.98613739013672,-130.7886962890625,60.402992248535156,-2.0424299240112305,-2.0697991847991943,7.638877868652344,5.064304351806641,-7.024659633636475 +4275,12.920276,1381995,15,True,True,True,True,0,624375412,264.5901794433594,467.6448974609375,464.1459045410156,11.162360191345215,-87.27218627929688,-130.2075653076172,60.417362213134766,-1.7651103734970093,-1.9593710899353027,6.763690948486328,4.668014049530029,-6.23867130279541 +4277,12.928287,1381996,15,True,True,True,True,0,624375420,265.5647277832031,469.33795166015625,464.6015625,10.409969329833984,-87.54196166992188,-129.5423583984375,60.43156814575195,-1.5079190731048584,-1.8559191226959229,5.95074462890625,4.2966156005859375,-5.508522033691406 +4280,12.936318,1381997,15,True,True,True,True,0,624375428,266.4543151855469,470.9240417480469,464.974609375,9.676228523254395,-87.79652404785156,-128.87979125976562,60.445411682128906,-1.2684215307235718,-1.764589786529541,5.197153091430664,3.952944278717041,-4.827888488769531 +4283,12.944278,1381998,15,True,True,True,True,0,624375436,267.28106689453125,472.38104248046875,465.3112487792969,8.573785781860352,-88.0329360961914,-127.83729553222656,60.45604705810547,-1.0474976301193237,-1.6803038120269775,4.502042293548584,3.6365954875946045,-4.209333419799805 +4285,12.952337,1381999,15,True,True,True,True,0,624375444,268.0218505859375,473.7431335449219,465.6114807128906,7.667938232421875,-88.24868774414062,-126.97834014892578,60.46772003173828,-0.8428318500518799,-1.603162169456482,3.8629493713378906,3.3464081287384033,-3.629457950592041 +4289,12.960323,1382000,15,True,True,True,True,0,624375452,268.7010498046875,474.9893493652344,465.892333984375,6.624913215637207,-88.4462661743164,-125.97392272949219,60.4774055480957,-0.6551903486251831,-1.5309449434280396,3.2770156860351562,3.0793333053588867,-3.097738742828369 +4291,12.968307,1382001,15,True,True,True,True,0,624375460,269.3203430175781,476.128173828125,466.1690673828125,4.841136932373047,-88.62351989746094,-124.22122192382812,60.485633850097656,-0.4834778904914856,-1.462394118309021,2.7410740852355957,2.8372082710266113,-2.6247785091400146 +4293,12.976291,1382002,15,True,True,True,True,0,624375468,269.8650817871094,477.16497802734375,466.4317932128906,2.738818883895874,-88.78392791748047,-122.14236450195312,60.49427032470703,-0.32810309529304504,-1.397946834564209,2.2536580562591553,2.6146528720855713,-2.1953773498535156 +4297,12.984284,1382003,15,True,True,True,True,0,624375476,270.3576354980469,478.10076904296875,466.6814270019531,0.7648493051528931,-88.92623138427734,-120.18719482421875,60.501487731933594,-0.18755637109279633,-1.3378050327301025,1.8125299215316772,2.4126882553100586,-1.7980189323425293 +4299,12.992341,1382004,15,True,True,True,True,0,624375484,270.79632568359375,478.95013427734375,466.89215087890625,-1.8090368509292603,-89.05278778076172,-117.62725067138672,60.508358001708984,-0.0605439767241478,-1.285258412361145,1.4144279956817627,2.232682704925537,-1.4441699981689453 +4301,13.000289,1382005,15,True,True,True,True,0,624375492,271.1876525878906,479.7132263183594,467.0897521972656,-4.972827434539795,-89.16110229492188,-114.47390747070312,60.51444625854492,0.05352430418133736,-1.237245798110962,1.0582711696624756,2.0733344554901123,-1.1308599710464478 +4304,13.008337,1382006,15,True,True,True,True,0,624375500,271.53924560546875,480.3961181640625,467.25506591796875,-8.048551559448242,-89.2579345703125,-111.4062728881836,60.519493103027344,0.15570396184921265,-1.1953281164169312,0.7426868081092834,1.930242896080017,-0.846489667892456 +4307,13.016294,1382007,15,True,True,True,True,0,624375508,271.84503173828125,481.0046081542969,467.3879699707031,-11.707520484924316,-89.34168243408203,-107.75267791748047,60.524444580078125,0.24621066451072693,-1.1597543954849243,0.4639242887496948,1.8044384717941284,-0.5974479913711548 +4309,13.024295,1382008,15,True,True,True,True,0,624375516,272.1258239746094,481.5384826660156,467.4984436035156,-15.705729484558105,-89.41321563720703,-103.75945281982422,60.527503967285156,0.3264233469963074,-1.1291184425354004,0.22092124819755554,1.6940094232559204,-0.3797355592250824 +4311,13.032326,1382009,15,True,True,True,True,0,624375524,272.35748291015625,482.00262451171875,467.5992431640625,-20.199857711791992,-89.46867370605469,-99.26788330078125,60.531166076660156,0.39540567994117737,-1.1023304462432861,0.010631970129907131,1.600974440574646,-0.19409923255443573 +4314,13.040281,1382010,15,True,True,True,True,0,624375532,272.55859375,482.4019470214844,467.6817321777344,-24.989646911621094,-89.51541900634766,-94.48003387451172,60.53407669067383,0.4548884630203247,-1.0793298482894897,-0.17123958468437195,1.5185521841049194,-0.03350314870476723 +4317,13.048332,1382011,15,True,True,True,True,0,624375540,272.7277526855469,482.72918701171875,467.730224609375,-30.29819679260254,-89.55118560791016,-89.17326354980469,60.5360221862793,0.5037935376167297,-1.0624645948410034,-0.3257932662963867,1.4499976634979248,0.09931468963623047 +4319,13.056287,1382012,15,True,True,True,True,0,624375548,272.8631591796875,483.0118408203125,467.7633972167969,-35.083499908447266,-89.57807922363281,-84.38821411132812,60.538612365722656,0.5452703237533569,-1.0495078563690186,-0.45425114035606384,1.3948160409927368,0.21167220175266266 +4323,13.064328,1382013,15,True,True,True,True,0,624375556,272.97064208984375,483.23797607421875,467.7799072265625,-39.30857849121094,-89.60460662841797,-80.16316223144531,60.540733337402344,0.5783824920654297,-1.039437174797058,-0.5597624182701111,1.3454352617263794,0.30919474363327026 +4325,13.072288,1382014,15,True,True,True,True,0,624375564,273.0642395019531,483.42034912109375,467.796875,-44.01029586791992,-89.6144027709961,-75.46198272705078,60.541805267333984,0.6055248975753784,-1.0313913822174072,-0.6450719833374023,1.308801293373108,0.37706393003463745 +4327,13.080287,1382015,15,True,True,True,True,0,624375572,273.130126953125,483.5687255859375,467.8089599609375,-48.00895309448242,-89.61836242675781,-71.46275329589844,60.5435905456543,0.6269300580024719,-1.0253233909606934,-0.7125676274299622,1.280745029449463,0.42880383133888245 +4330,13.088276,1382016,15,True,True,True,True,0,624375580,273.1842956542969,483.6767883300781,467.8211669921875,-50.3907356262207,-89.62371826171875,-69.08110046386719,60.54432678222656,0.6429385542869568,-1.0205403566360474,-0.7624372839927673,1.2605493068695068,0.4724292457103729 +4333,13.096285,1382017,15,True,True,True,True,0,624375588,273.22454833984375,483.7579040527344,467.8163146972656,-52.09377670288086,-89.62992095947266,-67.37808227539062,60.54490661621094,0.6548271775245667,-1.0186700820922852,-0.7975199222564697,1.2461544275283813,0.5054013729095459 +4335,13.104329,1382018,15,True,True,True,True,0,624375596,273.2519836425781,483.81982421875,467.8091735839844,-53.285743713378906,-89.63597106933594,-66.18578338623047,60.54563903808594,0.6636571288108826,-1.0176879167556763,-0.8218390345573425,1.235442876815796,0.5298940539360046 +4339,13.112315,1382019,7,True,True,True,False,0,624375604,273.2713623046875,483.8575439453125,467.7983703613281,-54.24752426147461,-89.6385498046875,-65.22402954101562,60.54584503173828,0.6691559553146362,-1.0178695917129517,-0.8360996246337891,1.2291887998580933,0.5426381826400757 +4341,13.120293,1382020,7,True,True,True,False,0,624375612,273.2827453613281,483.8821716308594,467.7841491699219,-55.140079498291016,-89.63822937011719,-64.33135986328125,60.54608917236328,0.6726118326187134,-1.018925666809082,-0.8444472551345825,1.2258353233337402,0.547467052936554 +4343,13.128295,1382021,7,True,True,True,False,0,624375620,273.2890319824219,483.89752197265625,467.77392578125,-55.529624938964844,-89.63884735107422,-63.94169235229492,60.54631423950195,0.6746944785118103,-1.0198405981063843,-0.8498149514198303,1.2243767976760864,0.5519473552703857 +4347,13.136303,1382022,7,True,True,True,False,0,624375628,273.29388427734375,483.90911865234375,467.76220703125,-55.95112991333008,-89.63809967041016,-63.52009582519531,60.546478271484375,0.6762437224388123,-1.0210784673690796,-0.8538101315498352,1.2238413095474243,0.5538225173950195 +4349,13.144286,1382023,7,True,True,True,False,0,624375636,273.29541015625,483.91571044921875,467.755126953125,-56.26089096069336,-89.63665008544922,-63.21018600463867,60.54668426513672,0.6770381927490234,-1.0218790769577026,-0.8563576936721802,1.2238247394561768,0.5540714263916016 +4351,13.152343,1382024,7,True,True,True,False,0,624375644,273.29608154296875,483.9195861816406,467.7505187988281,-56.51509475708008,-89.63513946533203,-62.95588302612305,60.54682922363281,0.6774864792823792,-1.0224037170410156,-0.8587266206741333,1.2238413095474243,0.5542871356010437 +4353,13.160329,1382025,7,True,True,True,False,0,624375652,273.2955627441406,483.9212646484375,467.748779296875,-56.634071350097656,-89.63420104980469,-62.83679962158203,60.54696273803711,0.6776283383369446,-1.0226190090179443,-0.8599768877029419,1.223940134048462,0.5543369054794312 +4356,13.168292,1382026,7,True,True,True,False,0,624375660,273.2947082519531,483.92169189453125,467.7478942871094,-56.7469367980957,-89.63310241699219,-62.72386169433594,60.54706573486328,0.6776170134544373,-1.0227400064468384,-0.8613775968551636,1.224063754081726,0.5544199347496033 +4359,13.176323,1382027,7,True,True,True,False,0,624375668,273.2939453125,483.9220275878906,467.74688720703125,-56.868709564208984,-89.63186645507812,-62.60203170776367,60.54715347290039,0.6775999665260315,-1.0228813886642456,-0.862806499004364,1.2242285013198853,0.5543867349624634 +4361,13.184294,1382028,7,True,True,True,False,0,624375676,273.2934265136719,483.9221496582031,467.7460021972656,-56.93334197998047,-89.63115692138672,-62.53736114501953,60.547210693359375,0.6775715947151184,-1.023009181022644,-0.8636242747306824,1.224393367767334,0.5543867349624634 +4364,13.192301,1382029,7,True,True,True,False,0,624375684,273.2931213378906,483.9215393066406,467.7452697753906,-56.964271545410156,-89.63050079345703,-62.50645065307617,60.547210693359375,0.6774694323539734,-1.0231437683105469,-0.864338755607605,1.224722981452942,0.5544365048408508 +4367,13.200303,1382030,7,True,True,True,False,0,624375692,273.2929382324219,483.9219970703125,467.7429504394531,-57.047420501708984,-89.62992858886719,-62.42327117919922,60.5472526550293,0.6774921417236328,-1.023432970046997,-0.8650907874107361,1.224871277809143,0.5544199347496033 +4369,13.208325,1382031,7,True,True,True,False,0,624375700,273.292236328125,483.9216003417969,467.7415466308594,-57.10725784301758,-89.62937927246094,-62.36341094970703,60.547298431396484,0.6773899793624878,-1.0236213207244873,-0.8657487630844116,1.2250360250473022,0.5544033050537109 +4373,13.216327,1382032,7,True,True,True,False,0,624375708,273.291748046875,483.92095947265625,467.739990234375,-57.159385681152344,-89.6288070678711,-62.311283111572266,60.54731369018555,0.6772822141647339,-1.0238432884216309,-0.8663598299026489,1.2252832651138306,0.5543535351753235 +4375,13.224295,1382033,7,True,True,True,False,0,624375716,273.2911376953125,483.9210205078125,467.738525390625,-57.20407485961914,-89.6283187866211,-62.266544342041016,60.5473747253418,0.6772310733795166,-1.0240451097488403,-0.8669427037239075,1.2255057096481323,0.554370105266571 +4377,13.232326,1382034,7,True,True,True,False,0,624375724,273.2904968261719,483.92108154296875,467.7369689941406,-57.230796813964844,-89.62789154052734,-62.23977279663086,60.54743576049805,0.6771799921989441,-1.0242739915847778,-0.8673939108848572,1.2258187532424927,0.554370105266571 +4381,13.240294,1382035,7,True,True,True,False,0,624375732,273.2897644042969,483.920654296875,467.7353210449219,-57.2639274597168,-89.62757110595703,-62.20660400390625,60.547481536865234,0.6770722270011902,-1.0244959592819214,-0.8677135109901428,1.226033091545105,0.554303765296936 +4383,13.24828,1382036,7,True,True,True,False,0,624375740,273.289306640625,483.9200134277344,467.7329406738281,-57.29737091064453,-89.62741088867188,-62.17316436767578,60.547489166259766,0.6769587397575378,-1.0248053073883057,-0.8679673075675964,1.2262471914291382,0.554303765296936 +4385,13.256333,1382037,7,True,True,True,False,0,624375748,273.2886657714844,483.91925048828125,467.7313537597656,-57.32023239135742,-89.62725830078125,-62.15028762817383,60.54751205444336,0.6768168210983276,-1.0250139236450195,-0.868174135684967,1.226412057876587,0.554303765296936 +4388,13.26433,1382038,7,True,True,True,False,0,624375756,273.2882080078125,483.9189453125,467.7294006347656,-57.3497200012207,-89.62708282470703,-62.120784759521484,60.54753875732422,0.6767261028289795,-1.025269627571106,-0.8683433532714844,1.2266016006469727,0.5542207360267639 +4391,13.272287,1382039,7,True,True,True,False,0,624375764,273.2882080078125,483.91864013671875,467.7270812988281,-57.37662124633789,-89.62696075439453,-62.09389114379883,60.547523498535156,0.6766749620437622,-1.0255721807479858,-0.8684937953948975,1.2268240451812744,0.5541709661483765 +4393,13.280302,1382040,7,True,True,True,False,0,624375772,273.2875671386719,483.91790771484375,467.7261962890625,-57.383689880371094,-89.62673950195312,-62.086814880371094,60.54754638671875,0.6765444874763489,-1.0257067680358887,-0.8686535358428955,1.2270382642745972,0.554121196269989 +4395,13.288299,1382041,7,True,True,True,False,0,624375780,273.28717041015625,483.9175720214844,467.7239685058594,-57.40468215942383,-89.62666320800781,-62.06581497192383,60.54756546020508,0.6764650344848633,-1.0260028839111328,-0.8687757849693298,1.2272608280181885,0.5541046261787415 +4399,13.296339,1382042,7,True,True,True,False,0,624375788,273.28680419921875,483.91693115234375,467.7221984863281,-57.43193817138672,-89.62655639648438,-62.0385627746582,60.54756546020508,0.6763458251953125,-1.0262248516082764,-0.8688979744911194,1.227392554283142,0.5540382266044617 +4401,13.304292,1382043,7,True,True,True,False,0,624375796,273.285888671875,483.9161071777344,467.7207946777344,-57.450538635253906,-89.6264419555664,-62.019920349121094,60.547607421875,0.6761869788169861,-1.0264132022857666,-0.8690108060836792,1.2275409698486328,0.5539884567260742 +4403,13.312292,1382044,7,True,True,True,False,0,624375804,273.28570556640625,483.9154968261719,467.7191162109375,-57.46620178222656,-89.62627410888672,-62.00428009033203,60.54759216308594,0.6760904788970947,-1.026641845703125,-0.86910480260849,1.2277716398239136,0.5538888573646545 +4407,13.320287,1382045,7,True,True,True,False,0,624375812,273.28521728515625,483.91461181640625,467.71710205078125,-57.479736328125,-89.6262435913086,-61.99074172973633,60.54759216308594,0.6759486198425293,-1.0269043445587158,-0.8691800236701965,1.2279776334762573,0.5538888573646545 +4409,13.328284,1382046,7,True,True,True,False,0,624375820,273.28460693359375,483.9135437011719,467.71514892578125,-57.49925231933594,-89.62614440917969,-61.97123718261719,60.54759216308594,0.6757670044898987,-1.0271666049957275,-0.8692457675933838,1.228183627128601,0.553805947303772 +4411,13.336292,1382047,7,True,True,True,False,0,624375828,273.28424072265625,483.9128723144531,467.7130432128906,-57.53185272216797,-89.6261215209961,-61.938636779785156,60.54759216308594,0.6756478548049927,-1.027422308921814,-0.8693491816520691,1.2282742261886597,0.5537727475166321 +4414,13.344302,1382048,7,True,True,True,False,0,624375836,273.28411865234375,483.9119567871094,467.71136474609375,-57.552528381347656,-89.6259994506836,-61.91800308227539,60.54755783081055,0.6755229830741882,-1.0276442766189575,-0.8694244027137756,1.2284473180770874,0.5536731481552124 +4417,13.352298,1382049,7,True,True,True,False,0,624375844,273.2834777832031,483.91107177734375,467.71051025390625,-57.559898376464844,-89.62583923339844,-61.91062545776367,60.54757308959961,0.6753754615783691,-1.0277653932571411,-0.8694996237754822,1.2286120653152466,0.5535901784896851 +4419,13.360317,1382050,7,True,True,True,False,0,624375852,273.28277587890625,483.91021728515625,467.7095947265625,-57.56065368652344,-89.6257095336914,-61.909847259521484,60.54759216308594,0.67522794008255,-1.0279066562652588,-0.8695278167724609,1.2288180589675903,0.5535072088241577 +4423,13.368327,1382051,7,True,True,True,False,0,624375860,273.2824401855469,483.9100341796875,467.70758056640625,-57.589813232421875,-89.62564849853516,-61.88067626953125,60.547611236572266,0.6751711964607239,-1.0281555652618408,-0.8696029782295227,1.2289416790008545,0.5534242391586304 +4425,13.376293,1382052,7,True,True,True,False,0,624375868,273.281982421875,483.9092102050781,467.7065734863281,-57.604434967041016,-89.62548065185547,-61.866050720214844,60.547611236572266,0.6750349402427673,-1.0282968282699585,-0.869659423828125,1.2290899753570557,0.5532914996147156 +4427,13.384337,1382053,7,True,True,True,False,0,624375876,273.2812194824219,483.90826416015625,467.70574951171875,-57.61231994628906,-89.62535095214844,-61.85814666748047,60.54763412475586,0.6748704314231873,-1.028417944908142,-0.8697158098220825,1.2292383909225464,0.5532085299491882 +4431,13.392264,1382054,7,True,True,True,False,0,624375884,273.2809143066406,483.90716552734375,467.704833984375,-57.60966110229492,-89.62533569335938,-61.86083984375,60.547607421875,0.6747228503227234,-1.0285524129867554,-0.8697346448898315,1.2293949127197266,0.5532251000404358 +4433,13.400334,1382055,7,True,True,True,False,0,624375892,273.280517578125,483.9062194824219,467.7043762207031,-57.59976577758789,-89.62531280517578,-61.870750427246094,60.54759216308594,0.6745866537094116,-1.0286264419555664,-0.8697346448898315,1.2295348644256592,0.5532417297363281 +4435,13.408296,1382056,7,True,True,True,False,0,624375900,273.28033447265625,483.9056091308594,467.7030029296875,-57.61248779296875,-89.6252670288086,-61.85804748535156,60.547576904296875,0.6744958758354187,-1.0288081169128418,-0.8697910308837891,1.229675054550171,0.5532085299491882 +4437,13.416324,1382057,7,True,True,True,False,0,624375908,273.27972412109375,483.9045104980469,467.7017517089844,-57.63063049316406,-89.6252212524414,-61.83991241455078,60.547576904296875,0.6743199825286865,-1.0289627313613892,-0.8698192238807678,1.2297492027282715,0.5531421899795532 +4440,13.424325,1382058,7,True,True,True,False,0,624375916,273.2792053222656,483.90386962890625,467.70184326171875,-57.62474822998047,-89.62517547607422,-61.84577178955078,60.54759216308594,0.6742121577262878,-1.0289627313613892,-0.8697722554206848,1.229806900024414,0.5530757904052734 +4443,13.432305,1382059,7,True,True,True,False,0,624375924,273.279052734375,483.9030456542969,467.7008361816406,-57.637290954589844,-89.62508392333984,-61.83325958251953,60.54756546020508,0.6741042733192444,-1.029097318649292,-0.8698286414146423,1.2299221754074097,0.5530094504356384 +4445,13.440323,1382060,7,True,True,True,False,0,624375932,273.2786560058594,483.90289306640625,467.69976806640625,-57.64518356323242,-89.6250991821289,-61.82533645629883,60.54759216308594,0.6740532517433167,-1.0292319059371948,-0.8698098063468933,1.2300045490264893,0.5529762506484985 +4449,13.448342,1382061,7,True,True,True,False,0,624375940,273.2784118652344,483.9024658203125,467.6995849609375,-57.64421463012695,-89.62503051757812,-61.82631301879883,60.54759216308594,0.6739851236343384,-1.0292655229568481,-0.8698286414146423,1.2300788164138794,0.5529430508613586 +4451,13.456267,1382062,7,True,True,True,False,0,624375948,273.278076171875,483.9017028808594,467.6994934082031,-57.64672088623047,-89.6249008178711,-61.823814392089844,60.547584533691406,0.6738773584365845,-1.0292924642562866,-0.8698474168777466,1.230161190032959,0.552843451499939 +4453,13.464283,1382063,7,True,True,True,False,0,624375956,273.2771911621094,483.90032958984375,467.7004089355469,-57.6280403137207,-89.62483215332031,-61.84248733520508,60.54759216308594,0.6736730337142944,-1.0291916131973267,-0.8698098063468933,1.230202317237854,0.5528102517127991 +4457,13.472281,1382064,7,True,True,True,False,0,624375964,273.2764892578125,483.8988037109375,467.70147705078125,-57.6053581237793,-89.62469482421875,-61.86518859863281,60.547576904296875,0.6734573841094971,-1.0290906429290771,-0.8698474168777466,1.2303012609481812,0.5528102517127991 +4459,13.480288,1382065,7,True,True,True,False,0,624375972,273.276611328125,483.8982849121094,467.7010803222656,-57.6073112487793,-89.62461853027344,-61.8632698059082,60.54754638671875,0.6734006404876709,-1.029151201248169,-0.8698192238807678,1.2303918600082397,0.5527107119560242 +4461,13.488301,1382066,7,True,True,True,False,0,624375980,273.2762451171875,483.89776611328125,467.7009582519531,-57.60565948486328,-89.6246109008789,-61.86491012573242,60.547550201416016,0.6733212471008301,-1.0291714668273926,-0.8698192238807678,1.2304248809814453,0.5527107119560242 +4464,13.496292,1382067,7,True,True,True,False,0,624375988,273.27508544921875,483.8958740234375,467.702880859375,-57.57059097290039,-89.62444305419922,-61.89997100830078,60.54755783081055,0.6730431914329529,-1.0289627313613892,-0.8698192238807678,1.230499029159546,0.5526940822601318 +4467,13.504297,1382068,7,True,True,True,False,0,624375996,273.27471923828125,483.89508056640625,467.7032165527344,-57.56571578979492,-89.6243667602539,-61.90485382080078,60.547550201416016,0.6729296445846558,-1.0289359092712402,-0.8698004484176636,1.2305402755737305,0.5526277422904968 +4469,13.512329,1382069,7,True,True,True,False,0,624376004,273.27447509765625,483.8947448730469,467.7027282714844,-57.575504302978516,-89.62432098388672,-61.89506530761719,60.54755783081055,0.6728785634040833,-1.028996467590332,-0.8698098063468933,1.230573058128357,0.552561342716217 +4473,13.520324,1382070,7,True,True,True,False,0,624376012,273.2735900878906,483.89337158203125,467.7041320800781,-57.554439544677734,-89.62418365478516,-61.916114807128906,60.54756546020508,0.672674298286438,-1.0288485288619995,-0.8698004484176636,1.2306143045425415,0.5525115728378296 +4475,13.528324,1382071,7,True,True,True,False,0,624376020,273.2732238769531,483.8925476074219,467.704345703125,-57.556514739990234,-89.62406158447266,-61.91405487060547,60.54755783081055,0.672555148601532,-1.0288282632827759,-0.8698098063468933,1.230647325515747,0.5524120330810547 +4477,13.536298,1382072,7,True,True,True,False,0,624376028,273.273193359375,483.89251708984375,467.7043151855469,-57.552669525146484,-89.62406921386719,-61.91789245605469,60.54755783081055,0.6725494265556335,-1.0288350582122803,-0.8697910308837891,1.230672001838684,0.5524120330810547 +4479,13.544348,1382073,7,True,True,True,False,0,624376036,273.2724914550781,483.8909912109375,467.70556640625,-57.53007507324219,-89.62397766113281,-61.94049835205078,60.54754638671875,0.6723394989967346,-1.0287004709243774,-0.8697910308837891,1.2307132482528687,0.5524120330810547 +4483,13.552309,1382074,7,True,True,True,False,0,624376044,273.2723083496094,483.8899841308594,467.7062072753906,-57.53425598144531,-89.623779296875,-61.93635940551758,60.54751205444336,0.6722202897071838,-1.0286332368850708,-0.8697910308837891,1.2307296991348267,0.5522295236587524 +4485,13.560326,1382075,7,True,True,True,False,0,624376052,273.2715148925781,483.8885803222656,467.7078857421875,-57.498023986816406,-89.62371826171875,-61.97259521484375,60.54751205444336,0.6720216870307922,-1.0284515619277954,-0.8697440028190613,1.2307791709899902,0.5522626638412476 +4487,13.568329,1382076,7,True,True,True,False,0,624376060,273.2712707519531,483.8882751464844,467.70794677734375,-57.50230407714844,-89.62358856201172,-61.96830749511719,60.547515869140625,0.6719706058502197,-1.0284515619277954,-0.8697910308837891,1.2308286428451538,0.5521796941757202 +4491,13.576309,1382077,7,True,True,True,False,0,624376068,273.2709655761719,483.8877258300781,467.708740234375,-57.48716354370117,-89.62349700927734,-61.98344802856445,60.547515869140625,0.6718854904174805,-1.02837073802948,-0.8697440028190613,1.2308779954910278,0.5521133542060852 +4494,13.584323,1382078,7,True,True,True,False,0,624376076,273.27032470703125,483.8868408203125,467.7093505859375,-57.47456741333008,-89.62348937988281,-61.996028900146484,60.54753112792969,0.6717549562454224,-1.0282968282699585,-0.8697722554206848,1.2308779954910278,0.5521796941757202 +4496,13.592303,1382079,7,True,True,True,False,0,624376084,273.2700500488281,483.8858337402344,467.7098693847656,-57.46422576904297,-89.62339782714844,-62.00638961791992,60.54750442504883,0.6716301441192627,-1.028249740600586,-0.8697440028190613,1.2309356927871704,0.5521133542060852 +4500,13.60036,1382080,7,True,True,True,False,0,624376092,273.2699279785156,483.8856506347656,467.7098388671875,-57.47734069824219,-89.62321472167969,-61.99327850341797,60.54750442504883,0.6716017127037048,-1.0282565355300903,-0.8697628378868103,1.2309685945510864,0.5519307851791382 +4502,13.608287,1382081,7,True,True,True,False,0,624376100,273.26959228515625,483.8850402832031,467.7102355957031,-57.47087860107422,-89.6231460571289,-61.99973678588867,60.54750442504883,0.6715109348297119,-1.0282227993011475,-0.8697346448898315,1.231009840965271,0.5518643856048584 +4504,13.616321,1382082,7,True,True,True,False,0,624376108,273.2691955566406,483.8840637207031,467.7109680175781,-57.45874786376953,-89.623046875,-62.01189041137695,60.547489166259766,0.6713804602622986,-1.0281420946121216,-0.8697440028190613,1.231050968170166,0.5518311858177185 +4508,13.624327,1382083,7,True,True,True,False,0,624376116,273.2686767578125,483.8834533691406,467.7116394042969,-57.45295715332031,-89.62296295166016,-62.01766586303711,60.54750442504883,0.6712839603424072,-1.028074860572815,-0.869725227355957,1.2310593128204346,0.5517649054527283 +4510,13.63231,1382084,7,True,True,True,False,0,624376124,273.2687072753906,483.8827819824219,467.7118835449219,-57.44992446899414,-89.62291717529297,-62.02073287963867,60.54747009277344,0.6712102293968201,-1.0280479192733765,-0.869706392288208,1.2310757637023926,0.551715075969696 +4512,13.640314,1382085,7,True,True,True,False,0,624376132,273.26837158203125,483.8821716308594,467.712646484375,-57.443538665771484,-89.6227035522461,-62.02711868286133,60.54747009277344,0.6711193919181824,-1.0279672145843506,-0.869725227355957,1.2311416864395142,0.5515823364257812 +4515,13.648291,1382086,7,True,True,True,False,0,624376140,273.26776123046875,483.88165283203125,467.7127990722656,-57.451297760009766,-89.62260437011719,-62.01932907104492,60.5474967956543,0.6710285544395447,-1.0279537439346313,-0.869725227355957,1.2311416864395142,0.551466166973114 +4518,13.656293,1382087,7,True,True,True,False,0,624376148,273.26751708984375,483.8809509277344,467.7135925292969,-57.44172668457031,-89.62244415283203,-62.02891159057617,60.547481536865234,0.6709378361701965,-1.0278730392456055,-0.869725227355957,1.2311993837356567,0.5513666272163391 +4520,13.664288,1382088,7,True,True,True,False,0,624376156,273.26708984375,483.8802185058594,467.714599609375,-57.43358612060547,-89.62232971191406,-62.037052154541016,60.547481536865234,0.6708356738090515,-1.0277585983276367,-0.869725227355957,1.2311911582946777,0.5513002276420593 +4522,13.672319,1382089,7,True,True,True,False,0,624376164,273.2669982910156,483.8796081542969,467.7151794433594,-57.4290657043457,-89.62225341796875,-62.04159927368164,60.547462463378906,0.6707618832588196,-1.02769136428833,-0.869725227355957,1.2311911582946777,0.5512504577636719 +4525,13.68029,1382090,7,True,True,True,False,0,624376172,273.266845703125,483.87933349609375,467.71527099609375,-57.42633056640625,-89.62220764160156,-62.04432678222656,60.547462463378906,0.6707221865653992,-1.0276846885681152,-0.8696876168251038,1.2312240600585938,0.5511840581893921 +4528,13.688293,1382091,7,True,True,True,False,0,624376180,273.26611328125,483.87847900390625,467.71661376953125,-57.40891647338867,-89.62203216552734,-62.06172180175781,60.547481536865234,0.6705859303474426,-1.0275434255599976,-0.869706392288208,1.2312735319137573,0.5511176586151123 +4530,13.69633,1382092,7,True,True,True,False,0,624376188,273.2662658691406,483.8780517578125,467.71661376953125,-57.412322998046875,-89.6219482421875,-62.058353424072266,60.547447204589844,0.6705462336540222,-1.0275501012802124,-0.869706392288208,1.231306552886963,0.5510347485542297 +4534,13.704321,1382093,7,True,True,True,False,0,624376196,273.2658996582031,483.8775329589844,467.7171936035156,-57.40373992919922,-89.62191009521484,-62.066932678222656,60.547454833984375,0.6704667806625366,-1.0274828672409058,-0.869706392288208,1.231306552886963,0.5510347485542297 +4536,13.712289,1382094,7,True,True,True,False,0,624376204,273.2661437988281,483.8770446777344,467.71771240234375,-57.40203857421875,-89.62178039550781,-62.06868362426758,60.54740905761719,0.6704327464103699,-1.0274289846420288,-0.869725227355957,1.2313311100006104,0.5509517788887024 +4538,13.720318,1382095,7,True,True,True,False,0,624376212,273.2658386230469,483.8766174316406,467.7179260253906,-57.40105438232422,-89.6217269897461,-62.06966018676758,60.54741287231445,0.6703646779060364,-1.0274088382720947,-0.8696876168251038,1.231347680091858,0.550868809223175 +4542,13.728321,1382096,7,True,True,True,False,0,624376220,273.2652893066406,483.87603759765625,467.7184753417969,-57.400123596191406,-89.62158966064453,-62.070560455322266,60.54743576049805,0.670268177986145,-1.027348279953003,-0.8696970343589783,1.231364130973816,0.5507692098617554 +4544,13.736277,1382097,7,True,True,True,False,0,624376228,273.2648620605469,483.8755798339844,467.718994140625,-57.40251541137695,-89.62149810791016,-62.06815719604492,60.547447204589844,0.6701943874359131,-1.0272810459136963,-0.8696970343589783,1.2313311100006104,0.550686240196228 +4546,13.74433,1382098,7,True,True,True,False,0,624376236,273.26470947265625,483.8752746582031,467.7193298339844,-57.39326095581055,-89.62147521972656,-62.07741928100586,60.547447204589844,0.6701546907424927,-1.0272473096847534,-0.8696876168251038,1.231364130973816,0.550686240196228 +4549,13.752326,1382099,7,True,True,True,False,0,624376244,273.26513671875,483.8750915527344,467.7193298339844,-57.393951416015625,-89.62144470214844,-62.07677459716797,60.547401428222656,0.6701603531837463,-1.0272541046142578,-0.869678258895874,1.2313807010650635,0.5506530404090881 +4552,13.760296,1382100,7,True,True,True,False,0,624376252,273.264892578125,483.8745422363281,467.72015380859375,-57.38460159301758,-89.62132263183594,-62.08613204956055,60.547393798828125,0.6700866222381592,-1.0271666049957275,-0.8696688413619995,1.2314136028289795,0.5505700707435608 +4554,13.768326,1382101,7,True,True,True,False,0,624376260,273.2640075683594,483.87322998046875,467.7218933105469,-57.3596076965332,-89.62118530273438,-62.11110305786133,60.54740905761719,0.6698936223983765,-1.0269715785980225,-0.869659423828125,1.2314300537109375,0.5505368709564209 +4558,13.776339,1382102,7,True,True,True,False,0,624376268,273.26373291015625,483.8723449707031,467.72283935546875,-57.35216522216797,-89.62107849121094,-62.118568420410156,60.547386169433594,0.6697857975959778,-1.026863932609558,-0.869640588760376,1.2314218282699585,0.5504539012908936 +4560,13.784317,1382103,7,True,True,True,False,0,624376276,273.2635192871094,483.8719787597656,467.72296142578125,-57.356056213378906,-89.62101745605469,-62.11468505859375,60.547386169433594,0.6697291135787964,-1.0268504619598389,-0.869659423828125,1.2314218282699585,0.5504041910171509 +4562,13.79229,1382104,7,True,True,True,False,0,624376284,273.26336669921875,483.8714294433594,467.72344970703125,-57.35163879394531,-89.6209487915039,-62.119117736816406,60.5473747253418,0.6696609854698181,-1.0267966985702515,-0.8696688413619995,1.2314300537109375,0.550370991230011 +4564,13.800318,1382105,7,True,True,True,False,0,624376292,273.2633361816406,483.87127685546875,467.7234191894531,-57.34675979614258,-89.6209487915039,-62.124000549316406,60.547367095947266,0.6696439981460571,-1.0268033742904663,-0.8696500062942505,1.231463074684143,0.550370991230011 +4568,13.808317,1382106,7,True,True,True,False,0,624376300,273.2628479003906,483.87091064453125,467.7239990234375,-57.348243713378906,-89.62084197998047,-62.12248229980469,60.547393798828125,0.6695758700370789,-1.0267361402511597,-0.8696312308311462,1.2314465045928955,0.5502548217773438 +4570,13.816298,1382107,7,True,True,True,False,0,624376308,273.26220703125,483.8695373535156,467.72589111328125,-57.32699966430664,-89.62068939208984,-62.14373779296875,60.54738235473633,0.6693885922431946,-1.0265207290649414,-0.8696312308311462,1.2314300537109375,0.5502050518989563 +4572,13.824327,1382108,7,True,True,True,False,0,624376316,273.2624816894531,483.8698425292969,467.7254638671875,-57.3421516418457,-89.62063598632812,-62.128597259521484,60.5473747253418,0.6694340109825134,-1.0265679359436035,-0.8696312308311462,1.2314218282699585,0.5501054525375366 +4575,13.832326,1382109,7,True,True,True,False,0,624376324,273.2626953125,483.86968994140625,467.72576904296875,-57.343284606933594,-89.62055969238281,-62.12749099731445,60.54734802246094,0.6694340109825134,-1.0265343189239502,-0.8696312308311462,1.2314218282699585,0.5500391125679016 +4578,13.840295,1382110,7,True,True,True,False,0,624376332,273.2623596191406,483.86907958984375,467.726318359375,-57.33713150024414,-89.62043762207031,-62.133644104003906,60.54734802246094,0.6693431735038757,-1.0264737606048584,-0.8696312308311462,1.231463074684143,0.5499560832977295 +4580,13.848323,1382111,7,True,True,True,False,0,624376340,273.2620849609375,483.86859130859375,467.7269592285156,-57.329341888427734,-89.62034606933594,-62.14142990112305,60.54734802246094,0.6692808270454407,-1.0264065265655518,-0.869612455368042,1.23148775100708,0.5498897433280945 +4584,13.856328,1382112,7,True,True,True,False,0,624376348,273.261962890625,483.8684997558594,467.72711181640625,-57.33140563964844,-89.62028503417969,-62.13935470581055,60.5473518371582,0.6692637801170349,-1.0263862609863281,-0.8696029782295227,1.23148775100708,0.5498233437538147 +4586,13.864324,1382113,7,True,True,True,False,0,624376356,273.26190185546875,483.8682861328125,467.72772216796875,-57.32400894165039,-89.6202392578125,-62.146759033203125,60.54734802246094,0.6692354083061218,-1.0263257026672363,-0.8696029782295227,1.231495976448059,0.5498067736625671 +4588,13.872324,1382114,7,True,True,True,False,0,624376364,273.2620544433594,483.86785888671875,467.7276306152344,-57.32862091064453,-89.6202163696289,-62.142189025878906,60.54731369018555,0.6692013740539551,-1.0263323783874512,-0.8696029782295227,1.23148775100708,0.5497735738754272 +4592,13.880315,1382115,7,True,True,True,False,0,624376372,273.2620849609375,483.8677062988281,467.7277526855469,-57.33396911621094,-89.62012481689453,-62.13685607910156,60.547298431396484,0.6691843271255493,-1.026318907737732,-0.8696029782295227,1.231495976448059,0.5496740341186523 +4594,13.888285,1382116,7,True,True,True,False,0,624376380,273.2617492675781,483.8680419921875,467.7277526855469,-57.33771896362305,-89.62010192871094,-62.133056640625,60.54734802246094,0.6692013740539551,-1.026318907737732,-0.8696029782295227,1.23148775100708,0.5496408343315125 +4596,13.896332,1382117,7,True,True,True,False,0,624376388,273.2618713378906,483.8675537109375,467.72845458984375,-57.3355712890625,-89.6199722290039,-62.13523483276367,60.54731369018555,0.6691559553146362,-1.0262383222579956,-0.8696029782295227,1.23148775100708,0.5495412945747375 +4599,13.904324,1382118,7,True,True,True,False,0,624376396,273.2615051269531,483.8669128417969,467.7292175292969,-57.321754455566406,-89.619873046875,-62.1490478515625,60.54731369018555,0.669070839881897,-1.0261576175689697,-0.8696029782295227,1.2315372228622437,0.5495080947875977 +4602,13.912269,1382119,7,True,True,True,False,0,624376404,273.2616271972656,483.8667297363281,467.7292785644531,-57.32208251953125,-89.61985778808594,-62.148746490478516,60.54729080200195,0.6690594553947449,-1.0261508226394653,-0.8695842027664185,1.2315372228622437,0.5494748950004578 +4604,13.920278,1382120,7,True,True,True,False,0,624376412,273.2615661621094,483.8663635253906,467.7296142578125,-57.318477630615234,-89.61981964111328,-62.152366638183594,60.547279357910156,0.669014036655426,-1.0261104106903076,-0.8696029782295227,1.2315372228622437,0.5494748950004578 +4606,13.928325,1382121,7,True,True,True,False,0,624376420,273.26129150390625,483.8661804199219,467.7297058105469,-57.32450866699219,-89.61973571777344,-62.14632034301758,60.54729080200195,0.6689800024032593,-1.0261037349700928,-0.8695842027664185,1.2315454483032227,0.5493587255477905 +4610,13.93635,1382122,7,True,True,True,False,0,624376428,273.261474609375,483.8660583496094,467.7298889160156,-57.321651458740234,-89.61974334716797,-62.14919662475586,60.547271728515625,0.6689743399620056,-1.0260835886001587,-0.8695842027664185,1.2315372228622437,0.5493753552436829 +4612,13.944326,1382123,7,True,True,True,False,0,624376436,273.2611389160156,483.865478515625,467.73040771484375,-57.323455810546875,-89.61968231201172,-62.14739227294922,60.547271728515625,0.66889488697052,-1.026016354560852,-0.8695842027664185,1.23148775100708,0.5493255257606506 +4614,13.952344,1382124,7,True,True,True,False,0,624376444,273.26116943359375,483.8651123046875,467.7310791015625,-57.31435775756836,-89.61962890625,-62.1565055847168,60.5472526550293,0.6688608527183533,-1.0259422063827515,-0.8695842027664185,1.231495976448059,0.5493089556694031 +4618,13.960296,1382125,7,True,True,True,False,0,624376452,273.2611389160156,483.86517333984375,467.73077392578125,-57.319393157958984,-89.61959075927734,-62.15147018432617,60.54725646972656,0.6688608527183533,-1.0259827375411987,-0.8695842027664185,1.231528878211975,0.5492591857910156 +4620,13.96833,1382126,7,True,True,True,False,0,624376460,273.26116943359375,483.8655090332031,467.7303161621094,-57.32477569580078,-89.61961364746094,-62.14607620239258,60.547271728515625,0.6689005494117737,-1.0260365009307861,-0.8695465922355652,1.2315372228622437,0.5492259860038757 +4622,13.976324,1382127,7,True,True,True,False,0,624376468,273.26116943359375,483.8651428222656,467.7308654785156,-57.320457458496094,-89.61953735351562,-62.15040588378906,60.5472526550293,0.6688608527183533,-1.0259759426116943,-0.8695842027664185,1.2315454483032227,0.5492093563079834 +4625,13.984299,1382128,7,True,True,True,False,0,624376476,273.26092529296875,483.86456298828125,467.7312927246094,-57.322330474853516,-89.61949157714844,-62.14854049682617,60.547245025634766,0.6687813997268677,-1.0259153842926025,-0.8695748448371887,1.231495976448059,0.549159586429596 +4628,13.992327,1382129,7,True,True,True,False,0,624376484,273.260986328125,483.8642578125,467.7315673828125,-57.31619644165039,-89.61943054199219,-62.154693603515625,60.54722213745117,0.6687530279159546,-1.025895118713379,-0.8695748448371887,1.2315454483032227,0.549126386642456 +4630,14.000327,1382130,7,True,True,True,False,0,624376492,273.2609558105469,483.8640441894531,467.7315979003906,-57.313941955566406,-89.61945343017578,-62.15696334838867,60.547218322753906,0.6687246561050415,-1.025888442993164,-0.8695278167724609,1.2315372228622437,0.5491098165512085 +4634,14.00834,1382131,7,True,True,True,False,0,624376500,273.2608642578125,483.8643493652344,467.7317199707031,-57.31626892089844,-89.6194076538086,-62.154605865478516,60.547237396240234,0.6687530279159546,-1.0258749723434448,-0.8695278167724609,1.2315372228622437,0.549060046672821 +4636,14.016331,1382132,7,True,True,True,False,0,624376508,273.2607421875,483.8639831542969,467.7323913574219,-57.30280303955078,-89.61939239501953,-62.16808319091797,60.5472297668457,0.6687076687812805,-1.0258010625839233,-0.8694902062416077,1.2315454483032227,0.549060046672821 +4638,14.024328,1382133,7,True,True,True,False,0,624376516,273.2604675292969,483.8636474609375,467.7327575683594,-57.29954528808594,-89.61933898925781,-62.17133331298828,60.547237396240234,0.6686622500419617,-1.0257606506347656,-0.8695090413093567,1.2315536737442017,0.5490434765815735 +4642,14.032331,1382134,7,True,True,True,False,0,624376524,273.260986328125,483.8637390136719,467.732421875,-57.3040771484375,-89.61936950683594,-62.166839599609375,60.54719543457031,0.6687019467353821,-1.025794267654419,-0.8694996237754822,1.2315454483032227,0.5490434765815735 +4644,14.040322,1382135,7,True,True,True,False,0,624376532,273.2609558105469,483.86370849609375,467.73260498046875,-57.30391311645508,-89.61934661865234,-62.1670036315918,60.54719543457031,0.6686906218528748,-1.0257741212844849,-0.8694996237754822,1.2315372228622437,0.5490269064903259 +4646,14.048312,1382136,7,True,True,True,False,0,624376540,273.2606201171875,483.86346435546875,467.73291015625,-57.29599380493164,-89.61936950683594,-62.174903869628906,60.547218322753906,0.6686452031135559,-1.025740385055542,-0.8694714307785034,1.2315372228622437,0.5490434765815735 +4648,14.05633,1382137,7,True,True,True,False,0,624376548,273.26092529296875,483.8634948730469,467.7326965332031,-57.29856872558594,-89.61937713623047,-62.172359466552734,60.54718780517578,0.668673574924469,-1.0257606506347656,-0.8694714307785034,1.2315372228622437,0.5490434765815735 +4651,14.064326,1382138,7,True,True,True,False,0,624376556,273.2608337402344,483.8634948730469,467.7326354980469,-57.303287506103516,-89.61933898925781,-62.167633056640625,60.54719543457031,0.6686622500419617,-1.0257741212844849,-0.8694714307785034,1.2315454483032227,0.548993706703186 +4654,14.072334,1382139,7,True,True,True,False,0,624376564,273.2611999511719,483.86285400390625,467.73321533203125,-57.294281005859375,-89.6192855834961,-62.176700592041016,60.54713439941406,0.6686168313026428,-1.025713562965393,-0.8694807887077332,1.2315618991851807,0.548993706703186 +4656,14.080324,1382140,7,True,True,True,False,0,624376572,273.2610778808594,483.862548828125,467.73358154296875,-57.294002532958984,-89.61921691894531,-62.17698669433594,60.54712677001953,0.6685771346092224,-1.0256664752960205,-0.8694714307785034,1.2315536737442017,0.5489273071289062 +4659,14.088298,1382141,7,True,True,True,False,0,624376580,273.26068115234375,483.8626708984375,467.73370361328125,-57.29121780395508,-89.6192398071289,-62.179725646972656,60.54716873168945,0.6685657501220703,-1.0256530046463013,-0.8694620132446289,1.2315454483032227,0.5489439368247986 +4662,14.096291,1382142,7,True,True,True,False,0,624376588,273.26080322265625,483.86260986328125,467.7336730957031,-57.289554595947266,-89.61927795410156,-62.181400299072266,60.54715347290039,0.6685657501220703,-1.0256530046463013,-0.8694620132446289,1.2315372228622437,0.5489770770072937 +4664,14.104327,1382143,7,True,True,True,False,0,624376596,273.2608947753906,483.86260986328125,467.7336120605469,-57.28643035888672,-89.6192855834961,-62.18453598022461,60.547149658203125,0.668571412563324,-1.0256664752960205,-0.8694244027137756,1.2315618991851807,0.5489605069160461 +4668,14.112311,1382144,7,True,True,True,False,0,624376604,273.2611389160156,483.8622741699219,467.73358154296875,-57.291656494140625,-89.61927795410156,-62.179344177246094,60.5471076965332,0.6685487627983093,-1.0256664752960205,-0.8694337606430054,1.2315372228622437,0.5489439368247986 +4670,14.12033,1382145,7,True,True,True,False,0,624376612,273.2609558105469,483.862060546875,467.73394775390625,-57.2861213684082,-89.6192398071289,-62.184879302978516,60.547115325927734,0.6685146689414978,-1.0256261825561523,-0.8694244027137756,1.2315454483032227,0.5489273071289062 +4672,14.128323,1382146,7,True,True,True,False,0,624376620,273.2610168457031,483.862548828125,467.7336730957031,-57.29054641723633,-89.6192626953125,-62.1804313659668,60.54713439941406,0.668571412563324,-1.0256530046463013,-0.8694244027137756,1.2315372228622437,0.5489273071289062 +4675,14.136323,1382147,7,True,True,True,False,0,624376628,273.2610168457031,483.8622741699219,467.7339172363281,-57.28868865966797,-89.61923217773438,-62.18230438232422,60.547119140625,0.6685431003570557,-1.0256261825561523,-0.8694432377815247,1.2315372228622437,0.5489273071289062 +4678,14.144323,1382148,7,True,True,True,False,0,624376636,273.2609558105469,483.8619079589844,467.7341613769531,-57.284908294677734,-89.61921691894531,-62.186100006103516,60.5471076965332,0.6685033440589905,-1.0255992412567139,-0.8694056272506714,1.2315372228622437,0.5488941073417664 +4680,14.152305,1382149,7,True,True,True,False,0,624376644,273.2611083984375,483.8616943359375,467.7344665527344,-57.27913284301758,-89.61918640136719,-62.191898345947266,60.547080993652344,0.6684919595718384,-1.025565505027771,-0.8693962097167969,1.2315536737442017,0.5488775372505188 +4684,14.160334,1382150,7,True,True,True,False,0,624376652,273.26116943359375,483.8617858886719,467.7342529296875,-57.28093338012695,-89.61920928955078,-62.190093994140625,60.547080993652344,0.6685033440589905,-1.025592565536499,-0.8694056272506714,1.2315618991851807,0.5488941073417664 +4686,14.168331,1382151,7,True,True,True,False,0,624376660,273.2608642578125,483.8617248535156,467.7347412109375,-57.280426025390625,-89.61914825439453,-62.190582275390625,60.5471076965332,0.6684749722480774,-1.0255318880081177,-0.8694056272506714,1.2315372228622437,0.5488443374633789 +4688,14.176323,1382152,7,True,True,True,False,0,624376668,273.2608642578125,483.8616027832031,467.73486328125,-57.2752571105957,-89.61917114257812,-62.19575119018555,60.54710006713867,0.6684636473655701,-1.0255184173583984,-0.8693962097167969,1.2315372228622437,0.5488775372505188 +4690,14.184278,1382153,7,True,True,True,False,0,624376676,273.2608337402344,483.861572265625,467.73480224609375,-57.27745819091797,-89.61915588378906,-62.19355010986328,60.54710006713867,0.668452262878418,-1.0255252122879028,-0.8693962097167969,1.2315372228622437,0.5488609075546265 +4694,14.192326,1382154,7,True,True,True,False,0,624376684,273.26104736328125,483.8612976074219,467.735107421875,-57.26909255981445,-89.61917114257812,-62.20195388793945,60.54706573486328,0.6684408783912659,-1.0254915952682495,-0.8693585991859436,1.2315454483032227,0.5488609075546265 +4696,14.200341,1382155,7,True,True,True,False,0,624376692,273.2610778808594,483.8611755371094,467.73516845703125,-57.270362854003906,-89.61914825439453,-62.20069122314453,60.54705810546875,0.6684296131134033,-1.0254848003387451,-0.8693680167198181,1.2315454483032227,0.5488443374633789 +4698,14.208328,1382156,7,True,True,True,False,0,624376700,273.2607116699219,483.861083984375,467.7353515625,-57.26780700683594,-89.61913299560547,-62.203216552734375,60.547088623046875,0.6684011816978455,-1.025464653968811,-0.8693680167198181,1.2315454483032227,0.5488443374633789 +4702,14.216326,1382157,7,True,True,True,False,0,624376708,273.2608337402344,483.8610534667969,467.7354431152344,-57.26517105102539,-89.61910247802734,-62.205867767333984,60.54707336425781,0.6684068441390991,-1.0254579782485962,-0.8693680167198181,1.2315702438354492,0.5488277673721313 +4704,14.224305,1382158,7,True,True,True,False,0,624376716,273.26092529296875,483.8609313964844,467.73516845703125,-57.276039123535156,-89.61909484863281,-62.19501495361328,60.54705810546875,0.6683955192565918,-1.0254848003387451,-0.8693585991859436,1.2315372228622437,0.5487779378890991 +4706,14.232329,1382159,7,True,True,True,False,0,624376724,273.2609558105469,483.860595703125,467.73583984375,-57.2615852355957,-89.61909484863281,-62.20948791503906,60.54703903198242,0.6683671474456787,-1.0254108905792236,-0.8693585991859436,1.2315454483032227,0.5488277673721313 +4709,14.240314,1382160,7,True,True,True,False,0,624376732,273.260986328125,483.8605041503906,467.7356872558594,-57.26530838012695,-89.61909484863281,-62.205772399902344,60.54703140258789,0.6683557629585266,-1.0254310369491577,-0.8693398237228394,1.2315454483032227,0.5487945675849915 +4712,14.248319,1382161,7,True,True,True,False,0,624376740,273.2606201171875,483.86053466796875,467.73602294921875,-57.25807571411133,-89.61909484863281,-62.21297073364258,60.54706573486328,0.6683387756347656,-1.025390625,-0.8693304061889648,1.2315454483032227,0.548811137676239 +4714,14.256323,1382162,7,True,True,True,False,0,624376748,273.26080322265625,483.8605651855469,467.7359619140625,-57.260108947753906,-89.61908721923828,-62.2109489440918,60.547054290771484,0.6683557629585266,-1.0253974199295044,-0.8693115711212158,1.2315454483032227,0.5487779378890991 +4718,14.264323,1382163,7,True,True,True,False,0,624376756,273.26080322265625,483.8603210449219,467.7359313964844,-57.26311492919922,-89.6190414428711,-62.20795822143555,60.54703903198242,0.6683273911476135,-1.0254040956497192,-0.8693304061889648,1.2315618991851807,0.548744797706604 +4720,14.272323,1382164,7,True,True,True,False,0,624376764,273.26080322265625,483.8598937988281,467.7364501953125,-57.25945281982422,-89.6189956665039,-62.211639404296875,60.547019958496094,0.668276309967041,-1.0253435373306274,-0.8693209886550903,1.2315454483032227,0.5487115979194641 +4722,14.28033,1382165,7,True,True,True,False,0,624376772,273.2607116699219,483.8597106933594,467.73675537109375,-57.25178527832031,-89.61898040771484,-62.21930694580078,60.547019958496094,0.6682536602020264,-1.0253099203109741,-0.8693209886550903,1.2315618991851807,0.5487281680107117 +4725,14.288325,1382166,7,True,True,True,False,0,624376780,273.2608337402344,483.8600769042969,467.73602294921875,-57.26062774658203,-89.6190414428711,-62.21045684814453,60.54702377319336,0.6682990789413452,-1.025390625,-0.8693209886550903,1.2315618991851807,0.548744797706604 +4728,14.296321,1382167,7,True,True,True,False,0,624376788,273.2607727050781,483.8599548339844,467.7362976074219,-57.25722885131836,-89.61902618408203,-62.21385955810547,60.54702377319336,0.6682876944541931,-1.0253636837005615,-0.8693209886550903,1.2315618991851807,0.548744797706604 +4730,14.304275,1382168,7,True,True,True,False,0,624376796,273.2608947753906,483.86004638671875,467.7362365722656,-57.25717544555664,-89.61901092529297,-62.21391296386719,60.547019958496094,0.6682990789413452,-1.025370478630066,-0.8693022131919861,1.2315784692764282,0.5487115979194641 diff --git a/analysis/2026042802-1/2026042802-1_j519_trigger_frames.csv b/analysis/2026042802-1/2026042802-1_j519_trigger_frames.csv new file mode 100644 index 0000000..ca82f0e --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_j519_trigger_frames.csv @@ -0,0 +1,18 @@ +frame_number,time_relative_s,sequence,last_data,write_io_type,write_io_index,write_io_mask,write_io_value,io_addrs,j1_deg,j2_deg,j3_deg,j4_deg,j5_deg,j6_deg,ext1_deg,ext2_deg,ext3_deg +1955,5.888271,1381124,0,2,1,10,10,"[2, 4]",45.8632698059082,2.9724788665771484,-13.609341621398926,-2.1851141452789307,7.582361221313477,5.701517581939697,0.0,0.0,0.0 +2151,6.480252,1381198,0,2,1,14,14,"[2, 3, 4]",56.34336471557617,11.66723918914795,-6.600923538208008,-68.1976089477539,6.058437347412109,71.5392837524414,0.0,0.0,0.0 +2223,6.704225,1381226,0,2,1,14,14,"[2, 3, 4]",53.50831985473633,7.412222862243652,-9.629739761352539,-32.30571365356445,12.488306045532227,30.826189041137695,0.0,0.0,0.0 +2336,7.048266,1381269,0,2,1,10,10,"[2, 4]",44.433006286621094,-2.1156294345855713,-18.39777374267578,4.8371992111206055,33.99372482299805,-20.26228904724121,0.0,0.0,0.0 +2477,7.464289,1381321,0,2,1,10,10,"[2, 4]",65.84967041015625,5.695003032684326,-14.563636779785156,-28.69342613220215,27.678611755371094,46.437156677246094,0.0,0.0,0.0 +2665,8.024277,1381391,0,2,1,12,12,"[3, 4]",60.847530364990234,24.47777557373047,-5.617043972015381,34.8044319152832,5.786255359649658,-31.2253475189209,0.0,0.0,0.0 +2811,8.472317,1381447,0,2,1,12,12,"[3, 4]",70.8250503540039,10.95259952545166,-13.136046409606934,-34.68459701538086,14.022420883178711,38.90604019165039,0.0,0.0,0.0 +2908,8.760361,1381483,0,2,1,10,10,"[2, 4]",69.40516662597656,-20.066781997680664,-8.152935981750488,-63.93235397338867,6.775912284851074,70.37761688232422,0.0,0.0,0.0 +3112,9.384332,1381561,0,2,1,10,10,"[2, 4]",73.90592956542969,-8.016124725341797,-9.002426147460938,-131.3327178955078,15.526206970214844,142.41668701171875,0.0,0.0,0.0 +3170,9.560358,1381583,0,2,1,10,10,"[2, 4]",76.72688293457031,-16.518726348876953,-7.15147066116333,-127.53003692626953,18.53788948059082,138.7897491455078,0.0,0.0,0.0 +3350,10.104322,1381651,0,2,1,10,10,"[2, 4]",83.71662139892578,-13.186573028564453,-13.738808631896973,-72.70317840576172,26.66758918762207,91.98423767089844,0.0,0.0,0.0 +3470,10.472333,1381697,0,2,1,10,10,"[2, 4]",59.78955841064453,-2.7094168663024902,-8.933828353881836,-108.24857330322266,21.177492141723633,114.14110565185547,0.0,0.0,0.0 +3627,10.944371,1381756,0,2,1,12,12,"[3, 4]",86.41329193115234,4.266173362731934,-19.15987777709961,-52.24028015136719,29.853723526000977,55.86907196044922,0.0,0.0,0.0 +3710,11.192339,1381787,0,2,1,10,10,"[2, 4]",110.7523193359375,0.8751887679100037,-34.756534576416016,-48.14332580566406,45.0865592956543,87.97444152832031,0.0,0.0,0.0 +3796,11.456361,1381820,0,2,1,10,10,"[2, 4]",111.79177856445312,-6.225440502166748,-31.958988189697266,-53.68573760986328,44.76150894165039,89.33454895019531,0.0,0.0,0.0 +3870,11.680362,1381848,0,2,1,10,10,"[2, 4]",117.87474060058594,-19.607177734375,-30.329103469848633,-56.600250244140625,49.08655548095703,90.32424926757812,0.0,0.0,0.0 +4138,12.496365,1381950,0,2,1,12,12,"[3, 4]",59.93589401245117,-21.102140426635742,-9.684239387512207,70.64311981201172,32.941307067871094,-63.34855651855469,0.0,0.0,0.0 diff --git a/analysis/2026042802-1/2026042802-1_status_feedback_summary.json b/analysis/2026042802-1/2026042802-1_status_feedback_summary.json new file mode 100644 index 0000000..25fe800 --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_status_feedback_summary.json @@ -0,0 +1,37 @@ +{ + "pcap_path": "D:\\Dev\\Codes\\rvbust-code\\FlyingShotPkg_3.15_VDA\\Rvbust\\uttc-20260428\\2026042802-1.pcap", + "command_count": 1788, + "trigger_count": 17, + "command_minus_paired_status_sequence_counter": { + "8": 17 + }, + "paired_status_average_max_error_deg": 6.469219290127501, + "paired_status_max_error_deg": 16.123934474475888, + "paired_status_max_error_axis_counter": { + "J1": 2, + "J6": 9, + "J2": 4, + "J4": 2 + }, + "best_status_average_max_error_deg": 0.14953970473465206, + "best_status_max_error_deg": 0.5696919293327056, + "best_status_max_error_axis_counter": { + "J6": 10, + "J1": 3, + "J5": 1, + "J2": 2, + "J4": 1 + }, + "best_status_delta_from_paired_cycles_counter": { + "10": 8, + "9": 9 + }, + "best_status_delta_from_trigger_sequence_counter": { + "2": 8, + "1": 9 + }, + "best_status_time_after_trigger_ms_min": 71.83799999999962, + "best_status_time_after_trigger_ms_max": 79.96399999999859, + "best_status_time_after_trigger_ms_avg": 75.6613529411763, + "search_window_cycles": 20 +} \ No newline at end of file diff --git a/analysis/2026042802-1/2026042802-1_trigger_manual_compare.csv b/analysis/2026042802-1/2026042802-1_trigger_manual_compare.csv new file mode 100644 index 0000000..bcc9197 --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_trigger_manual_compare.csv @@ -0,0 +1,18 @@ +trigger_no,waypoint_index,trigger_command_sequence,trigger_command_frame,trigger_command_time_relative_s,trigger_current_status_sequence,trigger_current_status_frame,trigger_current_status_time_relative_s,command_leads_status_cycles,trigger_current_status_max_error_axis,trigger_current_status_max_error_deg,trigger_current_status_rms_error_deg,best_status_sequence,best_status_frame,best_status_time_relative_s,best_status_delay_from_current_status_cycles,best_status_delay_from_trigger_command_cycles,best_status_delay_from_trigger_command_ms,best_status_max_error_axis,best_status_max_error_deg,best_status_rms_error_deg +1,1,1381124,1955,5.888271,1381116,1954,5.888166,8,J1,6.588241610414407,3.8225443630072706,1381126,1981,5.968124,10,2,79.85300000000083,J6,0.20758988641565335,0.12239685259209541 +2,2,1381198,2151,6.480252,1381190,2150,6.480138,8,J1,2.33531536749085,1.3827543088729335,1381199,2172,6.552164,9,1,71.9120000000002,J1,0.08213390997131853,0.04616054814443023 +3,3,1381226,2223,6.704225,1381218,2222,6.704166,8,J6,16.123934474475888,8.852376616648156,1381228,2248,6.784154,10,2,79.92899999999992,J6,0.5696919293327056,0.3239642953120798 +4,4,1381269,2336,7.048266,1381261,2335,7.048148,8,J6,4.901882807288516,3.2440556235708993,1381271,2362,7.128175,10,2,79.90899999999979,J6,0.09470430997406254,0.06580474324148583 +5,5,1381321,2477,7.464289,1381313,2476,7.464178,8,J6,9.21943820204428,4.621321121191458,1381323,2502,7.544122,10,2,79.83299999999983,J5,0.056622412878706285,0.03357478517689402 +6,6,1381391,2665,8.024277,1381383,2664,8.024156,8,J6,6.1464605154096255,3.0478405356855083,1381392,2687,8.096181,9,1,71.90399999999997,J6,0.17697427883735983,0.08563125303054821 +7,7,1381447,2811,8.472317,1381439,2810,8.472208,8,J6,13.15171783052326,7.78202379046887,1381448,2834,8.54419,9,1,71.87300000000008,J6,0.30658691011310424,0.18210669390591905 +8,8,1381483,2908,8.760361,1381475,2907,8.760245,8,J6,7.436279407197546,5.127041386597523,1381484,2929,8.832199,9,1,71.83799999999962,J2,0.06074767130727565,0.02750705896232285 +9,9,1381561,3112,9.384332,1381553,3111,9.384204,8,J6,5.585605293050492,3.277206017832995,1381562,3134,9.456205,9,1,71.87300000000008,J4,0.15225994654176134,0.089150370994325 +10,10,1381583,3170,9.560358,1381575,3169,9.56025,8,J2,4.165077087081428,2.128983861449047,1381585,3196,9.640214,10,2,79.85599999999948,J6,0.0734395372110157,0.043285948566531826 +11,11,1381651,3350,10.104322,1381643,3349,10.104215,8,J2,1.8309533977318093,1.169340011162825,1381652,3373,10.17622,9,1,71.8980000000009,J6,0.07766831894582538,0.04246508921571141 +12,12,1381697,3470,10.472333,1381689,3469,10.472191,8,J4,4.822353276363401,2.8611252897135864,1381698,3493,10.544283,9,1,71.94999999999929,J1,0.021077280093038553,0.014935517911187784 +13,13,1381756,3627,10.944371,1381748,3626,10.944251,8,J4,8.152507963730145,5.168659196117862,1381757,3651,11.016226,9,1,71.85499999999934,J1,0.14554304541260876,0.0699602863618738 +14,14,1381787,3710,11.192339,1381779,3709,11.19225,8,J6,10.40441920064211,5.679810575214033,1381789,3735,11.272261,10,2,79.92199999999983,J6,0.11945317484617135,0.05835950688086092 +15,15,1381820,3796,11.456361,1381812,3795,11.456253,8,J2,2.6161077284098373,1.5911754635072362,1381822,3821,11.536287,10,2,79.92600000000039,J2,0.14980253942005772,0.08716897465096986 +16,16,1381848,3870,11.680362,1381840,3869,11.680263,8,J2,4.682473812334727,2.2067399623329567,1381850,3895,11.760326,10,2,79.96399999999859,J6,0.17295643126782068,0.10419421346699378 +17,18,1381950,4138,12.496365,1381942,4137,12.496271,8,J6,1.8139599579791934,1.1536991630766555,1381951,4159,12.568313,9,1,71.94799999999901,J6,0.07492339792059965,0.03834497543010648 diff --git a/analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_compare.csv b/analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_compare.csv new file mode 100644 index 0000000..633a28a --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_compare.csv @@ -0,0 +1,18 @@ +trigger_no,waypoint_index,best_sample_order,best_frame_number,best_sequence,best_time_relative_s,teach_j1_deg,teach_j2_deg,teach_j3_deg,teach_j4_deg,teach_j5_deg,teach_j6_deg,offset_6_frame_number,offset_6_sequence,offset_6_time_relative_s,offset_6_max_error_axis,offset_6_max_error_deg,offset_6_rms_error_deg,offset_6_delta_from_best_ms,offset_6_j1_actual_deg,offset_6_diff_j1_deg,offset_6_j2_actual_deg,offset_6_diff_j2_deg,offset_6_j3_actual_deg,offset_6_diff_j3_deg,offset_6_j4_actual_deg,offset_6_diff_j4_deg,offset_6_j5_actual_deg,offset_6_diff_j5_deg,offset_6_j6_actual_deg,offset_6_diff_j6_deg,offset_7_frame_number,offset_7_sequence,offset_7_time_relative_s,offset_7_max_error_axis,offset_7_max_error_deg,offset_7_rms_error_deg,offset_7_delta_from_best_ms,offset_7_j1_actual_deg,offset_7_diff_j1_deg,offset_7_j2_actual_deg,offset_7_diff_j2_deg,offset_7_j3_actual_deg,offset_7_diff_j3_deg,offset_7_j4_actual_deg,offset_7_diff_j4_deg,offset_7_j5_actual_deg,offset_7_diff_j5_deg,offset_7_j6_actual_deg,offset_7_diff_j6_deg,offset_8_frame_number,offset_8_sequence,offset_8_time_relative_s,offset_8_max_error_axis,offset_8_max_error_deg,offset_8_rms_error_deg,offset_8_delta_from_best_ms,offset_8_j1_actual_deg,offset_8_diff_j1_deg,offset_8_j2_actual_deg,offset_8_diff_j2_deg,offset_8_j3_actual_deg,offset_8_diff_j3_deg,offset_8_j4_actual_deg,offset_8_diff_j4_deg,offset_8_j5_actual_deg,offset_8_diff_j5_deg,offset_8_j6_actual_deg,offset_8_diff_j6_deg,best_of_6_7_8_offset +1,1,730,1941,1381118,5.840207,48.886810269468405,2.1989850886957285,-11.021017368511105,0.4102097980549552,6.248381265333557,2.294990756284542,1955,1381124,5.888271,J6,3.4065268256551553,2.468438977201345,48.06399999999922,45.8632698059082,-3.0235404635602023,2.9724788665771484,0.7734937778814199,-13.609341621398926,-2.588324252887821,-2.1851141452789307,-2.5953239433338857,7.582361221313477,1.3339799559799195,5.701517581939697,3.4065268256551553,1958,1381125,5.896184,J6,4.180931828249149,2.9355776062802286,55.9769999999995,45.449710845947266,-3.43709942352114,3.117816925048828,0.9188318363530996,-13.963269233703613,-2.9422518651925085,-2.845515012741089,-3.255724810796044,7.768581867218018,1.5202006018844605,6.475922584533691,4.180931828249149,1961,1381126,5.904272,J6,5.0045521476156045,3.418446646752538,64.06499999999937,45.06731414794922,-3.8194961215191867,3.2660434246063232,1.0670583359105947,-14.290565490722656,-3.2695481222115514,-3.562589168548584,-3.972798966603539,7.9421586990356445,1.6937774337020874,7.2995429039001465,5.0045521476156045,6 +2,2,804,2135,1381192,6.432175,55.34775509527405,11.807039833001637,-7.0090952672806885,-71.01433145543973,6.012065051914967,74.24953191606797,2151,1381198,6.480252,J4,2.8167225076858244,1.6562461612538633,48.07700000000015,56.34336471557617,0.995609620302119,11.66723918914795,-0.13980064385368784,-6.600923538208008,0.40817172907268073,-68.1976089477539,2.8167225076858244,6.058437347412109,0.04637229549714217,71.5392837524414,-2.7102481636265594,2153,1381199,6.488246,J4,3.526759739131137,2.060948429836345,56.071000000000204,56.451515197753906,1.1037601024798533,11.618118286132812,-0.18892154686882456,-6.580313682556152,0.4287815847245362,-67.4875717163086,3.526759739131137,6.09804105758667,0.08597600567170272,70.84339141845703,-3.4061404976109344,2155,1381200,6.496271,J4,4.301478977412387,2.5033932349312007,64.09600000000015,56.54535675048828,1.1976016552142283,11.561552047729492,-0.24548778527214488,-6.571277618408203,0.4378176488724854,-66.71285247802734,4.301478977412387,6.147788047790527,0.13572299587556014,70.078857421875,-4.170674494192966,6 +3,3,832,2207,1381220,6.656264,55.109808014787404,8.759497374223619,-8.518216825284897,-41.11725046606459,10.108488114686867,41.956933292858096,2223,1381226,6.704225,J6,11.130744251720401,5.955607695749779,47.96099999999992,53.50831985473633,-1.6014881600510762,7.412222862243652,-1.3472745119799665,-9.629739761352539,-1.111522936067642,-32.30571365356445,8.811536812500137,12.488306045532227,2.379817930845359,30.826189041137695,-11.130744251720401,2227,1381227,6.712268,J6,13.165517124645206,7.03533795629365,56.00399999999972,53.18336486816406,-1.9264431466233418,7.154932975769043,-1.6045643984545759,-9.842713356018066,-1.3244965307331693,-30.750762939453125,10.366487526611465,12.961709976196289,2.8532218615094216,28.79141616821289,-13.165517124645206,2229,1381228,6.720286,J6,15.212756428234073,8.119717107623766,64.02199999999958,52.84844207763672,-2.2613659371506856,6.892857074737549,-1.86664029948607,-10.059968948364258,-1.5417521230793607,-29.200178146362305,11.917072319702285,13.449018478393555,3.340530363706687,26.744176864624023,-15.212756428234073,6 +4,4,875,2321,1381263,7.000291,43.65359310453334,-1.629659559507137,-17.715753611915318,5.995208633582219,32.17171770646655,-22.573973337684023,2336,1381269,7.048266,J6,2.3116842904428125,1.3731583485126422,47.9750000000001,44.433006286621094,0.779413182087751,-2.1156294345855713,-0.48596987507843425,-18.39777374267578,-0.6820201307604634,4.8371992111206055,-1.158009422461613,33.99372482299805,1.8220071165314948,-20.26228904724121,2.3116842904428125,2339,1381270,7.056293,J6,3.083185831580508,1.7437179916275436,56.00200000000033,44.65972137451172,1.006128269978376,-2.1617355346679688,-0.5320759751608317,-18.490602493286133,-0.774848881370815,4.406780242919922,-1.5884283906622967,34.24979019165039,2.0780724851838386,-19.490787506103516,3.083185831580508,2341,1381271,7.064278,J6,3.9520155587533594,2.15807761350517,63.987000000000016,44.91068649291992,1.2570933883865791,-2.196718215942383,-0.5670586564352458,-18.575654983520508,-0.85990137160519,3.9164717197418213,-2.0787369138403973,34.48752212524414,2.3158044187775886,-18.621957778930664,3.9520155587533594,6 +5,5,927,2457,1381315,7.416267,64.58244475717193,4.262979315506351,-15.669217122643433,-29.952927185666542,29.850439933020308,45.62652743544272,2477,1381321,7.464289,J5,2.171828177649214,1.404704060246151,48.021999999999565,65.84967041015625,1.2672256529843224,5.695003032684326,1.4320237171779748,-14.563636779785156,1.1055803428582767,-28.69342613220215,1.259501053464394,27.678611755371094,-2.171828177649214,46.437156677246094,0.810629241803376,2479,1381322,7.472265,J5,2.585320380408003,1.6670103454750829,55.99799999999977,66.01153564453125,1.4290908873593224,5.957343578338623,1.6943642628322717,-14.353693008422852,1.3155241142205814,-28.232824325561523,1.720102860105019,27.265119552612305,-2.585320380408003,46.24872589111328,0.6221984556705635,2483,1381323,7.480284,J5,3.0049218208376907,1.9507458917658702,64.01699999999977,66.15470886230469,1.57226410513276,6.221103191375732,1.958123875869381,-14.140877723693848,1.5283393989495853,-27.70670509338379,2.2462220922827534,26.845518112182617,-3.0049218208376907,45.96609115600586,0.33956372056314166,6 +6,6,997,2649,1381385,7.976283,60.47948252708421,23.068781981390185,-5.01126420129949,36.10685486878251,5.525681179628412,-31.3025636495649,2665,1381391,8.024277,J2,1.408993592340284,0.8424031169595625,47.99400000000009,60.847530364990234,0.36804783790602613,24.47777557373047,1.408993592340284,-5.617043972015381,-0.6057797707158912,34.8044319152832,-1.3024229534993097,5.786255359649658,0.260574180021246,-31.2253475189209,0.07721613064400046,2667,1381392,8.032258,J4,1.7701696881184503,1.0669192783996628,55.975000000000996,60.95240783691406,0.47292530982985426,24.69621467590332,1.6274326945131357,-5.75187873840332,-0.7406145371038306,34.33668518066406,-1.7701696881184503,5.885775089263916,0.3600939096355038,-30.920429229736328,0.38213441982857077,2669,1381393,8.040307,J4,2.303903056038372,1.3309750271252634,64.02400000000074,61.06847381591797,0.5889912888337605,24.908491134643555,1.83970915325337,-5.895160675048828,-0.8838964737493384,33.80295181274414,-2.303903056038372,5.999467372894287,0.4737861932658749,-30.538488388061523,0.7640752615033755,6 +7,7,1053,2795,1381441,8.424315,70.47583707168602,16.39384887825908,-13.456948007467595,-27.892318852946243,14.53566195089614,31.71193282686115,2811,1381447,8.472317,J6,7.1941073647892395,4.618545262978315,48.00200000000032,70.8250503540039,0.3492132823178906,10.95259952545166,-5.4412493528074215,-13.136046409606934,0.3209015978606615,-34.68459701538086,-6.7922781624346165,14.022420883178711,-0.5132410677174288,38.90604019165039,7.1941073647892395,2814,1381448,8.480253,J6,8.24868042387127,5.331515658278243,55.93799999999938,70.84622192382812,0.37038485214210937,9.979106903076172,-6.41474197518291,-13.042067527770996,0.414880479696599,-35.67851257324219,-7.786193720295945,13.882524490356445,-0.6531374605396945,39.96061325073242,8.24868042387127,2817,1381449,8.488291,J6,9.275558780804865,6.035874346469871,63.976000000000255,70.85871887207031,0.38288180038429687,8.986706733703613,-7.407142144555468,-12.9381742477417,0.5187737597258959,-36.64576721191406,-8.75344835896782,13.72922134399414,-0.8064406069019991,40.987491607666016,9.275558780804865,6 +8,8,1089,2892,1381477,8.71231,69.58246408878419,-17.10571341532583,-8.710589696831251,-58.47569441890704,7.63059003611043,64.43773280685575,2908,1381483,8.760361,J6,5.93988407546847,3.533132924843701,48.05099999999918,69.40516662597656,-0.1772974628076298,-20.066781997680664,-2.9610685823548337,-8.152935981750488,0.5576537150807628,-63.93235397338867,-5.456659554481632,6.775912284851074,-0.8546777512593557,70.37761688232422,5.93988407546847,2910,1381484,8.768294,J6,6.922168621366907,4.090127751125079,55.9839999999987,69.39288330078125,-0.1895807880029423,-20.37982177734375,-3.2741083620179197,-8.094615936279297,0.6159737605519542,-64.83366394042969,-6.357969521522648,6.682157039642334,-0.948432996468096,71.35990142822266,6.922168621366907,2912,1381485,8.77637,J6,7.916950115507532,4.649086823187336,64.05999999999956,69.38455200195312,-0.1979120868310673,-20.654869079589844,-3.5491556642640134,-8.043557167053223,0.6670325297780284,-65.74622344970703,-7.270529030799992,6.598722457885742,-1.0318675782246878,72.35468292236328,7.916950115507532,6 +9,9,1167,3096,1381555,9.336375,73.57125874861414,-6.42984524964374,-9.628579811400881,-128.80864727564332,14.671082817606491,140.00204816414424,3112,1381561,9.384332,J4,2.5240706198644887,1.630593251762862,47.95700000000025,73.90592956542969,0.3346708168155459,-8.016124725341797,-1.5862794756980572,-9.002426147460938,0.6261536639399434,-131.3327178955078,-2.5240706198644887,15.526206970214844,0.8551241526083526,142.41668701171875,2.4146388475745084,3114,1381562,9.392355,J4,2.830116152091051,1.8576181251506447,55.97999999999992,73.96361541748047,0.39235666886632714,-8.363268852233887,-1.933423602590147,-8.884164810180664,0.7444150012202169,-131.63876342773438,-2.830116152091051,15.65822696685791,0.987144149251419,142.69912719726562,2.6970790331213834,3118,1381563,9.400331,J4,3.1013106100988637,2.0726473342415157,63.955999999999236,74.02384948730469,0.4525907386905459,-8.728874206542969,-2.299028956899229,-8.76374626159668,0.8648335498042012,-131.9099578857422,-3.1013106100988637,15.788768768310547,1.1176859507040557,142.9464569091797,2.944408745035446,6 +10,10,1189,3154,1381577,9.512325,75.56938603377543,-14.67930590788221,-7.294156094303152,-130.92303342701462,17.688361072687904,141.89350789658602,3170,1381583,9.560358,J4,3.3929965007450846,2.1059862266772753,48.033000000000214,76.72688293457031,1.1574969007948823,-16.518726348876953,-1.8394204409947434,-7.15147066116333,0.14268543313982196,-127.53003692626953,3.3929965007450846,18.53788948059082,0.8495284079029162,138.7897491455078,-3.103758751078203,3172,1381584,9.568328,J4,4.172522257581022,2.562681778316925,56.00299999999869,76.96759033203125,1.3982042982558198,-16.79741096496582,-2.1181050570836106,-7.157340049743652,0.1368160445594997,-126.7505111694336,4.172522257581022,18.698274612426758,1.0099135397388537,138.08377075195312,-3.8097371446328907,3175,1381585,9.576354,J4,4.999006937756803,3.042227585289095,64.02899999999967,77.21700286865234,1.6476168348769136,-17.061315536499023,-2.3820096286168138,-7.1720428466796875,0.12211324762346454,-125.92402648925781,4.999006937756803,18.86079978942871,1.1724387167408068,137.33689880371094,-4.556609092875078,6 +11,11,1257,3334,1381645,10.056378,86.0934977330502,-14.498333177585573,-13.681510793859989,-69.8682955440411,26.742681901805224,88.9994136192388,3350,1381651,10.104322,J6,2.9848240516596434,2.0134925384056306,47.94399999999932,83.71662139892578,-2.376876334124418,-13.186573028564453,1.3117601490211204,-13.738808631896973,-0.05729783803698396,-72.70317840576172,-2.834882861720615,26.66758918762207,-0.07509271418315322,91.98423767089844,2.9848240516596434,3352,1381652,10.112375,J6,3.668104996483862,2.4582215610180698,55.99699999999963,83.25711822509766,-2.836379507952543,-12.97336483001709,1.5249683475684837,-13.721805572509766,-0.04029477864977693,-73.39217376708984,-3.52387822304874,26.632198333740234,-0.11048356806498916,92.66751861572266,3.668104996483862,3355,1381653,10.120334,J6,4.397696366112768,2.932320329432031,63.955999999999236,82.77881622314453,-3.314681509905668,-12.759296417236328,1.7390367603492454,-13.69810676574707,-0.016595971887081618,-74.13638305664062,-4.2680875125995215,26.590784072875977,-0.15189782892924697,93.39710998535156,4.397696366112768,6 +12,12,1303,3455,1381691,10.424364,61.720732564877665,-4.2327893098442155,-9.784423185760874,-108.38408270751574,22.16077221865394,118.14206389103131,3470,1381697,10.472333,J6,4.000958239175844,1.9902461373621054,47.96900000000015,59.78955841064453,-1.9311741542331333,-2.7094168663024902,1.5233724435417253,-8.933828353881836,0.8505948318790377,-108.24857330322266,0.13550940429308866,21.177492141723633,-0.9832800769303063,114.14110565185547,-4.000958239175844,3474,1381698,10.480323,J6,4.9290969720860005,2.3936680954994465,55.95899999999965,59.577754974365234,-2.14297759051243,-2.4748787879943848,1.7579105218498308,-8.810709953308105,0.9737132324527682,-107.96444702148438,0.4196356860313699,21.03158187866211,-1.1291903399918297,113.21296691894531,-4.9290969720860005,3476,1381699,10.488357,J6,5.929425036050844,2.82736270797146,63.99299999999997,59.389400482177734,-2.33133208269993,-2.241248369216919,1.9915409406272966,-8.690967559814453,1.0934556259464205,-107.61585998535156,0.7682227221641824,20.888513565063477,-1.2722586535904625,112.21263885498047,-5.929425036050844,6 +13,13,1362,3611,1381750,10.896324,79.75708299218505,4.6402310571176475,-15.311185536748695,-56.03531247084017,26.13313788666632,52.152257172101606,3627,1381756,10.944371,J1,6.656208938967296,4.109282348845555,48.047000000000395,86.41329193115234,6.656208938967296,4.266173362731934,-0.3740576943857139,-19.15987777709961,-3.848692240350914,-52.24028015136719,3.7950323194729805,29.853723526000977,3.720585639334658,55.86907196044922,3.7168147883476124,3629,1381757,10.952347,J1,7.722973770510265,4.798701518313248,56.02299999999971,87.48005676269531,7.722973770510265,4.181386947631836,-0.45884410948581156,-19.803678512573242,-4.492492975824547,-51.80745315551758,4.22785931532259,30.479778289794922,4.346640403128603,56.773921966552734,4.621664794451128,3633,1381758,10.960362,J1,8.793827957521984,5.501947903898078,64.03800000000004,88.55091094970703,8.793827957521984,4.09064245223999,-0.5495886048776573,-20.455745697021484,-5.144560160272789,-51.41057586669922,4.624736604140949,31.11459732055664,4.981459433890322,57.749176025390625,5.596918853289019,6 +14,14,1393,3694,1381781,11.144347,108.95555081237923,1.3707600427061273,-33.59442519685133,-48.36736100282485,43.963403989432074,85.03926111958742,3710,1381787,11.192339,J6,2.93518040873289,1.5679827735552678,47.9920000000007,110.7523193359375,1.7967685235582707,0.8751887679100037,-0.4955712747961236,-34.756534576416016,-1.1621093795646829,-48.14332580566406,0.2240351971607879,45.0865592956543,1.1231553062222233,87.97444152832031,2.93518040873289,3712,1381788,11.20037,J6,3.224395496623515,1.7217974200989363,56.02299999999971,110.92666625976562,1.9711154473863957,0.7935436964035034,-0.5772163463026239,-34.86042404174805,-1.265998844896714,-48.102813720703125,0.2645472821217254,45.1879768371582,1.2245728477261295,88.26365661621094,3.224395496623515,3714,1381789,11.208359,J6,3.4549634287524214,1.844232474600388,64.01199999999996,111.0645751953125,2.1090243829332707,0.7117024660110474,-0.6590575766950799,-34.93739700317383,-1.3429718063224954,-48.06473159790039,0.3026294049244598,45.2645149230957,1.3011109336636295,88.49422454833984,3.4549634287524214,6 +15,15,1426,3780,1381814,11.408361,110.58484797608095,-3.860899593758653,-32.39640711653168,-51.112590875369015,44.2291595765054,87.62399978413751,3796,1381820,11.456361,J4,2.573146734494266,1.686689503314435,48.00000000000004,111.79177856445312,1.206930588372174,-6.225440502166748,-2.364540908408095,-31.958988189697266,0.4374189268344111,-53.68573760986328,-2.573146734494266,44.76150894165039,0.532349365144988,89.33454895019531,1.7105491660578025,3798,1381821,11.464349,J4,3.0742606260958283,2.037357874022524,55.98800000000104,112.08367919921875,1.498831223137799,-6.6951823234558105,-2.8342827296971573,-31.910240173339844,0.48616694319183296,-54.186851501464844,-3.0742606260958283,44.907752990722656,0.6785934142172536,89.73998260498047,2.1159828208429587,3801,1381822,11.472359,J4,3.579433355588016,2.3980591339233284,63.998000000001554,112.39252471923828,1.8076767431573302,-7.177155494689941,-3.316255900931288,-31.86791229248047,0.528494824051208,-54.69202423095703,-3.579433355588016,45.06621170043945,0.8370521239340505,90.16300964355469,2.5390098594171775,6 +16,16,1454,3854,1381842,11.63236,118.09595224767921,-17.37638727211256,-31.06900084212659,-59.56053843778568,48.73257576059714,94.13477131163891,3870,1381848,11.680362,J6,3.8105220440607894,2.197782779718695,48.00200000000032,117.87474060058594,-0.2212116470932699,-19.607177734375,-2.2307904622624406,-30.329103469848633,0.7398973722779587,-56.600250244140625,2.960288193645056,49.08655548095703,0.353979720359888,90.32424926757812,-3.8105220440607894,3872,1381849,11.688367,J6,4.756826365349852,2.710409629634467,56.00699999999925,117.7159423828125,-0.3800098648667074,-19.938819885253906,-2.562432613141347,-30.167478561401367,0.9015222807252243,-55.84638595581055,3.714152481975134,49.0964241027832,0.3638483421860599,89.37794494628906,-4.756826365349852,3874,1381850,11.696354,J6,5.783033335564696,3.263710825457299,63.99399999999922,117.52371978759766,-0.5722324600815512,-20.255823135375977,-2.879435863263417,-29.996501922607422,1.0724989195191696,-55.02521514892578,4.5353232888598995,49.092247009277344,0.3596712486802005,88.35173797607422,-5.783033335564696,6 +17,18,1556,4121,1381944,12.448375,60.28248244838438,-22.938080568038327,-10.333247998560786,77.49164224770549,35.58337770883919,-69.66898071584893,4138,1381950,12.496365,J4,6.848522435693766,4.03614029285736,47.99000000000042,59.93589401245117,-0.34658843593320654,-21.102140426635742,1.8359401414025847,-9.684239387512207,0.6490086110485791,70.64311981201172,-6.848522435693766,32.941307067871094,-2.642070640968093,-63.34855651855469,6.320424197294244,4140,1381951,12.504369,J4,8.296535741357829,4.890437505980655,55.9940000000001,59.902915954589844,-0.37956649379453467,-20.689186096191406,2.2488944718469206,-9.52907943725586,0.8041685613049268,69.19510650634766,-8.296535741357829,32.343406677246094,-3.239971031593093,-62.032222747802734,7.636757968046197,4142,1381952,12.512395,J4,9.82779337075236,5.794164211715548,64.0199999999993,59.8752326965332,-0.4072497518511753,-20.24807357788086,2.6900069901574675,-9.361802101135254,0.9714458974255322,67.66384887695312,-9.82779337075236,31.704130172729492,-3.8792475361096947,-60.64377975463867,9.02520096121026,6 diff --git a/analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_summary.json b/analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_summary.json new file mode 100644 index 0000000..f303ad8 --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_summary.json @@ -0,0 +1,18 @@ +{ + "rows": 17, + "best_offset_win_counts": { + "6": 17, + "7": 0, + "8": 0 + }, + "average_max_error_deg": { + "6": 4.241583591714439, + "7": 5.068092118561757, + "8": 5.9316839578472145 + }, + "average_rms_error_deg": { + "6": 2.5406136706026206, + "7": 3.021296972079456, + "8": 3.5201758351699857 + } +} \ No newline at end of file diff --git a/analysis/2026042802-1/2026042802-1_trigger_status_feedback_vs_teach_points.csv b/analysis/2026042802-1/2026042802-1_trigger_status_feedback_vs_teach_points.csv new file mode 100644 index 0000000..d14ac3f --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_trigger_status_feedback_vs_teach_points.csv @@ -0,0 +1,18 @@ +trigger_no,waypoint_index,trigger_frame_number,trigger_time_relative_s,trigger_sequence,paired_status_frame_number,paired_status_time_relative_s,paired_status_sequence,paired_status_timestamp,paired_status_to_trigger_sequence_delta,paired_status_to_trigger_time_ms,teach_j1_deg,teach_j2_deg,teach_j3_deg,teach_j4_deg,teach_j5_deg,teach_j6_deg,paired_status_j1_actual_deg,paired_status_diff_j1_deg,paired_status_j2_actual_deg,paired_status_diff_j2_deg,paired_status_j3_actual_deg,paired_status_diff_j3_deg,paired_status_j4_actual_deg,paired_status_diff_j4_deg,paired_status_j5_actual_deg,paired_status_diff_j5_deg,paired_status_j6_actual_deg,paired_status_diff_j6_deg,paired_status_max_error_axis,paired_status_max_error_deg,paired_status_rms_error_deg,best_status_frame_number,best_status_time_relative_s,best_status_sequence,best_status_timestamp,best_status_delta_from_paired_cycles,best_status_delta_from_trigger_sequence,best_status_time_after_trigger_ms,best_status_j1_actual_deg,best_status_diff_j1_deg,best_status_j2_actual_deg,best_status_diff_j2_deg,best_status_j3_actual_deg,best_status_diff_j3_deg,best_status_j4_actual_deg,best_status_diff_j4_deg,best_status_j5_actual_deg,best_status_diff_j5_deg,best_status_j6_actual_deg,best_status_diff_j6_deg,best_status_max_error_axis,best_status_max_error_deg,best_status_rms_error_deg +1,1,1955,5.888271,1381124,1954,5.888166,1381116,624368380,-8,-0.10499999999957765,48.886810269468405,2.1989850886957285,-11.021017368511105,0.4102097980549552,6.248381265333557,2.294990756284542,55.47505187988281,6.588241610414407,1.2506256103515625,-0.948359478344166,-5.400295734405518,5.620721634105587,0.5259769558906555,0.1157671578357003,3.3612561225891113,-2.8871251427444458,0.44421958923339844,-1.8507711670511435,J1,6.588241610414407,3.8225443630072706,1981,5.968124,1381126,624368460,10,2,79.85300000000083,48.76339340209961,-0.12341686736879609,2.256481647491455,0.05749655879572657,-11.157909393310547,-0.13689202479944207,0.37860527634620667,-0.031604521708748556,6.3406662940979,0.0922850287643433,2.5025806427001953,0.20758988641565335,J6,0.20758988641565335,0.12239685259209541 +2,2,2151,6.480252,1381198,2150,6.480138,1381190,624368972,-8,-0.11399999999994748,55.34775509527405,11.807039833001637,-7.0090952672806885,-71.01433145543973,6.012065051914967,74.24953191606797,53.0124397277832,-2.33531536749085,11.506516456604004,-0.30052337639763316,-8.466276168823242,-1.4571809015425536,-69.63382720947266,1.3805042459670744,6.426647663116455,0.41458261120148787,72.93537902832031,-1.3141528877476532,J1,2.33531536749085,1.3827543088729335,2172,6.552164,1381199,624369044,9,1,71.9120000000002,55.265621185302734,-0.08213390997131853,11.803675651550293,-0.003364181451344095,-7.050373554229736,-0.04127828694904778,-71.06609344482422,-0.05176198938448806,6.026607990264893,0.014542938349925372,74.28738403320312,0.03785211713515935,J1,0.08213390997131853,0.04616054814443023 +3,3,2223,6.704225,1381226,2222,6.704166,1381218,624369196,-8,-0.059000000000253294,55.109808014787404,8.759497374223619,-8.518216825284897,-41.11725046606459,10.108488114686867,41.956933292858096,56.5781364440918,1.4683284293043926,10.494372367858887,1.7348749936352679,-7.114619255065918,1.4035975702189791,-55.112239837646484,-13.994989371581894,7.423236846923828,-2.6852512677630394,58.080867767333984,16.123934474475888,J6,16.123934474475888,8.852376616648156,2248,6.784154,1381228,624369276,10,2,79.92899999999992,55.00252151489258,-0.10728649989482619,8.69005012512207,-0.06944724910154854,-8.577027320861816,-0.05881049557691931,-40.59286117553711,0.5243892905274805,10.210430145263672,0.1019420305768044,41.38724136352539,-0.5696919293327056,J6,0.5696919293327056,0.3239642953120798 +4,4,2336,7.048266,1381269,2335,7.048148,1381261,624369540,-8,-0.11799999999961841,43.65359310453334,-1.629659559507137,-17.715753611915318,5.995208633582219,32.17171770646655,-22.573973337684023,44.479610443115234,0.8260173385818916,0.07192186266183853,1.7015814221689756,-15.996956825256348,1.7187967866589702,2.40028715133667,-3.5949214822455486,27.737812042236328,-4.433905664230224,-17.672090530395508,4.901882807288516,J6,4.901882807288516,3.2440556235708993,2362,7.128175,1381271,624369620,10,2,79.90899999999979,43.72715759277344,0.07356448824009476,-1.6386029720306396,-0.008943412523502614,-17.75617218017578,-0.04041856826046342,5.952699661254883,-0.04250897232733575,32.2616081237793,0.08989041731274483,-22.47926902770996,0.09470430997406254,J6,0.09470430997406254,0.06580474324148583 +5,5,2477,7.464289,1381321,2476,7.464178,1381313,624369956,-8,-0.11099999999952814,64.58244475717193,4.262979315506351,-15.669217122643433,-29.952927185666542,29.850439933020308,45.62652743544272,60.830230712890625,-3.7522140442813026,1.8808640241622925,-2.382115291344059,-17.297021865844727,-1.6278047432012936,-26.684900283813477,3.268026901853066,33.02191925048828,3.1714793174679734,36.40708923339844,-9.21943820204428,J6,9.21943820204428,4.621321121191458,2502,7.544122,1381323,624370036,10,2,79.83299999999983,64.5761947631836,-0.006249993988333813,4.283629417419434,0.020650101913082253,-15.615406036376953,0.05381108626647979,-29.94143295288086,0.011494232785683067,29.7938175201416,-0.056622412878706285,45.61851119995117,-0.008016235491545842,J5,0.056622412878706285,0.03357478517689402 +6,6,2665,8.024277,1381391,2664,8.024156,1381383,624370516,-8,-0.12100000000003774,60.47948252708421,23.068781981390185,-5.01126420129949,36.10685486878251,5.525681179628412,-31.3025636495649,60.89759063720703,0.418108110122823,20.716407775878906,-2.3523742055112784,-4.913214206695557,0.09804999460393304,32.72084045410156,-3.3860144146809503,6.405356407165527,0.8796752275371151,-25.156103134155273,6.1464605154096255,J6,6.1464605154096255,3.0478405356855083,2687,8.096181,1381392,624370588,9,1,71.90399999999997,60.54315185546875,0.06366932838454176,22.99761962890625,-0.07116235248393465,-5.037043571472168,-0.025779370172678284,36.056732177734375,-0.050122691048137824,5.545217037200928,0.019535857572515525,-31.12558937072754,0.17697427883735983,J6,0.17697427883735983,0.08563125303054821 +7,7,2811,8.472317,1381447,2810,8.472208,1381439,624370964,-8,-0.10900000000013677,70.47583707168602,16.39384887825908,-13.456948007467595,-27.892318852946243,14.53566195089614,31.71193282686115,69.13842010498047,-1.3374169667055469,22.202428817749023,5.808579939489942,-12.907807350158691,0.5491406573089037,-15.469873428344727,12.422445424601516,14.040997505187988,-0.4946644457081515,18.56021499633789,-13.15171783052326,J6,13.15171783052326,7.78202379046887,2834,8.54419,1381448,624371036,9,1,71.87300000000008,70.3967514038086,-0.07908566787742188,16.456680297851562,0.06283141959248084,-13.330730438232422,0.12621756923517324,-27.611600875854492,0.2807179770917507,14.542427062988281,0.006765112092141479,31.405345916748047,-0.30658691011310424,J6,0.30658691011310424,0.18210669390591905 +8,8,2908,8.760361,1381483,2907,8.760245,1381475,624371252,-8,-0.11600000000022703,69.58246408878419,-17.10571341532583,-8.710589696831251,-58.47569441890704,7.63059003611043,64.43773280685575,70.10379791259766,0.5213338238134639,-10.075156211853027,7.030557203472803,-9.98978042602539,-1.2791907291941396,-51.5866584777832,6.889035941123836,9.534638404846191,1.9040483687357614,57.0014533996582,-7.436279407197546,J6,7.436279407197546,5.127041386597523,2929,8.832199,1381484,624371324,9,1,71.83799999999962,69.59716033935547,0.014696250571276437,-17.044965744018555,0.06074767130727565,-8.703485488891602,0.007104207939649498,-58.473426818847656,0.0022676000593833123,7.6463942527771,0.015804216666669646,64.45584869384766,0.01811588699190736,J2,0.06074767130727565,0.02750705896232285 +9,9,3112,9.384332,1381561,3111,9.384204,1381553,624371876,-8,-0.128000000000128,73.57125874861414,-6.42984524964374,-9.628579811400881,-128.80864727564332,14.671082817606491,140.00204816414424,72.96144104003906,-0.6098177085750791,-6.3001484870910645,0.12969676255267526,-10.035088539123535,-0.40650872772265423,-123.29313659667969,5.515510678963636,13.165532112121582,-1.505550705484909,134.41644287109375,-5.585605293050492,J6,5.585605293050492,3.277206017832995,3134,9.456205,1381562,624371948,9,1,71.87300000000008,73.55095672607422,-0.02030202253992286,-6.441007614135742,-0.011162364492002474,-9.614203453063965,0.014376358336916084,-128.65638732910156,0.15225994654176134,14.635379791259766,-0.03570302634672551,139.8520965576172,-0.14995160652705408,J4,0.15225994654176134,0.089150370994325 +10,10,3170,9.560358,1381583,3169,9.56025,1381575,624372052,-8,-0.10800000000088517,75.56938603377543,-14.67930590788221,-7.294156094303152,-130.92303342701462,17.688361072687904,141.89350789658602,74.36400604248047,-1.2053799912949614,-10.514228820800781,4.165077087081428,-8.236283302307129,-0.9421272080039769,-132.6507110595703,-1.7276776325556966,16.38176155090332,-1.3065995217845838,143.5712890625,1.6777811659139843,J2,4.165077087081428,2.128983861449047,3196,9.640214,1381585,624372132,10,2,79.85599999999948,75.62290954589844,0.05352351212300732,-14.662798881530762,0.016507026351447962,-7.297325611114502,-0.003169516811349915,-130.88812255859375,0.03491086842086588,17.726865768432617,0.03850469574471305,141.820068359375,-0.0734395372110157,J6,0.0734395372110157,0.043285948566531826 +11,11,3350,10.104322,1381651,3349,10.104215,1381643,624372596,-8,-0.10699999999985721,86.0934977330502,-14.498333177585573,-13.681510793859989,-69.8682955440411,26.742681901805224,88.9994136192388,87.81878662109375,1.7252888880435506,-16.329286575317383,-1.8309533977318093,-13.073507308959961,0.6080034849000278,-70.97254180908203,-1.1042462650409277,26.366661071777344,-0.3760208300278798,89.37981414794922,0.3804005287104246,J2,1.8309533977318093,1.169340011162825,3373,10.17622,1381652,624372668,9,1,71.8980000000009,86.11857604980469,0.025078316754488128,-14.53605842590332,-0.03772524831774682,-13.651655197143555,0.029855596716434007,-69.8272933959961,0.04100214804500979,26.72992706298828,-0.012754838816942282,88.92174530029297,-0.07766831894582538,J6,0.07766831894582538,0.04246508921571141 +12,12,3470,10.472333,1381697,3469,10.472191,1381689,624372964,-8,-0.14200000000030855,61.720732564877665,-4.2327893098442155,-9.784423185760874,-108.38408270751574,22.16077221865394,118.14206389103131,65.91849517822266,4.197762613344992,-6.331206798553467,-2.0984174887092513,-11.008773803710938,-1.2243506179500638,-103.56172943115234,4.822353276363401,23.532543182373047,1.3717709637191078,117.46694946289062,-0.675114428140688,J4,4.822353276363401,2.8611252897135864,3493,10.544283,1381698,624373036,9,1,71.94999999999929,61.7418098449707,0.021077280093038553,-4.212831497192383,0.019957812651832718,-9.789913177490234,-0.00548999172936071,-108.36312103271484,0.020961674800901164,22.163673400878906,0.002901182224967158,118.13783264160156,-0.0042312494297505054,J1,0.021077280093038553,0.014935517911187784 +13,13,3627,10.944371,1381756,3626,10.944251,1381748,624373436,-8,-0.12000000000078614,79.75708299218505,4.6402310571176475,-15.311185536748695,-56.03531247084017,26.13313788666632,52.152257172101606,72.04009246826172,-7.716990523923329,4.5515007972717285,-0.08873025984591898,-11.385177612304688,3.926007924444008,-64.18782043457031,-8.152507963730145,22.412456512451172,-3.7206813742151468,54.39056396484375,2.2383067927421436,J4,8.152507963730145,5.168659196117862,3651,11.016226,1381757,624373508,9,1,71.85499999999934,79.90262603759766,0.14554304541260876,4.62429666519165,-0.015934391925997105,-15.370849609375,-0.05966407262630469,-55.99199676513672,0.043315705703449225,26.182939529418945,0.04980164275262666,52.15596008300781,0.003702910906206114,J1,0.14554304541260876,0.0699602863618738 +14,14,3710,11.192339,1381787,3709,11.19225,1381779,624373684,-8,-0.08900000000089392,108.95555081237923,1.3707600427061273,-33.59442519685133,-48.36736100282485,43.963403989432074,85.03926111958742,102.20675659179688,-6.748794220582354,2.5045764446258545,1.1338164019197272,-29.148040771484375,4.446384425366958,-48.76271438598633,-0.3953533831614777,39.65629959106445,-4.3071043983676205,74.63484191894531,-10.40441920064211,J6,10.40441920064211,5.679810575214033,3735,11.272261,1381789,624373764,10,2,79.92199999999983,108.94917297363281,-0.006377838746416842,1.3447811603546143,-0.025978882351513022,-33.561458587646484,0.032966609204848396,-48.376487731933594,-0.009126729108743348,44.02882766723633,0.06542367780425451,85.1587142944336,0.11945317484617135,J6,0.11945317484617135,0.05835950688086092 +15,15,3796,11.456361,1381820,3795,11.456253,1381812,624373948,-8,-0.10799999999910881,110.58484797608095,-3.860899593758653,-32.39640711653168,-51.112590875369015,44.2291595765054,87.62399978413751,110.58193969726562,-0.002908278815326071,-1.244791865348816,2.6161077284098373,-33.74542999267578,-1.3490228761441045,-48.585723876953125,2.5268669984158905,44.58126449584961,0.35210491934420673,87.75857543945312,0.134575655315615,J2,2.6161077284098373,1.5911754635072362,3821,11.536287,1381822,624374028,10,2,79.92600000000039,110.67292022705078,0.08807225096983018,-4.010702133178711,-0.14980253942005772,-32.37457275390625,0.021834362625426706,-51.22603225708008,-0.11344138171106266,44.249629974365234,0.020470397859831735,87.66435241699219,0.040352632854677495,J2,0.14980253942005772,0.08716897465096986 +16,16,3870,11.680362,1381848,3869,11.680263,1381840,624374172,-8,-0.09900000000051534,118.09595224767921,-17.37638727211256,-31.06900084212659,-59.56053843778568,48.73257576059714,94.13477131163891,115.98970031738281,-2.106251930296395,-12.693913459777832,4.682473812334727,-31.563575744628906,-0.49457490250231473,-59.300933837890625,0.2596045998950558,47.15116500854492,-1.5814107520522214,94.34331512451172,0.20854381287280432,J2,4.682473812334727,2.2067399623329567,3895,11.760326,1381850,624374252,10,2,79.96399999999859,118.06391906738281,-0.0320331802963949,-17.536325454711914,-0.15993818259935466,-31.00341796875,0.06558287337659152,-59.507286071777344,0.05325236600833705,48.77106475830078,0.038488997703638006,93.9618148803711,-0.17295643126782068,J6,0.17295643126782068,0.10419421346699378 +17,18,4138,12.496365,1381950,4137,12.496271,1381942,624374988,-8,-0.09400000000070463,60.28248244838438,-22.938080568038327,-10.333247998560786,77.49164224770549,35.58337770883919,-69.66898071584893,61.48033142089844,1.197848972514059,-23.1823787689209,-0.2442982008825716,-10.346473693847656,-0.01322569528687012,79.24906921386719,1.7574269661617024,35.91868591308594,0.33530820424675056,-71.48294067382812,-1.8139599579791934,J6,1.8139599579791934,1.1536991630766555,4159,12.568313,1381951,624375060,9,1,71.94799999999901,60.27695846557617,-0.00552398280820654,-22.918088912963867,0.019991655074459658,-10.347146987915039,-0.013898989354252933,77.54158782958984,0.0499455818843586,35.57386016845703,-0.009517540382155687,-69.74390411376953,-0.07492339792059965,J6,0.07492339792059965,0.03834497543010648 diff --git a/analysis/2026042802-1/2026042802-1_trigger_vs_teach_points.csv b/analysis/2026042802-1/2026042802-1_trigger_vs_teach_points.csv new file mode 100644 index 0000000..e9d855a --- /dev/null +++ b/analysis/2026042802-1/2026042802-1_trigger_vs_teach_points.csv @@ -0,0 +1,18 @@ +trigger_no,waypoint_index,frame_number,sequence,time_relative_s,write_io_value,io_addrs,config_addr,max_error_axis,max_error_deg,rms_error_deg,j1_actual_deg,j1_teach_deg,diff_j1_deg,j2_actual_deg,j2_teach_deg,diff_j2_deg,j3_actual_deg,j3_teach_deg,diff_j3_deg,j4_actual_deg,j4_teach_deg,diff_j4_deg,j5_actual_deg,j5_teach_deg,diff_j5_deg,j6_actual_deg,j6_teach_deg,diff_j6_deg +1,1,1955,1381124,5.888271,10,"[2, 4]","[2, 4]",J6,3.4065268256551553,2.468438977201345,45.8632698059082,48.886810269468405,-3.0235404635602023,2.9724788665771484,2.1989850886957285,0.7734937778814199,-13.609341621398926,-11.021017368511105,-2.588324252887821,-2.1851141452789307,0.4102097980549552,-2.5953239433338857,7.582361221313477,6.248381265333557,1.3339799559799195,5.701517581939697,2.294990756284542,3.4065268256551553 +2,2,2151,1381198,6.480252,14,"[2, 3, 4]","[3, 4, 2]",J4,2.8167225076858244,1.6562461612538633,56.34336471557617,55.34775509527405,0.995609620302119,11.66723918914795,11.807039833001637,-0.13980064385368784,-6.600923538208008,-7.0090952672806885,0.40817172907268073,-68.1976089477539,-71.01433145543973,2.8167225076858244,6.058437347412109,6.012065051914967,0.04637229549714217,71.5392837524414,74.24953191606797,-2.7102481636265594 +3,3,2223,1381226,6.704225,14,"[2, 3, 4]","[3, 4, 2]",J6,11.130744251720401,5.955607695749779,53.50831985473633,55.109808014787404,-1.6014881600510762,7.412222862243652,8.759497374223619,-1.3472745119799665,-9.629739761352539,-8.518216825284897,-1.111522936067642,-32.30571365356445,-41.11725046606459,8.811536812500137,12.488306045532227,10.108488114686867,2.379817930845359,30.826189041137695,41.956933292858096,-11.130744251720401 +4,4,2336,1381269,7.048266,10,"[2, 4]","[4, 2]",J6,2.3116842904428125,1.3731583485126422,44.433006286621094,43.65359310453334,0.779413182087751,-2.1156294345855713,-1.629659559507137,-0.48596987507843425,-18.39777374267578,-17.715753611915318,-0.6820201307604634,4.8371992111206055,5.995208633582219,-1.158009422461613,33.99372482299805,32.17171770646655,1.8220071165314948,-20.26228904724121,-22.573973337684023,2.3116842904428125 +5,5,2477,1381321,7.464289,10,"[2, 4]","[4, 2]",J5,2.171828177649214,1.404704060246151,65.84967041015625,64.58244475717193,1.2672256529843224,5.695003032684326,4.262979315506351,1.4320237171779748,-14.563636779785156,-15.669217122643433,1.1055803428582767,-28.69342613220215,-29.952927185666542,1.259501053464394,27.678611755371094,29.850439933020308,-2.171828177649214,46.437156677246094,45.62652743544272,0.810629241803376 +6,6,2665,1381391,8.024277,12,"[3, 4]","[3, 4]",J2,1.408993592340284,0.8424031169595625,60.847530364990234,60.47948252708421,0.36804783790602613,24.47777557373047,23.068781981390185,1.408993592340284,-5.617043972015381,-5.01126420129949,-0.6057797707158912,34.8044319152832,36.10685486878251,-1.3024229534993097,5.786255359649658,5.525681179628412,0.260574180021246,-31.2253475189209,-31.3025636495649,0.07721613064400046 +7,7,2811,1381447,8.472317,12,"[3, 4]","[3, 4]",J6,7.1941073647892395,4.618545262978315,70.8250503540039,70.47583707168602,0.3492132823178906,10.95259952545166,16.39384887825908,-5.4412493528074215,-13.136046409606934,-13.456948007467595,0.3209015978606615,-34.68459701538086,-27.892318852946243,-6.7922781624346165,14.022420883178711,14.53566195089614,-0.5132410677174288,38.90604019165039,31.71193282686115,7.1941073647892395 +8,8,2908,1381483,8.760361,10,"[2, 4]","[4, 2]",J6,5.93988407546847,3.533132924843701,69.40516662597656,69.58246408878419,-0.1772974628076298,-20.066781997680664,-17.10571341532583,-2.9610685823548337,-8.152935981750488,-8.710589696831251,0.5576537150807628,-63.93235397338867,-58.47569441890704,-5.456659554481632,6.775912284851074,7.63059003611043,-0.8546777512593557,70.37761688232422,64.43773280685575,5.93988407546847 +9,9,3112,1381561,9.384332,10,"[2, 4]","[4, 2]",J4,2.5240706198644887,1.630593251762862,73.90592956542969,73.57125874861414,0.3346708168155459,-8.016124725341797,-6.42984524964374,-1.5862794756980572,-9.002426147460938,-9.628579811400881,0.6261536639399434,-131.3327178955078,-128.80864727564332,-2.5240706198644887,15.526206970214844,14.671082817606491,0.8551241526083526,142.41668701171875,140.00204816414424,2.4146388475745084 +10,10,3170,1381583,9.560358,10,"[2, 4]","[4, 2]",J4,3.3929965007450846,2.1059862266772753,76.72688293457031,75.56938603377543,1.1574969007948823,-16.518726348876953,-14.67930590788221,-1.8394204409947434,-7.15147066116333,-7.294156094303152,0.14268543313982196,-127.53003692626953,-130.92303342701462,3.3929965007450846,18.53788948059082,17.688361072687904,0.8495284079029162,138.7897491455078,141.89350789658602,-3.103758751078203 +11,11,3350,1381651,10.104322,10,"[2, 4]","[4, 2]",J6,2.9848240516596434,2.0134925384056306,83.71662139892578,86.0934977330502,-2.376876334124418,-13.186573028564453,-14.498333177585573,1.3117601490211204,-13.738808631896973,-13.681510793859989,-0.05729783803698396,-72.70317840576172,-69.8682955440411,-2.834882861720615,26.66758918762207,26.742681901805224,-0.07509271418315322,91.98423767089844,88.9994136192388,2.9848240516596434 +12,12,3470,1381697,10.472333,10,"[2, 4]","[4, 2]",J6,4.000958239175844,1.9902461373621054,59.78955841064453,61.720732564877665,-1.9311741542331333,-2.7094168663024902,-4.2327893098442155,1.5233724435417253,-8.933828353881836,-9.784423185760874,0.8505948318790377,-108.24857330322266,-108.38408270751574,0.13550940429308866,21.177492141723633,22.16077221865394,-0.9832800769303063,114.14110565185547,118.14206389103131,-4.000958239175844 +13,13,3627,1381756,10.944371,12,"[3, 4]","[4, 3]",J1,6.656208938967296,4.109282348845555,86.41329193115234,79.75708299218505,6.656208938967296,4.266173362731934,4.6402310571176475,-0.3740576943857139,-19.15987777709961,-15.311185536748695,-3.848692240350914,-52.24028015136719,-56.03531247084017,3.7950323194729805,29.853723526000977,26.13313788666632,3.720585639334658,55.86907196044922,52.152257172101606,3.7168147883476124 +14,14,3710,1381787,11.192339,10,"[2, 4]","[4, 2]",J6,2.93518040873289,1.5679827735552678,110.7523193359375,108.95555081237923,1.7967685235582707,0.8751887679100037,1.3707600427061273,-0.4955712747961236,-34.756534576416016,-33.59442519685133,-1.1621093795646829,-48.14332580566406,-48.36736100282485,0.2240351971607879,45.0865592956543,43.963403989432074,1.1231553062222233,87.97444152832031,85.03926111958742,2.93518040873289 +15,15,3796,1381820,11.456361,10,"[2, 4]","[4, 2]",J4,2.573146734494266,1.686689503314435,111.79177856445312,110.58484797608095,1.206930588372174,-6.225440502166748,-3.860899593758653,-2.364540908408095,-31.958988189697266,-32.39640711653168,0.4374189268344111,-53.68573760986328,-51.112590875369015,-2.573146734494266,44.76150894165039,44.2291595765054,0.532349365144988,89.33454895019531,87.62399978413751,1.7105491660578025 +16,16,3870,1381848,11.680362,10,"[2, 4]","[4, 2]",J6,3.8105220440607894,2.197782779718695,117.87474060058594,118.09595224767921,-0.2212116470932699,-19.607177734375,-17.37638727211256,-2.2307904622624406,-30.329103469848633,-31.06900084212659,0.7398973722779587,-56.600250244140625,-59.56053843778568,2.960288193645056,49.08655548095703,48.73257576059714,0.353979720359888,90.32424926757812,94.13477131163891,-3.8105220440607894 +17,18,4138,1381950,12.496365,12,"[3, 4]","[4, 3]",J4,6.848522435693766,4.03614029285736,59.93589401245117,60.28248244838438,-0.34658843593320654,-21.102140426635742,-22.938080568038327,1.8359401414025847,-9.684239387512207,-10.333247998560786,0.6490086110485791,70.64311981201172,77.49164224770549,-6.848522435693766,32.941307067871094,35.58337770883919,-2.642070640968093,-63.34855651855469,-69.66898071584893,6.320424197294244 diff --git a/analysis/analyze_2026042802_1_status_feedback_vs_teach_points.py b/analysis/analyze_2026042802_1_status_feedback_vs_teach_points.py new file mode 100644 index 0000000..e119cce --- /dev/null +++ b/analysis/analyze_2026042802_1_status_feedback_vs_teach_points.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python3 +"""提取 2026042802-1 抓包中的 60015 状态反馈,并和 UTTC_MS11 示教点对比。""" + +from __future__ import annotations + +import csv +import json +import math +import struct +import subprocess +from collections import Counter +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +DEFAULT_PCAP = REPO_ROOT.parent / "Rvbust" / "uttc-20260428" / "2026042802-1.pcap" +DEFAULT_TSHARK = Path(r"D:\Zyx\Downloads\WiresharkPortable32\App\Wireshark\tshark.exe") +OUTPUT_DIR = REPO_ROOT / "analysis" / "2026042802-1" +CONFIG_PATH = REPO_ROOT / "Config" / "RobotConfig.json" +SEARCH_WINDOW_CYCLES = 20 + + +def be_u32(data: bytes, offset: int) -> int: + """按大端读取 4 字节无符号整数。""" + return struct.unpack(">I", data[offset : offset + 4])[0] + + +def be_u16(data: bytes, offset: int) -> int: + """按大端读取 2 字节无符号整数。""" + return struct.unpack(">H", data[offset : offset + 2])[0] + + +def be_f32(data: bytes, offset: int) -> float: + """按大端读取 4 字节浮点数。""" + return struct.unpack(">f", data[offset : offset + 4])[0] + + +def load_udp_rows(pcap: Path, tshark: Path) -> list[list[str]]: + """提取 UDP 60015 原始字段,后续按方向和长度拆分命令与状态。""" + command = [ + str(tshark), + "-r", + str(pcap), + "-Y", + "udp.port==60015", + "-T", + "fields", + "-e", + "frame.number", + "-e", + "frame.time_relative", + "-e", + "ip.src", + "-e", + "ip.dst", + "-e", + "udp.payload", + ] + output = subprocess.check_output(command, text=True, encoding="utf-8", errors="ignore") + rows: list[list[str]] = [] + for line in output.splitlines(): + if not line.strip(): + continue + parts = line.split("\t") + if len(parts) >= 5: + rows.append(parts[:5]) + return rows + + +def decode_command_records(rows: list[list[str]], client_ip: str, robot_ip: str) -> list[dict]: + """把 64B J519 命令帧解码成结构化记录。""" + records: list[dict] = [] + for frame_no, time_rel, ip_src, ip_dst, payload_hex in rows: + if ip_src != client_ip or ip_dst != robot_ip: + continue + + payload = bytes.fromhex(payload_hex) + if len(payload) != 64: + continue + + records.append( + { + "frame_number": int(frame_no), + "time_relative_s": float(time_rel), + "sequence": be_u32(payload, 0x08), + "write_io_value": be_u16(payload, 0x18), + "j1_deg": be_f32(payload, 0x1C), + "j2_deg": be_f32(payload, 0x20), + "j3_deg": be_f32(payload, 0x24), + "j4_deg": be_f32(payload, 0x28), + "j5_deg": be_f32(payload, 0x2C), + "j6_deg": be_f32(payload, 0x30), + } + ) + return records + + +def decode_status_records(rows: list[list[str]], client_ip: str, robot_ip: str) -> list[dict]: + """把 132B J519 状态帧按运行时代码同口径解码。""" + records: list[dict] = [] + for frame_no, time_rel, ip_src, ip_dst, payload_hex in rows: + if ip_src != robot_ip or ip_dst != client_ip: + continue + + payload = bytes.fromhex(payload_hex) + if len(payload) != 132: + continue + + status = payload[0x0C] + joints = [be_f32(payload, 0x3C + index * 4) for index in range(6)] + pose = [be_f32(payload, 0x18 + index * 4) for index in range(6)] + records.append( + { + "frame_number": int(frame_no), + "time_relative_s": float(time_rel), + "sequence": be_u32(payload, 0x08), + "status": status, + "accepts_command": bool(status & 0b0001), + "received_command": bool(status & 0b0010), + "system_ready": bool(status & 0b0100), + "robot_in_motion": bool(status & 0b1000), + "read_io_value": be_u16(payload, 0x12), + "timestamp": be_u32(payload, 0x14), + "pose_x_mm": pose[0], + "pose_y_mm": pose[1], + "pose_z_mm": pose[2], + "pose_w_deg": pose[3], + "pose_p_deg": pose[4], + "pose_r_deg": pose[5], + "j1_deg": joints[0], + "j2_deg": joints[1], + "j3_deg": joints[2], + "j4_deg": joints[3], + "j5_deg": joints[4], + "j6_deg": joints[5], + } + ) + return records + + +def pick_trigger_first_high_frames(records: list[dict]) -> list[dict]: + """由于 io_keep_cycles=2,只保留每组高电平脉冲的第一帧。""" + trigger_frames: list[dict] = [] + previous_high = False + for record in records: + current_high = record["write_io_value"] > 0 + if current_high and not previous_high: + trigger_frames.append(record) + previous_high = current_high + return trigger_frames + + +def load_uttc_ms11_config() -> dict: + """读取 UTTC_MS11 的示教点和触发配置。""" + config = json.loads(CONFIG_PATH.read_text(encoding="utf-8")) + return config["flying_shots"]["UTTC_MS11"] + + +def build_diff_row(prefix: str, actual_deg: list[float], teach_deg: list[float], row: dict) -> tuple[float, float, str]: + """向结果行写入逐轴误差,并返回聚合误差。""" + diffs = [actual_deg[index] - teach_deg[index] for index in range(6)] + abs_diffs = [abs(value) for value in diffs] + max_error = max(abs_diffs) + max_error_axis = f"J{abs_diffs.index(max_error) + 1}" + rms_error = math.sqrt(sum(value * value for value in diffs) / 6.0) + + for joint_index in range(6): + joint_no = joint_index + 1 + row[f"{prefix}_j{joint_no}_actual_deg"] = actual_deg[joint_index] + row[f"{prefix}_diff_j{joint_no}_deg"] = diffs[joint_index] + + row[f"{prefix}_max_error_axis"] = max_error_axis + row[f"{prefix}_max_error_deg"] = max_error + row[f"{prefix}_rms_error_deg"] = rms_error + return max_error, rms_error, max_error_axis + + +def build_trigger_status_rows(trigger_frames: list[dict], status_records: list[dict], shot_config: dict) -> list[dict]: + """按触发顺序对齐命令帧、当前状态帧以及最接近示教点的反馈状态帧。""" + rows: list[dict] = [] + trigger_waypoint_indices = [index for index, flag in enumerate(shot_config["shot_flags"]) if flag] + status_by_sequence = {record["sequence"]: record for record in status_records} + status_sequence_set = set(status_by_sequence) + + for trigger_no, (trigger_frame, waypoint_index) in enumerate(zip(trigger_frames, trigger_waypoint_indices), start=1): + teach_deg = [math.degrees(value) for value in shot_config["traj_waypoints"][waypoint_index]] + current_status_sequence = trigger_frame["sequence"] - 8 + current_status = status_by_sequence[current_status_sequence] + + row = { + "trigger_no": trigger_no, + "waypoint_index": waypoint_index, + "trigger_frame_number": trigger_frame["frame_number"], + "trigger_time_relative_s": trigger_frame["time_relative_s"], + "trigger_sequence": trigger_frame["sequence"], + "paired_status_frame_number": current_status["frame_number"], + "paired_status_time_relative_s": current_status["time_relative_s"], + "paired_status_sequence": current_status["sequence"], + "paired_status_timestamp": current_status["timestamp"], + "paired_status_to_trigger_sequence_delta": current_status["sequence"] - trigger_frame["sequence"], + "paired_status_to_trigger_time_ms": (current_status["time_relative_s"] - trigger_frame["time_relative_s"]) * 1000.0, + } + + for joint_index in range(6): + joint_no = joint_index + 1 + row[f"teach_j{joint_no}_deg"] = teach_deg[joint_index] + + build_diff_row( + "paired_status", + [current_status[f"j{joint_no}_deg"] for joint_no in range(1, 7)], + teach_deg, + row, + ) + + best_candidate = None + for delta_cycles in range(-SEARCH_WINDOW_CYCLES, SEARCH_WINDOW_CYCLES + 1): + candidate_sequence = current_status_sequence + delta_cycles + if candidate_sequence not in status_sequence_set: + continue + + candidate = status_by_sequence[candidate_sequence] + diffs = [candidate[f"j{joint_no}_deg"] - teach_deg[joint_no - 1] for joint_no in range(1, 7)] + rms_error = math.sqrt(sum(value * value for value in diffs) / 6.0) + max_error = max(abs(value) for value in diffs) + score = (rms_error, max_error, abs(delta_cycles)) + if best_candidate is None or score < best_candidate["score"]: + best_candidate = { + "score": score, + "delta_cycles": delta_cycles, + "record": candidate, + } + + if best_candidate is None: + raise RuntimeError(f"Trigger {trigger_no} 未找到候选状态帧。") + + best_status = best_candidate["record"] + row["best_status_frame_number"] = best_status["frame_number"] + row["best_status_time_relative_s"] = best_status["time_relative_s"] + row["best_status_sequence"] = best_status["sequence"] + row["best_status_timestamp"] = best_status["timestamp"] + row["best_status_delta_from_paired_cycles"] = best_candidate["delta_cycles"] + row["best_status_delta_from_trigger_sequence"] = best_status["sequence"] - trigger_frame["sequence"] + row["best_status_time_after_trigger_ms"] = (best_status["time_relative_s"] - trigger_frame["time_relative_s"]) * 1000.0 + + build_diff_row( + "best_status", + [best_status[f"j{joint_no}_deg"] for joint_no in range(1, 7)], + teach_deg, + row, + ) + + rows.append(row) + + return rows + + +def write_csv(path: Path, rows: list[dict]) -> None: + """把分析结果落成 UTF-8 CSV。""" + if not rows: + raise ValueError(f"No rows to write: {path}") + + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", newline="", encoding="utf-8") as handle: + writer = csv.DictWriter(handle, fieldnames=list(rows[0].keys())) + writer.writeheader() + writer.writerows(rows) + + +def build_summary(command_records: list[dict], trigger_status_rows: list[dict]) -> dict: + """汇总命令序列偏移和状态反馈误差分布。""" + sequence_offsets = [row["trigger_sequence"] - row["paired_status_sequence"] for row in trigger_status_rows] + best_paired_cycle_offsets = [row["best_status_delta_from_paired_cycles"] for row in trigger_status_rows] + best_trigger_sequence_offsets = [row["best_status_delta_from_trigger_sequence"] for row in trigger_status_rows] + best_time_offsets = [row["best_status_time_after_trigger_ms"] for row in trigger_status_rows] + paired_max_errors = [row["paired_status_max_error_deg"] for row in trigger_status_rows] + best_max_errors = [row["best_status_max_error_deg"] for row in trigger_status_rows] + paired_axes = Counter(row["paired_status_max_error_axis"] for row in trigger_status_rows) + best_axes = Counter(row["best_status_max_error_axis"] for row in trigger_status_rows) + + sequence_offset_counter = Counter() + trigger_frames = pick_trigger_first_high_frames(command_records) + status_pairs = {row["trigger_frame_number"]: row for row in trigger_status_rows} + for trigger_frame in trigger_frames: + sequence_offset_counter[trigger_frame["sequence"] - status_pairs[trigger_frame["frame_number"]]["paired_status_sequence"]] += 1 + + return { + "pcap_path": str(DEFAULT_PCAP), + "command_count": len(command_records), + "trigger_count": len(trigger_status_rows), + "command_minus_paired_status_sequence_counter": dict(sequence_offset_counter), + "paired_status_average_max_error_deg": sum(paired_max_errors) / len(paired_max_errors), + "paired_status_max_error_deg": max(paired_max_errors), + "paired_status_max_error_axis_counter": dict(paired_axes), + "best_status_average_max_error_deg": sum(best_max_errors) / len(best_max_errors), + "best_status_max_error_deg": max(best_max_errors), + "best_status_max_error_axis_counter": dict(best_axes), + "best_status_delta_from_paired_cycles_counter": dict(Counter(best_paired_cycle_offsets)), + "best_status_delta_from_trigger_sequence_counter": dict(Counter(best_trigger_sequence_offsets)), + "best_status_time_after_trigger_ms_min": min(best_time_offsets), + "best_status_time_after_trigger_ms_max": max(best_time_offsets), + "best_status_time_after_trigger_ms_avg": sum(best_time_offsets) / len(best_time_offsets), + "search_window_cycles": SEARCH_WINDOW_CYCLES, + } + + +def build_manual_compare_rows(trigger_status_rows: list[dict]) -> list[dict]: + """整理成便于人工逐点核对的三时刻对照表。""" + rows: list[dict] = [] + for row in trigger_status_rows: + rows.append( + { + "trigger_no": row["trigger_no"], + "waypoint_index": row["waypoint_index"], + "trigger_command_sequence": row["trigger_sequence"], + "trigger_command_frame": row["trigger_frame_number"], + "trigger_command_time_relative_s": row["trigger_time_relative_s"], + "trigger_current_status_sequence": row["paired_status_sequence"], + "trigger_current_status_frame": row["paired_status_frame_number"], + "trigger_current_status_time_relative_s": row["paired_status_time_relative_s"], + "command_leads_status_cycles": row["trigger_sequence"] - row["paired_status_sequence"], + "trigger_current_status_max_error_axis": row["paired_status_max_error_axis"], + "trigger_current_status_max_error_deg": row["paired_status_max_error_deg"], + "trigger_current_status_rms_error_deg": row["paired_status_rms_error_deg"], + "best_status_sequence": row["best_status_sequence"], + "best_status_frame": row["best_status_frame_number"], + "best_status_time_relative_s": row["best_status_time_relative_s"], + "best_status_delay_from_current_status_cycles": row["best_status_delta_from_paired_cycles"], + "best_status_delay_from_trigger_command_cycles": row["best_status_delta_from_trigger_sequence"], + "best_status_delay_from_trigger_command_ms": row["best_status_time_after_trigger_ms"], + "best_status_max_error_axis": row["best_status_max_error_axis"], + "best_status_max_error_deg": row["best_status_max_error_deg"], + "best_status_rms_error_deg": row["best_status_rms_error_deg"], + } + ) + return rows + + +def main() -> None: + """执行状态反馈提取、触发对齐和摘要落盘。""" + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + rows = load_udp_rows(DEFAULT_PCAP, DEFAULT_TSHARK) + command_records = decode_command_records(rows, client_ip="192.168.10.10", robot_ip="192.168.10.11") + status_records = decode_status_records(rows, client_ip="192.168.10.10", robot_ip="192.168.10.11") + trigger_frames = pick_trigger_first_high_frames(command_records) + shot_config = load_uttc_ms11_config() + trigger_status_rows = build_trigger_status_rows(trigger_frames, status_records, shot_config) + manual_compare_rows = build_manual_compare_rows(trigger_status_rows) + summary = build_summary(command_records, trigger_status_rows) + + write_csv(OUTPUT_DIR / "2026042802-1_j519_status_feedback_all.csv", status_records) + write_csv(OUTPUT_DIR / "2026042802-1_trigger_status_feedback_vs_teach_points.csv", trigger_status_rows) + write_csv(OUTPUT_DIR / "2026042802-1_trigger_manual_compare.csv", manual_compare_rows) + (OUTPUT_DIR / "2026042802-1_status_feedback_summary.json").write_text( + json.dumps(summary, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + + print(json.dumps(summary, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/analysis/analyze_2026042802_1_trigger_vs_teach_points.py b/analysis/analyze_2026042802_1_trigger_vs_teach_points.py new file mode 100644 index 0000000..c2db673 --- /dev/null +++ b/analysis/analyze_2026042802_1_trigger_vs_teach_points.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +"""提取 2026042802-1 抓包中的真实 J519 发包,并对比 UTTC_MS11 示教点。""" + +from __future__ import annotations + +import csv +import json +import math +import struct +import subprocess +from collections import Counter +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +DEFAULT_PCAP = REPO_ROOT.parent / "Rvbust" / "uttc-20260428" / "2026042802-1.pcap" +DEFAULT_TSHARK = Path(r"D:\Zyx\Downloads\WiresharkPortable32\App\Wireshark\tshark.exe") +OUTPUT_DIR = REPO_ROOT / "analysis" / "2026042802-1" +CONFIG_PATH = REPO_ROOT / "Config" / "RobotConfig.json" +RUNTIME_DATA_DIR = REPO_ROOT / "Config" / "Data" / "UTTC_MS11" + + +def be_u32(data: bytes, offset: int) -> int: + """按大端读取 4 字节无符号整数。""" + return struct.unpack(">I", data[offset : offset + 4])[0] + + +def be_u16(data: bytes, offset: int) -> int: + """按大端读取 2 字节无符号整数。""" + return struct.unpack(">H", data[offset : offset + 2])[0] + + +def be_f32(data: bytes, offset: int) -> float: + """按大端读取 4 字节浮点数。""" + return struct.unpack(">f", data[offset : offset + 4])[0] + + +def load_j519_command_rows(pcap: Path, tshark: Path) -> list[list[str]]: + """只提取 UDP 60015 的原始字段,后续再按 IP 方向筛选真实下发命令。""" + command = [ + str(tshark), + "-r", + str(pcap), + "-Y", + "udp.port==60015", + "-T", + "fields", + "-e", + "frame.number", + "-e", + "frame.time_relative", + "-e", + "ip.src", + "-e", + "udp.srcport", + "-e", + "ip.dst", + "-e", + "udp.dstport", + "-e", + "udp.payload", + ] + output = subprocess.check_output(command, text=True, encoding="utf-8", errors="ignore") + rows: list[list[str]] = [] + for line in output.splitlines(): + if not line.strip(): + continue + parts = line.split("\t") + if len(parts) >= 7: + rows.append(parts[:7]) + return rows + + +def decode_command_records(rows: list[list[str]], client_ip: str, robot_ip: str) -> list[dict]: + """把抓包中的 64B J519 命令帧解码成带 IO 信息的结构化记录。""" + records: list[dict] = [] + for frame_no, time_rel, ip_src, _udp_src, ip_dst, _udp_dst, payload_hex in rows: + if ip_src != client_ip or ip_dst != robot_ip: + continue + + payload = bytes.fromhex(payload_hex) + if len(payload) != 64: + continue + + io_value = be_u16(payload, 0x18) + io_addrs = [bit + 1 for bit in range(16) if io_value & (1 << bit)] + targets = [be_f32(payload, 0x1C + index * 4) for index in range(9)] + records.append( + { + "frame_number": int(frame_no), + "time_relative_s": float(time_rel), + "sequence": be_u32(payload, 0x08), + "last_data": payload[0x0C], + "write_io_type": payload[0x13], + "write_io_index": be_u16(payload, 0x14), + "write_io_mask": be_u16(payload, 0x16), + "write_io_value": io_value, + "io_addrs": io_addrs, + "j1_deg": targets[0], + "j2_deg": targets[1], + "j3_deg": targets[2], + "j4_deg": targets[3], + "j5_deg": targets[4], + "j6_deg": targets[5], + "ext1_deg": targets[6], + "ext2_deg": targets[7], + "ext3_deg": targets[8], + } + ) + return records + + +def pick_trigger_first_high_frames(records: list[dict]) -> list[dict]: + """由于 io_keep_cycles=2,只记录每组高电平脉冲的第一帧。""" + trigger_frames: list[dict] = [] + previous_high = False + for record in records: + current_high = record["write_io_value"] > 0 + if current_high and not previous_high: + trigger_frames.append(record) + previous_high = current_high + return trigger_frames + + +def load_uttc_ms11_config() -> dict: + """读取 UTTC_MS11 的示教点和触发配置。""" + config = json.loads(CONFIG_PATH.read_text(encoding="utf-8")) + return config["flying_shots"]["UTTC_MS11"] + + +def build_trigger_vs_teach_rows(trigger_frames: list[dict], shot_config: dict) -> list[dict]: + """按 shot_flags 为 true 的 waypoint 顺序,对齐抓包触发帧和示教点。""" + rows: list[dict] = [] + trigger_waypoint_indices = [index for index, flag in enumerate(shot_config["shot_flags"]) if flag] + + for trigger_no, (frame, waypoint_index) in enumerate(zip(trigger_frames, trigger_waypoint_indices), start=1): + teach_rad = shot_config["traj_waypoints"][waypoint_index] + teach_deg = [math.degrees(value) for value in teach_rad] + actual_deg = [frame[f"j{joint_index}_deg"] for joint_index in range(1, 7)] + diffs = [actual_deg[index] - teach_deg[index] for index in range(6)] + abs_diffs = [abs(value) for value in diffs] + max_error = max(abs_diffs) + max_error_axis = f"J{abs_diffs.index(max_error) + 1}" + rms_error = math.sqrt(sum(value * value for value in diffs) / 6.0) + + row = { + "trigger_no": trigger_no, + "waypoint_index": waypoint_index, + "frame_number": frame["frame_number"], + "sequence": frame["sequence"], + "time_relative_s": frame["time_relative_s"], + "write_io_value": frame["write_io_value"], + "io_addrs": frame["io_addrs"], + "config_addr": shot_config["addr"][waypoint_index], + "max_error_axis": max_error_axis, + "max_error_deg": max_error, + "rms_error_deg": rms_error, + } + + for joint_index in range(6): + joint_no = joint_index + 1 + row[f"j{joint_no}_actual_deg"] = actual_deg[joint_index] + row[f"j{joint_no}_teach_deg"] = teach_deg[joint_index] + row[f"diff_j{joint_no}_deg"] = diffs[joint_index] + + rows.append(row) + + return rows + + +def write_csv(path: Path, rows: list[dict]) -> None: + """把分析结果落成 UTF-8 CSV,便于后续继续筛选和画图。""" + if not rows: + raise ValueError(f"No rows to write: {path}") + + serializable_rows: list[dict] = [] + for row in rows: + serializable_row: dict = {} + for key, value in row.items(): + if isinstance(value, list): + serializable_row[key] = json.dumps(value, ensure_ascii=False) + else: + serializable_row[key] = value + serializable_rows.append(serializable_row) + + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", newline="", encoding="utf-8") as handle: + writer = csv.DictWriter(handle, fieldnames=list(serializable_rows[0].keys())) + writer.writeheader() + writer.writerows(serializable_rows) + + +def build_summary(records: list[dict], trigger_rows: list[dict]) -> dict: + """汇总本次分析关心的导出状态和误差统计。""" + max_errors = [float(row["max_error_deg"]) for row in trigger_rows] + rms_errors = [float(row["rms_error_deg"]) for row in trigger_rows] + axis_counter = Counter(str(row["max_error_axis"]) for row in trigger_rows) + + order_only_addr_mismatch = 0 + real_addr_mismatch = 0 + for row in trigger_rows: + io_addrs = list(row["io_addrs"]) + config_addr = list(row["config_addr"]) + if io_addrs != config_addr: + if sorted(io_addrs) == sorted(config_addr): + order_only_addr_mismatch += 1 + else: + real_addr_mismatch += 1 + + return { + "pcap_path": str(DEFAULT_PCAP), + "all_command_count": len(records), + "trigger_count": len(trigger_rows), + "existing_runtime_actual_send_exists": (RUNTIME_DATA_DIR / "ActualSendJointTraj.txt").exists(), + "existing_runtime_actual_send_has_io_columns": False, + "existing_shot_events_exists": (RUNTIME_DATA_DIR / "ShotEvents.json").exists(), + "pcap_specific_combined_export_preexisting": False, + "average_max_error_deg": sum(max_errors) / len(max_errors), + "max_error_deg": max(max_errors), + "average_rms_error_deg": sum(rms_errors) / len(rms_errors), + "max_error_axis_counter": dict(axis_counter), + "order_only_addr_mismatch_count": order_only_addr_mismatch, + "real_addr_mismatch_count": real_addr_mismatch, + } + + +def main() -> None: + """执行抓包提取、示教点对齐、CSV 导出和摘要落盘。""" + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + rows = load_j519_command_rows(DEFAULT_PCAP, DEFAULT_TSHARK) + records = decode_command_records(rows, client_ip="192.168.10.10", robot_ip="192.168.10.11") + trigger_frames = pick_trigger_first_high_frames(records) + shot_config = load_uttc_ms11_config() + trigger_rows = build_trigger_vs_teach_rows(trigger_frames, shot_config) + summary = build_summary(records, trigger_rows) + + write_csv(OUTPUT_DIR / "2026042802-1_j519_actual_send_all_with_io.csv", records) + write_csv(OUTPUT_DIR / "2026042802-1_j519_trigger_frames.csv", trigger_frames) + write_csv(OUTPUT_DIR / "2026042802-1_trigger_vs_teach_points.csv", trigger_rows) + (OUTPUT_DIR / "2026042802-1_analysis_summary.json").write_text( + json.dumps(summary, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + + print(json.dumps(summary, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/analysis/build_trigger_offset_compare_6_7_8.py b/analysis/build_trigger_offset_compare_6_7_8.py new file mode 100644 index 0000000..4c6a579 --- /dev/null +++ b/analysis/build_trigger_offset_compare_6_7_8.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""生成 2026042802-1 抓包中触发偏移 6/7/8 周期的对照表。""" + +from __future__ import annotations + +import csv +import json +import math +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +OUTPUT_DIR = REPO_ROOT / "analysis" / "2026042802-1" +CONFIG_PATH = REPO_ROOT / "Config" / "RobotConfig.json" +ACTUAL_SEND_CSV = OUTPUT_DIR / "2026042802-1_j519_actual_send_all_with_io.csv" +TRIGGER_CSV = OUTPUT_DIR / "2026042802-1_j519_trigger_frames.csv" +OUTPUT_CSV = OUTPUT_DIR / "2026042802-1_trigger_offset_6_7_8_compare.csv" +OUTPUT_JSON = OUTPUT_DIR / "2026042802-1_trigger_offset_6_7_8_summary.json" + + +def load_rows(path: Path) -> list[dict]: + with path.open(encoding="utf-8") as handle: + return list(csv.DictReader(handle)) + + +def to_float_list(record: dict, prefix: str = "j") -> list[float]: + return [float(record[f"{prefix}{index}_deg"]) for index in range(1, 7)] + + +def compute_diff_metrics(actual_deg: list[float], teach_deg: list[float]) -> tuple[list[float], float, float, str]: + diffs = [actual_deg[index] - teach_deg[index] for index in range(6)] + abs_diffs = [abs(value) for value in diffs] + max_error = max(abs_diffs) + rms_error = math.sqrt(sum(value * value for value in diffs) / 6.0) + max_axis = f"J{abs_diffs.index(max_error) + 1}" + return diffs, max_error, rms_error, max_axis + + +def main() -> None: + actual_rows = load_rows(ACTUAL_SEND_CSV) + trigger_rows = load_rows(TRIGGER_CSV) + config = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))["flying_shots"]["UTTC_MS11"] + trigger_waypoint_indices = [index for index, flag in enumerate(config["shot_flags"]) if flag] + + actual_index_by_frame = {int(row["frame_number"]): row for row in actual_rows} + actual_order_by_frame = {int(row["frame_number"]): idx for idx, row in enumerate(actual_rows)} + + output_rows: list[dict] = [] + offset_win_counts = {6: 0, 7: 0, 8: 0} + + for trigger_no, (trigger_row, waypoint_index) in enumerate(zip(trigger_rows, trigger_waypoint_indices), start=1): + trigger_frame = int(trigger_row["frame_number"]) + trigger_order = actual_order_by_frame[trigger_frame] + teach_deg = [math.degrees(value) for value in config["traj_waypoints"][waypoint_index]] + + window_start = max(0, trigger_order - 20) + window_end = min(len(actual_rows) - 1, trigger_order + 20) + best_order = trigger_order + best_rms = float("inf") + best_max = float("inf") + + for candidate_order in range(window_start, window_end + 1): + candidate = actual_rows[candidate_order] + _, max_error, rms_error, _ = compute_diff_metrics(to_float_list(candidate), teach_deg) + score = (rms_error, max_error, abs(candidate_order - trigger_order)) + if (best_rms, best_max, abs(best_order - trigger_order)) > score: + best_order = candidate_order + best_rms = rms_error + best_max = max_error + + row = { + "trigger_no": trigger_no, + "waypoint_index": waypoint_index, + "best_sample_order": best_order, + "best_frame_number": int(actual_rows[best_order]["frame_number"]), + "best_sequence": int(actual_rows[best_order]["sequence"]), + "best_time_relative_s": float(actual_rows[best_order]["time_relative_s"]), + } + + for joint_index in range(6): + row[f"teach_j{joint_index + 1}_deg"] = teach_deg[joint_index] + + for offset in (6, 7, 8): + target_order = min(len(actual_rows) - 1, best_order + offset) + target = actual_rows[target_order] + actual_deg = to_float_list(target) + diffs, max_error, rms_error, max_axis = compute_diff_metrics(actual_deg, teach_deg) + + row[f"offset_{offset}_frame_number"] = int(target["frame_number"]) + row[f"offset_{offset}_sequence"] = int(target["sequence"]) + row[f"offset_{offset}_time_relative_s"] = float(target["time_relative_s"]) + row[f"offset_{offset}_max_error_axis"] = max_axis + row[f"offset_{offset}_max_error_deg"] = max_error + row[f"offset_{offset}_rms_error_deg"] = rms_error + row[f"offset_{offset}_delta_from_best_ms"] = ( + float(target["time_relative_s"]) - float(actual_rows[best_order]["time_relative_s"]) + ) * 1000.0 + for joint_index in range(6): + joint_no = joint_index + 1 + row[f"offset_{offset}_j{joint_no}_actual_deg"] = actual_deg[joint_index] + row[f"offset_{offset}_diff_j{joint_no}_deg"] = diffs[joint_index] + + best_offset = min( + (6, 7, 8), + key=lambda offset: ( + row[f"offset_{offset}_rms_error_deg"], + row[f"offset_{offset}_max_error_deg"], + ), + ) + row["best_of_6_7_8_offset"] = best_offset + offset_win_counts[best_offset] += 1 + output_rows.append(row) + + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + with OUTPUT_CSV.open("w", newline="", encoding="utf-8") as handle: + writer = csv.DictWriter(handle, fieldnames=list(output_rows[0].keys())) + writer.writeheader() + writer.writerows(output_rows) + + summary = { + "rows": len(output_rows), + "best_offset_win_counts": offset_win_counts, + "average_max_error_deg": { + str(offset): sum(row[f"offset_{offset}_max_error_deg"] for row in output_rows) / len(output_rows) + for offset in (6, 7, 8) + }, + "average_rms_error_deg": { + str(offset): sum(row[f"offset_{offset}_rms_error_deg"] for row in output_rows) / len(output_rows) + for offset in (6, 7, 8) + }, + } + OUTPUT_JSON.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8") + print(json.dumps(summary, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/analysis/calc_jointdetail_acceleration.py b/analysis/calc_jointdetail_acceleration.py new file mode 100644 index 0000000..5156b81 --- /dev/null +++ b/analysis/calc_jointdetail_acceleration.py @@ -0,0 +1,461 @@ +#!/usr/bin/env python3 +"""计算 JointDetialTraj 类文件的速度 / 加速度 / 跃度峰值,并与当前生效轴限位对比。 + +输入格式: + time joint1 joint2 ... jointN + +本脚本采用的规则: +1. 将轨迹按离散时间采样点读取,允许时间轴非等间隔。 +2. 自动推断角度单位: + - 任一关节绝对值超过 2*pi*1.5,则按 degree 处理 + - 否则按 radian 处理 +3. 使用后向差分计算导数: + v_i = (q_i - q_{i-1}) / dt_i + a_i = (v_i - v_{i-1}) / dt_i + j_i = (a_i - a_{i-1}) / dt_i +4. 所有导数量统一换算成 rad 基单位,再与当前生效的机器人限值比较。 + +当前生效限值来源: + .robot limit.velocity + .robot limit.acceleration * RobotConfig.robot.acc_limit + .robot limit.jerk * RobotConfig.robot.jerk_limit +""" + +from __future__ import annotations + +import argparse +import math +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable + + +AUTO_DEG_THRESHOLD = 2.0 * math.pi * 1.5 +DEFAULT_VELOCITY_LIMITS = [6.45, 5.41, 7.15, 9.59, 9.51, 17.45] +DEFAULT_ACCELERATION_LIMITS = [26.90, 22.54, 29.81, 39.99, 39.63, 72.72] +DEFAULT_JERK_LIMITS = [224.22, 187.86, 248.46, 333.30, 330.27, 606.01] +DEFAULT_JOINT_NAMES = [f"Joint{index}" for index in range(1, 7)] + + +@dataclass(frozen=True) +class JointLimit: + name: str + velocity: float + acceleration: float + jerk: float + + +@dataclass(frozen=True) +class PeakMetric: + joint_name: str + axis_index: int + window_start: float + window_end: float + row_number: int + metric_native: float + metric_rad: float + effective_limit_rad: float + + @property + def ratio_vs_limit(self) -> float: + return abs(self.metric_rad) / self.effective_limit_rad + + +@dataclass(frozen=True) +class EffectiveLimits: + joints: list[JointLimit] + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Calculate velocity / acceleration / jerk peaks from JointDetialTraj.txt and compare with built-in effective robot limits." + ) + parser.add_argument("joint_detail", type=Path, help="Path to JointDetialTraj.txt") + parser.add_argument( + "--limit-csv", + type=Path, + default=None, + help="Optional CSV file with columns: Joint,Velocity,Acceleration,Jerk . If omitted, use built-in 1/1 effective limits.", + ) + parser.add_argument( + "--unit", + choices=("auto", "rad", "deg"), + default="auto", + help="Input joint-angle unit. Default: auto.", + ) + return parser.parse_args() + + +def resolve_path(path: Path) -> Path: + return path if path.is_absolute() else (Path.cwd() / path).resolve() + + +def read_joint_rows(path: Path) -> list[list[float]]: + rows: list[list[float]] = [] + for raw_line in path.read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line: + continue + rows.append([float(part) for part in line.split()]) + + if len(rows) < 4: + raise ValueError(f"{path} must contain at least 4 rows to calculate jerk.") + + width = len(rows[0]) + if width < 3: + raise ValueError(f"{path} must contain time + at least 2 joint columns.") + + for index, row in enumerate(rows, start=1): + if len(row) != width: + raise ValueError(f"{path} line {index} has inconsistent column count.") + + return rows + + +def trim_rows_to_limit_count(rows: list[list[float]], limit_count: int) -> tuple[list[list[float]], str | None]: + joint_count = len(rows[0]) - 1 + if joint_count == limit_count: + return rows, None + + if joint_count < limit_count: + raise ValueError(f"Joint column count ({joint_count}) is smaller than robot limit count ({limit_count}).") + + trimmed_rows = [row[: limit_count + 1] for row in rows] + ignored_joint_count = joint_count - limit_count + trim_note = ( + f"ignored_joint_columns={ignored_joint_count} " + f"(using first {limit_count} joints out of {joint_count}; trailing columns treated as external axes/placeholders)" + ) + return trimmed_rows, trim_note + + +def infer_unit(rows: Iterable[list[float]], requested_unit: str) -> str: + if requested_unit != "auto": + return requested_unit + + max_abs_joint = max(abs(value) for row in rows for value in row[1:]) + return "deg" if max_abs_joint > AUTO_DEG_THRESHOLD else "rad" + + +def read_limit_csv(path: Path) -> list[JointLimit]: + rows = [line.strip() for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] + if len(rows) < 2: + raise ValueError(f"{path} must contain a header and at least one data row.") + + header = [part.strip().lower() for part in rows[0].split(",")] + expected = ["joint", "velocity", "acceleration", "jerk"] + if header != expected: + raise ValueError(f"{path} header must be: Joint,Velocity,Acceleration,Jerk") + + limits: list[JointLimit] = [] + for row_index, row in enumerate(rows[1:], start=2): + parts = [part.strip() for part in row.split(",")] + if len(parts) != 4: + raise ValueError(f"{path} line {row_index} must contain 4 columns.") + limits.append( + JointLimit( + name=parts[0], + velocity=float(parts[1]), + acceleration=float(parts[2]), + jerk=float(parts[3]), + ) + ) + return limits + + +def load_effective_limits(limit_csv_path: Path | None) -> EffectiveLimits: + if limit_csv_path is not None: + limits = read_limit_csv(resolve_path(limit_csv_path)) + else: + limits = [ + JointLimit( + name=name, + velocity=velocity, + acceleration=acceleration, + jerk=jerk, + ) + for name, velocity, acceleration, jerk in zip( + DEFAULT_JOINT_NAMES, + DEFAULT_VELOCITY_LIMITS, + DEFAULT_ACCELERATION_LIMITS, + DEFAULT_JERK_LIMITS, + strict=True, + ) + ] + + return EffectiveLimits(joints=limits) + + +def to_radians(value: float, unit: str) -> float: + return math.radians(value) if unit == "deg" else value + + +def to_native_from_rad(value: float, unit: str) -> float: + return math.degrees(value) if unit == "deg" else value + + +def calculate_velocity_peaks(rows: list[list[float]], unit: str, limits: list[JointLimit]) -> list[PeakMetric]: + joint_count = len(rows[0]) - 1 + if joint_count != len(limits): + raise ValueError(f"Joint column count ({joint_count}) does not match robot limit count ({len(limits)}).") + + peaks: list[PeakMetric | None] = [None] * joint_count + + for row_index in range(1, len(rows)): + previous = rows[row_index - 1] + current = rows[row_index] + dt = current[0] - previous[0] + if dt <= 0.0: + raise ValueError(f"Non-positive dt at line {row_index + 1}: {dt}") + + for joint_index in range(joint_count): + dq_native = current[joint_index + 1] - previous[joint_index + 1] + dq_rad = to_radians(dq_native, unit) + velocity_rad = dq_rad / dt + velocity_native = to_native_from_rad(velocity_rad, unit) + + candidate = PeakMetric( + joint_name=limits[joint_index].name, + axis_index=joint_index + 1, + window_start=previous[0], + window_end=current[0], + row_number=row_index + 1, + metric_native=velocity_native, + metric_rad=velocity_rad, + effective_limit_rad=limits[joint_index].velocity, + ) + + current_peak = peaks[joint_index] + if current_peak is None or abs(candidate.metric_rad) > abs(current_peak.metric_rad): + peaks[joint_index] = candidate + + return [peak for peak in peaks if peak is not None] + + +def calculate_acceleration_peaks(rows: list[list[float]], unit: str, limits: list[JointLimit]) -> list[PeakMetric]: + joint_count = len(rows[0]) - 1 + if joint_count != len(limits): + raise ValueError(f"Joint column count ({joint_count}) does not match robot limit count ({len(limits)}).") + + velocities_rad: list[list[float]] = [] + velocity_windows: list[tuple[float, float, int]] = [] + + for row_index in range(1, len(rows)): + previous = rows[row_index - 1] + current = rows[row_index] + dt = current[0] - previous[0] + if dt <= 0.0: + raise ValueError(f"Non-positive dt at line {row_index + 1}: {dt}") + + velocity_row = [] + for joint_index in range(joint_count): + dq_native = current[joint_index + 1] - previous[joint_index + 1] + dq_rad = to_radians(dq_native, unit) + velocity_row.append(dq_rad / dt) + + velocities_rad.append(velocity_row) + velocity_windows.append((previous[0], current[0], row_index + 1)) + + peaks: list[PeakMetric | None] = [None] * joint_count + + for velocity_index in range(1, len(velocities_rad)): + dt = velocity_windows[velocity_index][1] - velocity_windows[velocity_index][0] + for joint_index in range(joint_count): + acceleration_rad = (velocities_rad[velocity_index][joint_index] - velocities_rad[velocity_index - 1][joint_index]) / dt + acceleration_native = to_native_from_rad(acceleration_rad, unit) + + candidate = PeakMetric( + joint_name=limits[joint_index].name, + axis_index=joint_index + 1, + window_start=velocity_windows[velocity_index][0], + window_end=velocity_windows[velocity_index][1], + row_number=velocity_windows[velocity_index][2], + metric_native=acceleration_native, + metric_rad=acceleration_rad, + effective_limit_rad=limits[joint_index].acceleration, + ) + + current_peak = peaks[joint_index] + if current_peak is None or abs(candidate.metric_rad) > abs(current_peak.metric_rad): + peaks[joint_index] = candidate + + return [peak for peak in peaks if peak is not None] + + +def calculate_jerk_peaks(rows: list[list[float]], unit: str, limits: list[JointLimit]) -> list[PeakMetric]: + joint_count = len(rows[0]) - 1 + if joint_count != len(limits): + raise ValueError(f"Joint column count ({joint_count}) does not match robot limit count ({len(limits)}).") + + velocities_rad: list[list[float]] = [] + velocity_windows: list[tuple[float, float, int]] = [] + for row_index in range(1, len(rows)): + previous = rows[row_index - 1] + current = rows[row_index] + dt = current[0] - previous[0] + if dt <= 0.0: + raise ValueError(f"Non-positive dt at line {row_index + 1}: {dt}") + + velocity_row = [] + for joint_index in range(joint_count): + dq_native = current[joint_index + 1] - previous[joint_index + 1] + dq_rad = to_radians(dq_native, unit) + velocity_row.append(dq_rad / dt) + + velocities_rad.append(velocity_row) + velocity_windows.append((previous[0], current[0], row_index + 1)) + + accelerations_rad: list[list[float]] = [] + acceleration_windows: list[tuple[float, float, int]] = [] + for velocity_index in range(1, len(velocities_rad)): + dt = velocity_windows[velocity_index][1] - velocity_windows[velocity_index][0] + acceleration_row = [] + for joint_index in range(joint_count): + acceleration_row.append((velocities_rad[velocity_index][joint_index] - velocities_rad[velocity_index - 1][joint_index]) / dt) + accelerations_rad.append(acceleration_row) + acceleration_windows.append((velocity_windows[velocity_index][0], velocity_windows[velocity_index][1], velocity_windows[velocity_index][2])) + + peaks: list[PeakMetric | None] = [None] * joint_count + + for acceleration_index in range(1, len(accelerations_rad)): + dt = acceleration_windows[acceleration_index][1] - acceleration_windows[acceleration_index][0] + for joint_index in range(joint_count): + jerk_rad = (accelerations_rad[acceleration_index][joint_index] - accelerations_rad[acceleration_index - 1][joint_index]) / dt + jerk_native = to_native_from_rad(jerk_rad, unit) + + candidate = PeakMetric( + joint_name=limits[joint_index].name, + axis_index=joint_index + 1, + window_start=acceleration_windows[acceleration_index][0], + window_end=acceleration_windows[acceleration_index][1], + row_number=acceleration_windows[acceleration_index][2], + metric_native=jerk_native, + metric_rad=jerk_rad, + effective_limit_rad=limits[joint_index].jerk, + ) + + current_peak = peaks[joint_index] + if current_peak is None or abs(candidate.metric_rad) > abs(current_peak.metric_rad): + peaks[joint_index] = candidate + + return [peak for peak in peaks if peak is not None] + + +def format_table(peaks: list[PeakMetric], native_unit: str, rad_unit: str, limit_header: str) -> str: + lines = [ + f"{'Joint':<8} {'Window(s)':<20} {'Line':>6} {'Peak(' + native_unit + ')':>18} {'Peak(' + rad_unit + ')':>18} {limit_header:>20} {'Ratio':>10}", + "-" * 108, + ] + for peak in peaks: + lines.append( + f"{peak.joint_name:<8} " + f"{peak.window_start:>7.6f}->{peak.window_end:<10.6f} " + f"{peak.row_number:>6} " + f"{peak.metric_native:>18.6f} " + f"{peak.metric_rad:>18.6f} " + f"{peak.effective_limit_rad:>20.6f} " + f"{peak.ratio_vs_limit:>10.4f}" + ) + return "\n".join(lines) + + +def print_metric_section(title: str, peaks: list[PeakMetric], unit: str, native_suffix: str, rad_suffix: str, limit_header: str) -> None: + print(build_metric_section(title, peaks, unit, native_suffix, rad_suffix, limit_header)) + + +def build_metric_section(title: str, peaks: list[PeakMetric], unit: str, native_suffix: str, rad_suffix: str, limit_header: str) -> str: + native_unit = f"deg/{native_suffix}" if unit == "deg" else f"rad/{native_suffix}" + rad_unit = f"rad/{rad_suffix}" + lines = [title, format_table(peaks, native_unit, rad_unit, limit_header)] + worst = max(peaks, key=lambda item: item.ratio_vs_limit) + metric_key = title.lower().replace(" ", "_") + lines.append( + f"worst_{metric_key}=" + f"{worst.joint_name}, window={worst.window_start:.6f}->{worst.window_end:.6f}, " + f"peak_rad={worst.metric_rad:.6f}, limit_rad={worst.effective_limit_rad:.6f}, " + f"ratio={worst.ratio_vs_limit:.4f}" + ) + return "\n".join(lines) + + +def build_report_text( + joint_detail_path: Path, + rows: list[list[float]], + unit: str, + max_abs_joint: float, + limit_source_text: str, + trim_note: str | None, + velocity_peaks: list[PeakMetric], + acceleration_peaks: list[PeakMetric], + jerk_peaks: list[PeakMetric], +) -> str: + lines = [ + f"joint_detail={joint_detail_path}", + limit_source_text, + f"row_count={len(rows)}", + f"joint_count={len(rows[0]) - 1}", + f"inferred_unit={unit}", + f"max_abs_joint_value={max_abs_joint:.6f}", + ] + if trim_note is not None: + lines.append(trim_note) + lines.extend( + [ + "", + build_metric_section("Velocity Peaks", velocity_peaks, unit, "s", "s", "VelLimit(rad/s)"), + "", + build_metric_section("Acceleration Peaks", acceleration_peaks, unit, "s^2", "s^2", "AccLimit(rad/s^2)"), + "", + build_metric_section("Jerk Peaks", jerk_peaks, unit, "s^3", "s^3", "JerkLimit(rad/s^3)"), + "", + ] + ) + return "\n".join(lines) + + +def main() -> int: + args = parse_args() + joint_detail_path = resolve_path(args.joint_detail) + rows = read_joint_rows(joint_detail_path) + limits_info = load_effective_limits(args.limit_csv) + rows, trim_note = trim_rows_to_limit_count(rows, len(limits_info.joints)) + unit = infer_unit(rows, args.unit) + + velocity_peaks = calculate_velocity_peaks(rows, unit, limits_info.joints) + acceleration_peaks = calculate_acceleration_peaks(rows, unit, limits_info.joints) + jerk_peaks = calculate_jerk_peaks(rows, unit, limits_info.joints) + + max_abs_joint = max(abs(value) for row in rows for value in row[1:]) + + if args.limit_csv is None: + limit_source_text = "limit_source=built-in fixed effective limits (acc_limit=1, jerk_limit=1)" + else: + limit_source_text = f"limit_source_csv={resolve_path(args.limit_csv)}" + + report_text = build_report_text( + joint_detail_path=joint_detail_path, + rows=rows, + unit=unit, + max_abs_joint=max_abs_joint, + limit_source_text=limit_source_text, + trim_note=trim_note, + velocity_peaks=velocity_peaks, + acceleration_peaks=acceleration_peaks, + jerk_peaks=jerk_peaks, + ) + + print(report_text, end="") + + output_path = joint_detail_path.with_suffix(".analysis.txt") + try: + output_path.write_text(report_text, encoding="utf-8") + print(f"saved_report={output_path}") + except PermissionError as error: + print(f"save_report_failed={output_path}") + print(f"save_report_error={error}") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/analysis/fanuc_10010_dissector.lua b/analysis/fanuc_10010_dissector.lua new file mode 100644 index 0000000..76f2e78 --- /dev/null +++ b/analysis/fanuc_10010_dissector.lua @@ -0,0 +1,199 @@ +local plugin_info = { + version = "1.1.0", + author = "OpenAI Codex", + description = "FANUC TCP 10010 状态帧解析器", +} + +set_plugin_info(plugin_info) + +local fanuc10010_proto = Proto("fanuc10010_state_frame", "FANUC 10010 State Frame") + +local FRAME_LENGTH = 90 +local HEADER_LENGTH = 3 +local TRAILER_LENGTH = 3 +local LENGTH_OFFSET = 3 +local MESSAGE_ID_OFFSET = 7 +local POSE_OFFSET = 11 +local POSE_COUNT = 6 +local JOINT_OFFSET = 35 +local JOINT_COUNT = 9 +local TAIL_OFFSET = 71 +local TAIL_COUNT = 4 + +local POSE_NAMES = { "X", "Y", "Z", "W", "P", "R" } +local JOINT_NAMES = { "J1", "J2", "J3", "J4", "J5", "J6", "Ext1", "Ext2", "Ext3" } +local HEADER_MAGIC = "doz" +local TRAILER_MAGIC = "zod" + +local fields = { + frame = ProtoField.bytes("fanuc10010.frame", "原始状态帧"), + header = ProtoField.string("fanuc10010.header", "帧头 Magic"), + declared_length = ProtoField.uint32("fanuc10010.length", "声明长度", base.DEC), + message_id = ProtoField.uint32("fanuc10010.message_id", "消息号", base.DEC), + pose = ProtoField.none("fanuc10010.pose", "笛卡尔位姿"), + joints = ProtoField.none("fanuc10010.joints", "关节与扩展轴"), + joint_degrees = ProtoField.none("fanuc10010.joint_degrees", "Joint Degrees"), + tail = ProtoField.none("fanuc10010.tail", "尾部状态字"), + trailer = ProtoField.string("fanuc10010.trailer", "帧尾 Magic"), + expert_bad_length = ProtoField.string("fanuc10010.expert.bad_length", "长度异常"), + expert_bad_magic = ProtoField.string("fanuc10010.expert.bad_magic", "Magic 异常"), +} + +for index = 1, POSE_COUNT do + fields["pose_" .. index] = ProtoField.float( + "fanuc10010.pose_" .. index, + "Pose " .. POSE_NAMES[index]) +end + +for index = 1, JOINT_COUNT do + fields["joint_" .. index] = ProtoField.float( + "fanuc10010.joint_" .. index, + JOINT_NAMES[index] .. " (raw)") +end + +for index = 1, 6 do + fields["joint_deg_" .. index] = ProtoField.float( + "fanuc10010.joint_deg_" .. index, + JOINT_NAMES[index] .. " (deg)") +end + +for index = 1, TAIL_COUNT do + fields["tail_" .. index] = ProtoField.uint32( + "fanuc10010.tail_" .. index, + "Tail[" .. (index - 1) .. "]", + base.DEC) +end + +fanuc10010_proto.fields = fields + +local function read_f32_be(tvb, offset) + return tvb(offset, 4):tvb():range(0, 4):float() +end + +local function read_u32_be(tvb, offset) + return tvb(offset, 4):uint() +end + +local function read_ascii(tvb, offset, length) + return tvb(offset, length):string() +end + +local function radians_to_degrees(value) + return value * 180.0 / math.pi +end + +local function add_error(subtree, range, field, message) + local item = subtree:add(field, range, message) + item:add_expert_info(PI_MALFORMED, PI_ERROR, message) +end + +local function dissect_single_frame(tvb, pinfo, tree, frame_offset) + local remaining = tvb:len() - frame_offset + local candidate_length = math.min(remaining, FRAME_LENGTH) + local frame_range = tvb(frame_offset, candidate_length) + local subtree = tree:add(fanuc10010_proto, frame_range, "FANUC 10010 状态帧") + subtree:add(fields.frame, frame_range) + + if remaining < FRAME_LENGTH then + add_error(subtree, frame_range, fields.expert_bad_length, "剩余字节不足 90B,无法组成完整状态帧") + return remaining + end + + local header = read_ascii(tvb, frame_offset, HEADER_LENGTH) + local declared_length = read_u32_be(tvb, frame_offset + LENGTH_OFFSET) + local message_id = read_u32_be(tvb, frame_offset + MESSAGE_ID_OFFSET) + local trailer = read_ascii(tvb, frame_offset + FRAME_LENGTH - TRAILER_LENGTH, TRAILER_LENGTH) + + subtree:add(fields.header, tvb(frame_offset, HEADER_LENGTH), header) + subtree:add(fields.declared_length, tvb(frame_offset + LENGTH_OFFSET, 4), declared_length) + subtree:add(fields.message_id, tvb(frame_offset + MESSAGE_ID_OFFSET, 4), message_id) + + local pose_tree = subtree:add(fields.pose, tvb(frame_offset + POSE_OFFSET, POSE_COUNT * 4)) + local pose_values = {} + for index = 1, POSE_COUNT do + local field_offset = frame_offset + POSE_OFFSET + ((index - 1) * 4) + local value = read_f32_be(tvb, field_offset) + pose_values[index] = value + pose_tree:add(fields["pose_" .. index], tvb(field_offset, 4), value) + end + + local joint_tree = subtree:add(fields.joints, tvb(frame_offset + JOINT_OFFSET, JOINT_COUNT * 4)) + local joint_values = {} + for index = 1, JOINT_COUNT do + local field_offset = frame_offset + JOINT_OFFSET + ((index - 1) * 4) + local value = read_f32_be(tvb, field_offset) + joint_values[index] = value + joint_tree:add(fields["joint_" .. index], tvb(field_offset, 4), value) + end + + -- 单独保留一组角度显示,便于对照原始关节弧度值。 + local joint_degree_tree = subtree:add(fields.joint_degrees, tvb(frame_offset + JOINT_OFFSET, 6 * 4)) + for index = 1, 6 do + local field_offset = frame_offset + JOINT_OFFSET + ((index - 1) * 4) + joint_degree_tree:add( + fields["joint_deg_" .. index], + tvb(field_offset, 4), + radians_to_degrees(joint_values[index])) + end + + local tail_tree = subtree:add(fields.tail, tvb(frame_offset + TAIL_OFFSET, TAIL_COUNT * 4)) + local tail_values = {} + for index = 1, TAIL_COUNT do + local field_offset = frame_offset + TAIL_OFFSET + ((index - 1) * 4) + local value = read_u32_be(tvb, field_offset) + tail_values[index] = value + tail_tree:add(fields["tail_" .. index], tvb(field_offset, 4), value) + end + + subtree:add(fields.trailer, tvb(frame_offset + FRAME_LENGTH - TRAILER_LENGTH, TRAILER_LENGTH), trailer) + + if header ~= HEADER_MAGIC then + add_error(subtree, tvb(frame_offset, HEADER_LENGTH), fields.expert_bad_magic, "帧头不是 doz") + end + + if trailer ~= TRAILER_MAGIC then + add_error(subtree, tvb(frame_offset + FRAME_LENGTH - TRAILER_LENGTH, TRAILER_LENGTH), fields.expert_bad_magic, "帧尾不是 zod") + end + + if declared_length ~= FRAME_LENGTH then + add_error(subtree, tvb(frame_offset + LENGTH_OFFSET, 4), fields.expert_bad_length, "长度字段不是 90") + end + + local summary = string.format( + "MsgId=%u X=%.3f Y=%.3f Z=%.3f J1=%.6f rad / %.3f deg J2=%.6f rad / %.3f deg Tail=[%u,%u,%u,%u]", + message_id, + pose_values[1], pose_values[2], pose_values[3], + joint_values[1], radians_to_degrees(joint_values[1]), + joint_values[2], radians_to_degrees(joint_values[2]), + tail_values[1], tail_values[2], tail_values[3], tail_values[4]) + + subtree:set_text("FANUC 10010 状态帧, " .. summary) + pinfo.cols.info:append(" | " .. summary) + + return FRAME_LENGTH +end + +function fanuc10010_proto.dissector(tvb, pinfo, tree) + if tvb:len() == 0 then + return 0 + end + + pinfo.cols.protocol = "FANUC10010" + + local offset = 0 + while offset < tvb:len() do + local consumed = dissect_single_frame(tvb, pinfo, tree, offset) + if consumed <= 0 then + break + end + + offset = offset + consumed + if consumed < FRAME_LENGTH then + break + end + end + + return tvb:len() +end + +DissectorTable.get("tcp.port"):add(10010, fanuc10010_proto) diff --git a/analysis/read_fanuc_allowable_limits.py b/analysis/read_fanuc_allowable_limits.py new file mode 100644 index 0000000..6b8e2b7 --- /dev/null +++ b/analysis/read_fanuc_allowable_limits.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +"""读取 FANUC J519 允许速度/加速度/jerk 上限表。 + +本脚本只做 UDP packet type=3 查询,不启动运行时服务,也不写入机器人参数。 +响应中的 limit 值保留为控制器原始单位:速度 deg/s、加速度 deg/s^2、jerk deg/s^3。 +""" + +from __future__ import annotations + +import argparse +import csv +import socket +import struct +import sys +from dataclasses import dataclass +from pathlib import Path + + +LIMIT_TYPES = { + 0: ("velocity", "deg/s"), + 1: ("acceleration", "deg/s^2"), + 2: ("jerk", "deg/s^3"), +} + + +@dataclass(frozen=True) +class LimitTable: + """保存单个轴、单种限制类型的 20 档 no-payload / max-payload 上限表。""" + + axis: int + limit_type: int + vmax_mm_s: int + intermediate_check_s: int + no_payload: tuple[float, ...] + max_payload: tuple[float, ...] + + +def pack_request(axis: int, limit_type: int) -> bytes: + """按手册 Table 5 封装允许上限表查询请求。""" + + return struct.pack(">IIII", 3, 1, axis, limit_type) + + +def parse_response(packet: bytes, expected_axis: int, expected_type: int) -> LimitTable: + """按手册 Table 6 解析允许上限表响应。""" + + if len(packet) != 184: + raise ValueError(f"响应长度应为 184 字节,实际为 {len(packet)} 字节。") + + packet_type, version, axis, limit_type, vmax, check_time = struct.unpack_from(">IIIIII", packet, 0) + if packet_type != 3 or version != 1: + raise ValueError(f"响应头不正确:packet_type={packet_type}, version={version}。") + if axis != expected_axis or limit_type != expected_type: + raise ValueError( + f"响应与请求不匹配:期望 axis={expected_axis}, type={expected_type}," + f"实际 axis={axis}, type={limit_type}。" + ) + + values = struct.unpack_from(">40f", packet, 24) + return LimitTable( + axis=axis, + limit_type=limit_type, + vmax_mm_s=vmax, + intermediate_check_s=check_time, + no_payload=tuple(values[:20]), + max_payload=tuple(values[20:]), + ) + + +def query_table(sock: socket.socket, robot_ip: str, port: int, axis: int, limit_type: int) -> LimitTable: + """发送单个查询请求并等待对应响应。""" + + sock.sendto(pack_request(axis, limit_type), (robot_ip, port)) + packet, address = sock.recvfrom(2048) + if address[0] != robot_ip: + raise ValueError(f"收到非目标机器人响应:{address[0]}:{address[1]}。") + return parse_response(packet, axis, limit_type) + + +def iter_requested_tables( + robot_ip: str, + port: int, + timeout_s: float, + axes: range, + limit_types: tuple[int, ...], +) -> list[LimitTable]: + """按轴和限制类型顺序读取所有上限表。""" + + tables: list[LimitTable] = [] + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.settimeout(timeout_s) + for axis in axes: + for limit_type in limit_types: + tables.append(query_table(sock, robot_ip, port, axis, limit_type)) + return tables + + +def write_csv(path: Path, tables: list[LimitTable]) -> None: + """把 20 档速度区间和两套 payload 表写成扁平 CSV。""" + + with path.open("w", newline="", encoding="utf-8") as file: + writer = csv.writer(file) + writer.writerow( + [ + "axis", + "limit_type", + "unit", + "vmax_mm_s", + "intermediate_check_s", + "speed_bin_index", + "speed_bin_upper_mm_s", + "no_payload_limit", + "max_payload_limit", + ] + ) + for table in tables: + limit_name, unit = LIMIT_TYPES[table.limit_type] + for index, (no_payload, max_payload) in enumerate(zip(table.no_payload, table.max_payload), start=1): + writer.writerow( + [ + table.axis, + limit_name, + unit, + table.vmax_mm_s, + table.intermediate_check_s, + index, + table.vmax_mm_s * index / 20.0, + f"{no_payload:.9g}", + f"{max_payload:.9g}", + ] + ) + + +def print_summary(tables: list[LimitTable]) -> None: + """打印每张表的首末档,便于现场快速确认返回值是否正常。""" + + for table in tables: + limit_name, unit = LIMIT_TYPES[table.limit_type] + print( + f"axis={table.axis} type={limit_name:<12} unit={unit:<8} " + f"vmax={table.vmax_mm_s}mm/s check={table.intermediate_check_s}s " + f"no_payload[1]={table.no_payload[0]:.6g} no_payload[20]={table.no_payload[-1]:.6g} " + f"max_payload[1]={table.max_payload[0]:.6g} max_payload[20]={table.max_payload[-1]:.6g}" + ) + + +def parse_args() -> argparse.Namespace: + """解析命令行参数。""" + + parser = argparse.ArgumentParser( + description="Read FANUC J519 packet type=3 allowable upper limit tables.", + ) + parser.add_argument("robot_ip", help="机器人控制柜 IP,例如 192.168.10.11。") + parser.add_argument("--port", type=int, default=60015, help="J519 UDP 端口,默认 60015。") + parser.add_argument("--timeout", type=float, default=2.0, help="单次响应超时秒数,默认 2.0。") + parser.add_argument("--axis-start", type=int, default=1, help="起始轴号,默认 1。") + parser.add_argument("--axis-end", type=int, default=9, help="结束轴号,默认 9。") + parser.add_argument( + "--types", + default="0,1,2", + help="限制类型列表:0=velocity, 1=acceleration, 2=jerk;默认 0,1,2。", + ) + parser.add_argument( + "--csv", + type=Path, + default=Path("analysis/fanuc_allowable_limit_tables.csv"), + help="CSV 输出路径。", + ) + return parser.parse_args() + + +def main() -> int: + """脚本入口。""" + + args = parse_args() + limit_types = tuple(int(item.strip()) for item in args.types.split(",") if item.strip()) + unknown_types = [item for item in limit_types if item not in LIMIT_TYPES] + if unknown_types: + print(f"未知限制类型:{unknown_types}", file=sys.stderr) + return 2 + if not 1 <= args.axis_start <= args.axis_end <= 9: + print("轴范围必须满足 1 <= axis-start <= axis-end <= 9。", file=sys.stderr) + return 2 + + tables = iter_requested_tables( + args.robot_ip, + args.port, + args.timeout, + range(args.axis_start, args.axis_end + 1), + limit_types, + ) + args.csv.parent.mkdir(parents=True, exist_ok=True) + write_csv(args.csv, tables) + print_summary(tables) + print(f"\nCSV 已写入:{args.csv}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/analysis/segment_02_acc_jerk_summary.csv b/analysis/segment_02_acc_jerk_summary.csv new file mode 100644 index 0000000..84c4d62 --- /dev/null +++ b/analysis/segment_02_acc_jerk_summary.csv @@ -0,0 +1,7 @@ +Axis,AccPeakRadPerS2,AccLimitRadPerS2,AccRatio,AccWindowStartS,AccWindowEndS,AccLine,JerkPeakRadPerS3,JerkLimitRadPerS3,JerkRatio,JerkWindowStartS,JerkWindowEndS,JerkLine +Joint1,16.638678,26.900000,0.618538,0.128012,0.135939,18,902.687973,224.220000,4.025903,6.312082,6.320127,791 +Joint2,14.521836,22.540000,0.644270,3.088013,3.096086,388,888.335197,187.860000,4.728709,2.904052,2.912069,365 +Joint3,14.267221,29.810000,0.478605,0.128012,0.135939,18,728.505873,248.460000,2.932085,0.135939,0.143989,19 +Joint4,-34.694506,39.990000,0.867580,6.832125,6.840105,856,-2222.596524,333.300000,6.668456,6.312082,6.320127,791 +Joint5,-16.329775,39.630000,0.412056,6.840105,6.848111,857,842.738923,330.270000,2.551667,6.936077,6.944096,869 +Joint6,34.766065,72.720000,0.478081,1.392021,1.399995,176,2678.050822,606.010000,4.419153,6.312082,6.320127,791 diff --git a/docs/0.7x.pcap b/docs/0.7x.pcap new file mode 100644 index 0000000..a966039 Binary files /dev/null and b/docs/0.7x.pcap differ diff --git a/docs/2026042802-1-trigger-vs-teach-point-analysis.md b/docs/2026042802-1-trigger-vs-teach-point-analysis.md new file mode 100644 index 0000000..095c309 --- /dev/null +++ b/docs/2026042802-1-trigger-vs-teach-point-analysis.md @@ -0,0 +1,526 @@ +# 2026042802-1 抓包触发点与示教点对比记录 + +## 1. 目的 + +- 使用 `../Rvbust/uttc-20260428/2026042802-1.pcap` 确认当前仓库是否已经导出过这份抓包对应的真实运动轨迹发包记录。 +- 如果现有导出不包含 IO 触发信息,则补一份“真实 J519 发包 + IO 标记”导出。 +- 将抓包里真实触发 IO 的关节坐标,与 `Config/RobotConfig.json` 中 `flying_shots.UTTC_MS11` 的示教点逐点对比。 +- 把结论固定成仓库文档,后续继续和新程序抓包、`ShotEvents.json`、运行时导出结果做同口径比较。 + +## 2. 数据来源与口径 + +- 抓包文件:`../Rvbust/uttc-20260428/2026042802-1.pcap` +- 旧目录配置文件:`../Rvbust/uttc-20260428/RobotConfig.json` +- 新版程序运行时配置文件:`Config/RobotConfig.json` +- 对比对象:两份配置中的 `flying_shots.UTTC_MS11` +- 协议口径:只看 `192.168.10.10 -> 192.168.10.11` 的 `UDP 60015` 64B J519 命令帧 +- 触发判定:`write_io_value > 0` 视为 IO 置位 +- 去重规则:由于 `io_keep_cycles=2`,每次触发只记录首个高电平帧 +- 关节单位:抓包 J519 目标关节为 `deg`;`RobotConfig.json` `traj_waypoints` 为 `rad`,分析时先转成 `deg` + +补充说明: + +- `../Rvbust/uttc-20260428/` 目录中的内容代表旧抓包及其同目录资料。 +- `Config/Data/UTTC_MS11/` 目录中的内容代表现在新版程序的运行时导出。 +- 本文不会把两者混成同一次运行的数据;这里只拿新版程序目录里的文件做“现状参照”,核心分析对象仍然是旧抓包 `2026042802-1.pcap`。 + +## 3. 现状核对 + +### 3.1 仓库里已经存在什么 + +- 旧抓包目录已有原始抓包:`../Rvbust/uttc-20260428/2026042802-1.pcap` +- 旧抓包目录已有按运动段拆分的导出:`../Rvbust/uttc-20260428/2026042802-1_joint_segments/` +- 旧抓包目录已有速度档位整理结果:`../Rvbust/uttc-20260428/1倍速度 角度坐标点/真实轨迹JointDetialTraj.txt` +- 新版程序目录已有运行时导出:`Config/Data/UTTC_MS11/ActualSendJointTraj.txt` +- 新版程序目录已有运行时导出:`Config/Data/UTTC_MS11/ActualSendTiming.txt` +- 新版程序目录已有理论触发时间轴:`Config/Data/UTTC_MS11/ShotEvents.json` + +### 3.2 当前缺的是什么 + +- 新版程序目录中的 `ActualSendJointTraj.txt` 是现在新版程序落盘的真实发送关节轨迹,但不带 IO 列,也不属于 `2026042802-1.pcap` 这次旧抓包本身。 +- 旧抓包目录中的 `2026042802-1_joint_segments/` 是按运动段拆开的 `JointDetialTraj.txt` 风格导出,也不包含整条抓包的 IO 标记。 +- 旧抓包目录中的 `1倍速度 角度坐标点/真实轨迹JointDetialTraj.txt` 提供了真实轨迹文本,但同样不带 IO 触发列。 +- 本次分析前,没有发现一份专门对应 `2026042802-1.pcap` 的“整条真实 J519 发包记录 + IO 标记 + 触发帧筛选”组合导出。 + +结论: +旧抓包目录和新版程序目录里都已经有各自的轨迹资料,但在本次分析前,还没有一份专门对应 `2026042802-1.pcap` 的、带 IO 触发信息的整条实际发包记录。 + +## 4. 本次新增导出 + +本次补充生成了以下文件: + +- `analysis/2026042802-1/2026042802-1_j519_actual_send_all_with_io.csv` + - `1788` 条客户端真实下发 J519 命令 + - 包含 `frame_number / time_relative_s / sequence / write_io_value / io_addrs / j1..j6` +- `analysis/2026042802-1/2026042802-1_j519_trigger_frames.csv` + - 只保留每次 IO 高电平脉冲的第一帧 + - 共 `17` 条,和 `ShotEvents.json` 的触发次数一致 +- `analysis/2026042802-1/2026042802-1_trigger_vs_teach_points.csv` + - 每个真实触发帧与 `UTTC_MS11` 示教点的逐点偏差表 +- `analysis/2026042802-1/2026042802-1_analysis_summary.json` + - 本文档引用的汇总统计 + +对应复现脚本: + +- `analysis/analyze_2026042802_1_trigger_vs_teach_points.py` + +## 5. 触发与地址核对 + +- 抓包提取到的真实触发次数:`17` +- `ShotEvents.json` 中的触发次数:`17` +- `write_io_value` 解码出的 `io_addrs` 与 `UTTC_MS11.addr` 没有真实内容不一致 +- 其中有 `14` 处列表顺序不同,例如抓包里是 `[2, 3, 4]`,配置里写的是 `[3, 4, 2]` +- 这 `14` 处只是顺序差异,不是触发地址集合差异 +- 真正的地址集合不匹配数:`0` + +结论: +`2026042802-1.pcap` 中真实发出的 IO 地址集合,与 `UTTC_MS11` 配置的 `addr` 集合是一致的。 + +## 6. 触发点与示教点误差统计 + +- 平均单点最大单轴误差:`4.241584 deg` +- 最大单轴误差:`11.130744 deg` +- 平均 RMS 误差:`2.540614 deg` +- 最大误差轴分布:`J6=9`、`J4=5`、`J5=1`、`J2=1`、`J1=1` + +这说明: + +- 这份旧抓包里的真实触发点,并不是严格贴在配置示教点上触发 +- 误差主要集中在 `J6` 和 `J4` +- 由于 `../Rvbust/uttc-20260428/RobotConfig.json` 和当前仓库 `Config/RobotConfig.json` 的 `UTTC_MS11` 四组关键字段完全一致,因此这里的“示教点”对旧抓包和当前配置是同一组数据 +- 如果用户的“对焦准确示教点”就是这组 `traj_waypoints`,那么旧程序实际触发时已经存在几度量级的偏移,而不是亚角度级别的完全重合 + +## 7. 最大偏差点 + +按最大单轴误差从大到小列出前 5 个触发点: + +| 触发序号 | waypoint_index | 实际 io_addrs | 最大误差轴 | 最大误差(deg) | RMS误差(deg) | 主要差值 | +| --- | --- | --- | --- | ---: | ---: | --- | +| 3 | 3 | `[2, 3, 4]` | `J6` | `11.130744` | `5.955608` | `J4 +8.811537`,`J6 -11.130744` | +| 7 | 7 | `[3, 4]` | `J6` | `7.194107` | `4.618545` | `J2 -5.441249`,`J4 -6.792278`,`J6 +7.194107` | +| 17 | 18 | `[3, 4]` | `J4` | `6.848522` | `4.036140` | `J2 +1.835940`,`J4 -6.848522`,`J6 +6.320424` | +| 13 | 13 | `[3, 4]` | `J1` | `6.656209` | `4.109282` | `J1 +6.656209`,`J3 -3.848692`,`J4 +3.795032` | +| 8 | 8 | `[2, 4]` | `J6` | `5.939884` | `3.533133` | `J2 -2.961069`,`J4 -5.456660`,`J6 +5.939884` | + +## 8. 逐点结果 + +完整逐点数据已写入: + +- `analysis/2026042802-1/2026042802-1_trigger_vs_teach_points.csv` + +这里保留简表,方便直接看每次触发的最大偏差: + +| 触发序号 | waypoint_index | frame | seq | time(s) | io_addrs | 最大误差轴 | 最大误差(deg) | RMS误差(deg) | +| --- | --- | ---: | ---: | ---: | --- | --- | ---: | ---: | +| 1 | 1 | 1955 | 1381124 | 5.888271 | `[2, 4]` | `J6` | `3.096909` | `2.341857` | +| 2 | 2 | 2151 | 1381198 | 6.480252 | `[2, 3, 4]` | `J4` | `2.737488` | `1.612807` | +| 3 | 3 | 2223 | 1381226 | 6.704225 | `[2, 3, 4]` | `J6` | `11.130744` | `5.955608` | +| 4 | 4 | 2336 | 1381269 | 7.048266 | `[2, 4]` | `J6` | `2.373383` | `1.439206` | +| 5 | 5 | 2477 | 1381321 | 7.464289 | `[2, 4]` | `J5` | `1.973330` | `1.273254` | +| 6 | 6 | 2665 | 1381391 | 8.024277 | `[3, 4]` | `J2` | `1.265078` | `0.786940` | +| 7 | 7 | 2811 | 1381447 | 8.472317 | `[3, 4]` | `J6` | `7.194107` | `4.618545` | +| 8 | 8 | 2908 | 1381483 | 8.760361 | `[2, 4]` | `J6` | `5.939884` | `3.533133` | +| 9 | 9 | 3112 | 1381561 | 9.384332 | `[2, 4]` | `J4` | `2.414749` | `1.565121` | +| 10 | 10 | 3170 | 1381583 | 9.560358 | `[2, 4]` | `J4` | `3.629631` | `2.268783` | +| 11 | 11 | 3350 | 1381651 | 10.104322 | `[2, 4]` | `J6` | `3.113037` | `2.115595` | +| 12 | 12 | 3470 | 1381697 | 10.472333 | `[2, 4]` | `J6` | `4.018547` | `2.003201` | +| 13 | 13 | 3627 | 1381756 | 10.944371 | `[3, 4]` | `J1` | `6.656209` | `4.109282` | +| 14 | 14 | 3710 | 1381787 | 11.192339 | `[2, 4]` | `J6` | `2.834801` | `1.514764` | +| 15 | 15 | 3796 | 1381820 | 11.456361 | `[2, 4]` | `J4` | `2.496544` | `1.640818` | +| 16 | 16 | 3870 | 1381848 | 11.680362 | `[2, 4]` | `J6` | `3.782349` | `2.176883` | +| 17 | 18 | 4138 | 1381950 | 12.496365 | `[3, 4]` | `J4` | `6.848522` | `4.036140` | + +## 9. UTTC_MS11 示教点角度表 + +为了便于和导出的触发序列直接手工比对,下面把当前 `UTTC_MS11` 示教点从 `rad` 转成 `deg`。 + +说明: + +- `waypoint_index` 使用配置里的原始下标。 +- `shot_flag=true` 表示该点在配置里是拍照点候选。 +- `addr` 保留原配置里的触发地址列表,便于和 `io_addrs` 对照。 + +| waypoint_index | shot_flag | addr | J1(deg) | J2(deg) | J3(deg) | J4(deg) | J5(deg) | J6(deg) | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 0 | `false` | `[]` | 60.546226 | 0.668344 | -1.025155 | -0.869105 | 1.231405 | 0.548197 | +| 1 | `true` | `[2, 4]` | 48.886810 | 2.198985 | -11.021017 | 0.410210 | 6.248381 | 2.294991 | +| 2 | `true` | `[3, 4, 2]` | 55.347755 | 11.807040 | -7.009095 | -71.014331 | 6.012065 | 74.249532 | +| 3 | `true` | `[3, 4, 2]` | 55.109808 | 8.759497 | -8.518217 | -41.117250 | 10.108488 | 41.956933 | +| 4 | `true` | `[4, 2]` | 43.653593 | -1.629660 | -17.715754 | 5.995209 | 32.171718 | -22.573973 | +| 5 | `true` | `[4, 2]` | 64.582445 | 4.262979 | -15.669217 | -29.952927 | 29.850440 | 45.626527 | +| 6 | `true` | `[3, 4]` | 60.479483 | 23.068782 | -5.011264 | 36.106855 | 5.525681 | -31.302564 | +| 7 | `true` | `[3, 4]` | 70.475837 | 16.393849 | -13.456948 | -27.892319 | 14.535662 | 31.711933 | +| 8 | `true` | `[4, 2]` | 69.582464 | -17.105713 | -8.710590 | -58.475694 | 7.630590 | 64.437733 | +| 9 | `true` | `[4, 2]` | 73.571259 | -6.429845 | -9.628580 | -128.808647 | 14.671083 | 140.002048 | +| 10 | `true` | `[4, 2]` | 75.569386 | -14.679306 | -7.294156 | -130.923033 | 17.688361 | 141.893508 | +| 11 | `true` | `[4, 2]` | 86.093498 | -14.498333 | -13.681511 | -69.868296 | 26.742682 | 88.999414 | +| 12 | `true` | `[4, 2]` | 61.720733 | -4.232789 | -9.784423 | -108.384083 | 22.160772 | 118.142064 | +| 13 | `true` | `[4, 3]` | 79.757083 | 4.640231 | -15.311186 | -56.035312 | 26.133138 | 52.152257 | +| 14 | `true` | `[4, 2]` | 108.955551 | 1.370760 | -33.594425 | -48.367361 | 43.963404 | 85.039261 | +| 15 | `true` | `[4, 2]` | 110.584848 | -3.860900 | -32.396407 | -51.112591 | 44.229160 | 87.624000 | +| 16 | `true` | `[4, 2]` | 118.095952 | -17.376387 | -31.069001 | -59.560538 | 48.732576 | 94.134771 | +| 17 | `false` | `[4, 2]` | 62.573787 | -22.938069 | -10.333288 | 77.491373 | 35.583412 | -69.668648 | +| 18 | `true` | `[4, 3]` | 60.282482 | -22.938081 | -10.333248 | 77.491642 | 35.583378 | -69.668981 | +| 19 | `false` | `[]` | 60.546226 | 0.668344 | -1.025155 | -0.869105 | 1.231405 | 0.548197 | + +## 10. 结论 + +1. `../Rvbust/uttc-20260428/` 目录里已有旧抓包相关轨迹资料,`Config/Data/UTTC_MS11/` 目录里已有新版程序运行时导出,但在本次分析前,没有一份专门对应 `2026042802-1.pcap` 的“整条真实 J519 发包记录 + IO 标记”导出。 +2. 本次已经补齐该导出,文件位于 `analysis/2026042802-1/2026042802-1_j519_actual_send_all_with_io.csv`。 +3. `2026042802-1.pcap` 的真实 IO 触发次数是 `17`,与 `ShotEvents.json` 一致。 +4. 抓包中真实触发时使用的 IO 地址集合,与 `UTTC_MS11.addr` 一致;只有列表顺序差异,没有地址集合差异。 +5. 抓包中真实触发点相对示教点的平均最大单轴误差约 `4.24 deg`,最大达到 `11.13 deg`,主要偏在 `J6` 和 `J4`。 +6. 因此,如果后续要分析“为什么新程序拍照位置偏了”,不能把 `2026042802-1.pcap` 视为“与当前示教点完全重合的零误差基准”;它本身相对当前配置示教点就存在明显偏差。 + +## 11. 触发点前 6 个周期的实际关节坐标与示教点差值 + +在这份旧抓包里,17 个真实触发点对应的“最接近示教点”的实际采样点,统一出现在触发前 `6` 个周期,也就是前 `48ms`。 + +为了方便手工比对,这里把每个触发点前 `6` 个周期的实际关节坐标单独列出来,并和对应示教点逐轴做差。 + +说明: + +- `pre6_frame` / `pre6_time` 是触发帧前 `6` 个 `8ms` 周期的实际 J519 发包点。 +- `pre6_j*` 是该时刻的实际关节角度,单位 `deg`。 +- `diff_j*` 定义为 `pre6_actual - teach_point`,单位 `deg`。 +- 这张表更适合你直接和导出的触发序列逐点人工核对。 + +| 触发序号 | waypoint_index | pre6_frame | pre6_time(s) | pre6_J1 | pre6_J2 | pre6_J3 | pre6_J4 | pre6_J5 | pre6_J6 | diff_J1 | diff_J2 | diff_J3 | diff_J4 | diff_J5 | diff_J6 | max_abs_diff(deg) | rms_diff(deg) | +| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 1 | 1941 | 5.840207 | 49.036247 | 2.169637 | -10.893022 | 0.469577 | 6.183248 | 2.196172 | +0.149437 | -0.029348 | +0.127996 | +0.059368 | -0.065133 | -0.098818 | 0.149437 | 0.097560 | +| 2 | 2 | 2135 | 6.432175 | 55.402473 | 11.806953 | -6.980721 | -70.955170 | 6.007696 | 74.193436 | +0.054718 | -0.000086 | +0.028375 | +0.059162 | -0.004369 | -0.056096 | 0.059162 | 0.041764 | +| 3 | 3 | 2207 | 6.656264 | 55.185375 | 8.828770 | -8.461165 | -41.609406 | 9.991771 | 42.555542 | +0.075567 | +0.069272 | +0.057051 | -0.492155 | -0.116717 | +0.598609 | 0.598609 | 0.323506 | +| 4 | 4 | 2321 | 7.000291 | 43.639214 | -1.593877 | -17.673382 | 5.988685 | 32.060623 | -22.587208 | -0.014380 | +0.035782 | +0.042372 | -0.006524 | -0.111095 | -0.013234 | 0.111095 | 0.051385 | +| 5 | 5 | 2457 | 7.416267 | 64.456329 | 4.153820 | -15.749991 | -29.951208 | 30.008862 | 45.422455 | -0.126115 | -0.109159 | -0.080774 | +0.001719 | +0.158422 | -0.204073 | 0.204073 | 0.129802 | +| 6 | 6 | 2649 | 7.976283 | 60.479656 | 23.070845 | -5.011842 | 36.106964 | 5.525600 | -31.304708 | +0.000174 | +0.002063 | -0.000578 | +0.000109 | -0.000081 | -0.002145 | 0.002145 | 0.001241 | +| 7 | 7 | 2795 | 8.424315 | 70.489815 | 16.270372 | -13.455009 | -28.079985 | 14.531018 | 31.910471 | +0.013978 | -0.123476 | +0.001939 | -0.187666 | -0.004644 | +0.198538 | 0.198538 | 0.122545 | +| 8 | 8 | 2892 | 8.712310 | 69.566750 | -17.333443 | -8.667440 | -58.787853 | 7.566183 | 64.777092 | -0.015715 | -0.227729 | +0.043149 | -0.312159 | -0.064407 | +0.339359 | 0.339359 | 0.212417 | +| 9 | 9 | 3096 | 9.336375 | 73.577042 | -6.447144 | -9.620103 | -128.858627 | 14.686206 | 140.051056 | +0.005783 | -0.017298 | +0.008477 | -0.049980 | +0.015123 | +0.049008 | 0.049980 | 0.030367 | +| 10 | 10 | 3154 | 9.512325 | 75.498016 | -14.523483 | -7.317341 | -131.103271 | 17.629053 | 142.061157 | -0.071370 | +0.155823 | -0.023185 | -0.180238 | -0.059308 | +0.167649 | 0.180238 | 0.125181 | +| 11 | 11 | 3334 | 10.056378 | 86.017281 | -14.446944 | -13.689492 | -69.926414 | 26.744770 | 89.069130 | -0.076217 | +0.051389 | -0.007981 | -0.058119 | +0.002088 | +0.069716 | 0.076217 | 0.052846 | +| 12 | 12 | 3455 | 10.424364 | 61.554295 | -4.126511 | -9.723098 | -108.476807 | 22.091070 | 117.986275 | -0.166438 | +0.106278 | +0.061325 | -0.092724 | -0.069702 | -0.155789 | 0.166438 | 0.115819 | +| 13 | 13 | 3611 | 10.896324 | 80.232681 | 4.624288 | -15.574081 | -55.685375 | 26.385595 | 52.279755 | +0.475598 | -0.015943 | -0.262896 | +0.349937 | +0.252457 | +0.127497 | 0.475598 | 0.288100 | +| 14 | 14 | 3694 | 11.144347 | 108.790268 | 1.405766 | -33.485344 | -48.378551 | 43.857502 | 84.771103 | -0.165283 | +0.035006 | +0.109081 | -0.011190 | -0.105902 | -0.268158 | 0.268158 | 0.143580 | +| 15 | 15 | 3780 | 11.408361 | 110.543327 | -3.739877 | -32.432842 | -50.983562 | 44.216587 | 87.566269 | -0.041521 | +0.121023 | -0.036435 | +0.129028 | -0.012573 | -0.057731 | 0.129028 | 0.079412 | +| 16 | 16 | 3854 | 11.632360 | 118.081940 | -17.294281 | -31.087101 | -59.614193 | 48.711964 | 94.208046 | -0.014013 | +0.082106 | -0.018100 | -0.053655 | -0.020612 | +0.073275 | 0.082106 | 0.051540 | +| 17 | 18 | 4121 | 12.448375 | 60.258179 | -22.880474 | -10.315624 | 77.249168 | 35.501560 | -69.439224 | -0.024304 | +0.057606 | +0.017624 | -0.242474 | -0.081817 | +0.229756 | 0.242474 | 0.142884 | + +这张表说明: + +- 如果把旧程序的真实 IO 触发点整体前移 `6` 个周期,再去看对应关节坐标,它们和示教点已经非常接近。 +- 17 个点里,大多数前 `6` 周期采样点与示教点的最大单轴误差都在 `0.05 ~ 0.34 deg`。 +- 误差较大的两个点是: + - `trigger 3`:`max_abs_diff = 0.598609 deg` + - `trigger 13`:`max_abs_diff = 0.475598 deg` +- 这进一步支持一个很强的现象:旧抓包里的真实 IO 触发时刻,相对“最接近示教点”的实际运动位置,整体滞后约 `48ms`。 + +## 12. 复现命令 + +在仓库根目录执行: + +```powershell +python analysis/analyze_2026042802_1_trigger_vs_teach_points.py +python analysis/analyze_2026042802_1_status_feedback_vs_teach_points.py +``` + +脚本默认读取: + +- `../Rvbust/uttc-20260428/2026042802-1.pcap` +- `Config/RobotConfig.json` +- `D:\Zyx\Downloads\WiresharkPortable32\App\Wireshark\tshark.exe` + +如果后续 Wireshark 路径变化,需要先同步修改脚本中的 `DEFAULT_TSHARK`。 + +## 13. 60015 状态反馈视角复核 + +上面第 11 节的“前 6 个周期最接近示教点”,只是在看: + +- `192.168.10.10 -> 192.168.10.11` 的 64B J519 下发命令序列 + +它说明的是“命令目标流”和示教点之间的相对时序,不能直接等价成“机械臂实时反馈已经过了示教点才触发”。 + +针对你补充的 `buffer_size` 线索,这里进一步直接解析了: + +- `192.168.10.11 -> 192.168.10.10` 的 132B `UDP 60015` 状态包 +- 解码口径与仓库运行时代码 `src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Protocol.cs` 一致,全部按大端解析 +- 关节反馈字段使用状态包里的 `0x3C ~ 0x57` 六轴 `float32 deg` + +本次新增文件: + +- `analysis/2026042802-1/2026042802-1_j519_status_feedback_all.csv` +- `analysis/2026042802-1/2026042802-1_trigger_status_feedback_vs_teach_points.csv` +- `analysis/2026042802-1/2026042802-1_trigger_manual_compare.csv` +- `analysis/2026042802-1/2026042802-1_status_feedback_summary.json` +- `analysis/analyze_2026042802_1_status_feedback_vs_teach_points.py` + +### 13.1 序列号关系先确认 + +整条抓包里共提取到: + +- 状态包 `1789` 条 +- 命令包 `1788` 条 + +对每一条命令帧,取它前一个状态帧做对齐,结果是: + +- `命令 sequence - 前一条状态 sequence = 8` +- 统计结果:`1788 / 1788` 全部成立 + +这说明在这份旧抓包里,`buffer_size=8` 不是偶发现象,而是整条 J519 流都稳定成立。 + +换句话说: + +- 触发命令包上的 `sequence=1381124` +- 它对应“当前实时反馈”的状态包 `sequence=1381116` +- 中间固定隔着 `8` 个状态周期,也就是约 `64ms` + +### 13.2 触发当下的实时反馈,并不在示教点附近 + +如果直接取“每个触发命令帧对应的当前状态包”,也就是: + +- `paired_status_sequence = trigger_sequence - 8` + +那么 17 个点相对示教点的误差反而更大: + +- 平均最大单轴误差:`6.469219 deg` +- 最大单轴误差:`16.123934 deg` +- 最大误差轴分布:`J6=9`、`J2=4`、`J1=2`、`J4=2` + +这说明: + +- 机械臂在触发命令真正下发的那个时刻,对应的实时反馈位置,大多数情况下还没有跑到示教点附近 +- 所以“最接近示教点都出现在触发前 6 个周期”这个现象,不能理解成“机器人已经过了示教点才开始触发” +- 那只是命令流相对示教点的时序关系;实时反馈要再往后看 + +### 13.3 实时反馈最接近示教点,出现在触发后 9 到 10 个状态周期 + +对每个触发点,以 `paired_status_sequence = trigger_sequence - 8` 为起点,在前后 `±20` 个状态周期里搜索“与示教点最接近”的状态反馈帧,结果统一得到: + +- 最佳点相对 `paired_status` 晚 `9` 或 `10` 个状态周期 +- 统计分布:`9 周期 = 9` 次,`10 周期 = 8` 次 + +折算成触发命令时刻: + +- 最佳点相对触发命令晚 `1` 或 `2` 个状态周期 +- 统计分布:`+1 周期 = 9` 次,`+2 周期 = 8` 次 +- 对应抓包时间差约 `71.838 ~ 79.964 ms` +- 平均约 `75.661 ms` + +这里看起来像是 `1~2` 周期,但要注意基准不同: + +- 触发命令本身领先当前状态 `8` 个周期 +- 所以“触发后约 `72~80ms` 才最接近示教点”与“状态流从当前反馈再往后走 `9~10` 个周期才到位”本质上是同一件事 + +### 13.4 实时反馈视角的逐点结果 + +| 触发序号 | waypoint_index | trigger_frame | trigger_seq | paired_status_frame | paired_status_seq | 触发当下最大误差轴 | 触发当下最大误差(deg) | 触发当下RMS(deg) | 最佳状态seq | 最佳状态相对当前反馈晚几周期 | 最佳状态相对触发时间(ms) | 最佳状态最大误差(deg) | 最佳状态RMS(deg) | +| --- | --- | ---: | ---: | ---: | ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 1 | 1955 | 1381124 | 1954 | 1381116 | J1 | 6.588242 | 3.822544 | 1381126 | 10 | 79.853 | 0.207590 | 0.122397 | +| 2 | 2 | 2151 | 1381198 | 2150 | 1381190 | J1 | 2.335315 | 1.382754 | 1381199 | 9 | 71.912 | 0.082134 | 0.046161 | +| 3 | 3 | 2223 | 1381226 | 2222 | 1381218 | J6 | 16.123934 | 8.852377 | 1381228 | 10 | 79.929 | 0.569692 | 0.323964 | +| 4 | 4 | 2336 | 1381269 | 2335 | 1381261 | J6 | 4.901883 | 3.244056 | 1381271 | 10 | 79.909 | 0.094704 | 0.065805 | +| 5 | 5 | 2477 | 1381321 | 2476 | 1381313 | J6 | 9.219438 | 4.621321 | 1381323 | 10 | 79.833 | 0.056622 | 0.033575 | +| 6 | 6 | 2665 | 1381391 | 2664 | 1381383 | J6 | 6.146461 | 3.047841 | 1381392 | 9 | 71.904 | 0.176974 | 0.085631 | +| 7 | 7 | 2811 | 1381447 | 2810 | 1381439 | J6 | 13.151718 | 7.782024 | 1381448 | 9 | 71.873 | 0.306587 | 0.182107 | +| 8 | 8 | 2908 | 1381483 | 2907 | 1381475 | J6 | 7.436279 | 5.127041 | 1381484 | 9 | 71.838 | 0.060748 | 0.027507 | +| 9 | 9 | 3112 | 1381561 | 3111 | 1381553 | J6 | 5.585605 | 3.277206 | 1381562 | 9 | 71.873 | 0.152260 | 0.089150 | +| 10 | 10 | 3170 | 1381583 | 3169 | 1381575 | J2 | 4.165077 | 2.128984 | 1381585 | 10 | 79.856 | 0.073440 | 0.043286 | +| 11 | 11 | 3350 | 1381651 | 3349 | 1381643 | J2 | 1.830953 | 1.169340 | 1381652 | 9 | 71.898 | 0.077668 | 0.042465 | +| 12 | 12 | 3470 | 1381697 | 3469 | 1381689 | J4 | 4.822353 | 2.861125 | 1381698 | 9 | 71.950 | 0.021077 | 0.014936 | +| 13 | 13 | 3627 | 1381756 | 3626 | 1381748 | J4 | 8.152508 | 5.168659 | 1381757 | 9 | 71.855 | 0.145543 | 0.069960 | +| 14 | 14 | 3710 | 1381787 | 3709 | 1381779 | J6 | 10.404419 | 5.679811 | 1381789 | 10 | 79.922 | 0.119453 | 0.058360 | +| 15 | 15 | 3796 | 1381820 | 3795 | 1381812 | J2 | 2.616108 | 1.591175 | 1381822 | 10 | 79.926 | 0.149803 | 0.087169 | +| 16 | 16 | 3870 | 1381848 | 3869 | 1381840 | J2 | 4.682474 | 2.206740 | 1381850 | 10 | 79.964 | 0.172956 | 0.104194 | +| 17 | 18 | 4138 | 1381950 | 4137 | 1381942 | J6 | 1.813960 | 1.153699 | 1381951 | 9 | 71.948 | 0.074923 | 0.038345 | + +### 13.4.1 手工核对专用三时刻对照 + +如果你后面要拿着导出的触发序列人工对比,最推荐直接看下面这张表。 + +对应单独导出文件: + +- `analysis/2026042802-1/2026042802-1_trigger_manual_compare.csv` + +它把每个触发点压缩成 3 个时刻: + +- `触发命令时刻` + - 你真正发出去、同时带着 IO 触发的那条命令 +- `触发当下实时反馈` + - 按 `buffer_size=8` 回推到当时机器人真实反馈所对应的状态包 +- `最接近示教点的实时反馈` + - 在状态流中真正最贴近示教点的那一帧 + +可以直接把它理解成一句话: + +- “我在第几条命令上触发了” +- “触发那一刻机器人实际还差多少” +- “再过多久机器人才真正到最接近示教点的位置” + +| 触发序号 | waypoint_index | 触发命令seq | 触发命令frame | 触发当下状态seq | 触发当下状态frame | 命令领先状态(周期) | 触发当下最大误差(deg) | 最接近示教点状态seq | 最接近示教点状态frame | 从触发当下反馈再晚几周期 | 相对触发命令再晚几周期 | 相对触发命令再晚多久(ms) | 最接近示教点最大误差(deg) | +| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 1 | 1381124 | 1955 | 1381116 | 1954 | 8 | 6.588242 | 1381126 | 1977 | 10 | 2 | 79.853 | 0.207590 | +| 2 | 2 | 1381198 | 2151 | 1381190 | 2150 | 8 | 2.335315 | 1381199 | 2174 | 9 | 1 | 71.912 | 0.082134 | +| 3 | 3 | 1381226 | 2223 | 1381218 | 2222 | 8 | 16.123934 | 1381228 | 2248 | 10 | 2 | 79.929 | 0.569692 | +| 4 | 4 | 1381269 | 2336 | 1381261 | 2335 | 8 | 4.901883 | 1381271 | 2361 | 10 | 2 | 79.909 | 0.094704 | +| 5 | 5 | 1381321 | 2477 | 1381313 | 2476 | 8 | 9.219438 | 1381323 | 2502 | 10 | 2 | 79.833 | 0.056622 | +| 6 | 6 | 1381391 | 2665 | 1381383 | 2664 | 8 | 6.146461 | 1381392 | 2687 | 9 | 1 | 71.904 | 0.176974 | +| 7 | 7 | 1381447 | 2811 | 1381439 | 2810 | 8 | 13.151718 | 1381448 | 2833 | 9 | 1 | 71.873 | 0.306587 | +| 8 | 8 | 1381483 | 2908 | 1381475 | 2907 | 8 | 7.436279 | 1381484 | 2930 | 9 | 1 | 71.838 | 0.060748 | +| 9 | 9 | 1381561 | 3112 | 1381553 | 3111 | 8 | 5.585605 | 1381562 | 3134 | 9 | 1 | 71.873 | 0.152260 | +| 10 | 10 | 1381583 | 3170 | 1381575 | 3169 | 8 | 4.165077 | 1381585 | 3195 | 10 | 2 | 79.856 | 0.073440 | +| 11 | 11 | 1381651 | 3350 | 1381643 | 3349 | 8 | 1.830953 | 1381652 | 3372 | 9 | 1 | 71.898 | 0.077668 | +| 12 | 12 | 1381697 | 3470 | 1381689 | 3469 | 8 | 4.822353 | 1381698 | 3492 | 9 | 1 | 71.950 | 0.021077 | +| 13 | 13 | 1381756 | 3627 | 1381748 | 3626 | 8 | 8.152508 | 1381757 | 3649 | 9 | 1 | 71.855 | 0.145543 | +| 14 | 14 | 1381787 | 3710 | 1381779 | 3709 | 8 | 10.404419 | 1381789 | 3735 | 10 | 2 | 79.922 | 0.119453 | +| 15 | 15 | 1381820 | 3796 | 1381812 | 3795 | 8 | 2.616108 | 1381822 | 3821 | 10 | 2 | 79.926 | 0.149803 | +| 16 | 16 | 1381848 | 3870 | 1381840 | 3869 | 8 | 4.682474 | 1381850 | 3895 | 10 | 2 | 79.964 | 0.172956 | +| 17 | 18 | 1381950 | 4138 | 1381942 | 4137 | 8 | 1.813960 | 1381951 | 4160 | 9 | 1 | 71.948 | 0.074923 | + +这张表最适合回答两个实际问题: + +1. 触发到底是不是已经“跑过点”才发生的 + - 不是 + - 因为触发当下对应的实时反馈误差仍然明显存在 + +2. 真正最接近示教点的实时位置,距离触发还差多久 + - 大部分点还要再过 `1` 或 `2` 个命令周期 + - 如果换成状态反馈基准,就是再过 `9` 或 `10` 个状态周期 + - 时间量级稳定在 `72~80ms` + +### 13.4.2 可以优先人工盯的异常点 + +如果你不想一开始就看 17 个点,建议优先盯下面几类: + +- `trigger 3` + - 触发当下实时反馈最大误差 `16.123934 deg` + - 即便到“最佳反馈时刻”,最大误差也还有 `0.569692 deg` + - 它是 17 个点里最值得优先复核的一个 +- `trigger 7` + - 触发当下实时反馈最大误差 `13.151718 deg` + - 到位后仍有 `0.306587 deg` +- `trigger 14` + - 触发当下实时反馈最大误差 `10.404419 deg` + - 属于第二档明显偏大的点 +- `trigger 1` + - 触发当下误差 `6.588242 deg`,而且最大误差轴是 `J1` + - 和前面很多 `J6` 主导的点不一样,适合拿来对比不同类型 + +如果你想最快抓住旧程序补偿逻辑,建议先人工对照: + +- `trigger 3` +- `trigger 7` +- `trigger 14` +- `trigger 1` + +这 4 个点已经能覆盖: + +- `J6` 主导的大偏差 +- `J1` 主导的特殊点 +- `+1` 周期到位与 `+2` 周期到位两种情况 + +### 13.5 对“为什么触发时间看起来在我运动到这个位置之后”的解释 + +现在可以把两种视角彻底分开: + +1. 命令流视角 + - 触发命令帧前 `6` 个周期的目标点,和示教点已经非常接近 + - 所以如果只看“发给机器人什么目标”,会误以为触发发生在“经过示教点之后” + +2. 实时反馈视角 + - 由于 `buffer_size=8`,触发命令帧本身就领先当前状态 `8` 个周期 + - 触发时刻对应的实时反馈,其实还没到示教点附近 + - 再往后走 `9~10` 个状态周期,反馈才最接近示教点 + +因此更准确的表述应该是: + +- 旧程序不是“机器人已经跑过示教点才开始触发” +- 而是“触发命令是按一个提前缓存的目标序列发出去的,命令序列本身比实时反馈领先 8 个周期” +- 叠加旧程序里触发标志在目标序列上的布置位置,就会出现: + - 命令流看起来:最接近示教点在触发前 `6` 个周期 + - 实时反馈看起来:最接近示教点在触发后 `9~10` 个状态周期 + +两者并不矛盾,基准不同而已。 + +### 13.6 本节结论 + +1. 这份旧抓包的 `60015` 状态流已经直接证明:下发命令序列稳定领先当前状态序列 `8` 个周期。 +2. 因此不能再把“命令流中的前 6 周期最接近示教点”误解成“机械臂真实位置已经过点后才触发”。 +3. 按实时反馈看,17 个触发点在触发当下都还没有到示教点附近;最接近示教点的反馈统一出现在触发后约 `72~80ms`。 +4. 如果后续要反推旧程序拍照补偿逻辑,优先应该建模成: + - `命令目标序列` 提前缓存 `8` 个周期 + - `触发标志` 在目标序列上又相对示教点提前约 `6` 个周期 + - 两者叠加后,实时反馈到位时刻落在触发之后约 `1~2` 个命令周期 + +## 14. TriggerSampleIndexOffsetCycles 取值 6 / 7 / 8 的对照 + +在代码里把触发绑定后移做成显式配置之后,进一步对 `6 / 7 / 8` 三档做了离线对照。 + +本节的比较口径是: + +- 先找“最接近示教点”的最佳命令 sample +- 再分别向后偏移 `6 / 7 / 8` 个命令周期 +- 比较这三档各自落到的命令帧,与旧抓包真实触发帧的误差大小 + +新增文件: + +- `analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_compare.csv` +- `analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_summary.json` +- `analysis/build_trigger_offset_compare_6_7_8.py` + +### 14.1 汇总结论 + +三档比较结果非常明确: + +- `6` 周期胜出 `17 / 17` +- `7` 周期胜出 `0 / 17` +- `8` 周期胜出 `0 / 17` + +平均误差也呈现单调变差: + +| 偏移周期 | 平均最大单轴误差(deg) | 平均RMS误差(deg) | +| --- | ---: | ---: | +| `6` | `4.241584` | `2.540614` | +| `7` | `5.068092` | `3.021297` | +| `8` | `5.931684` | `3.520176` | + +这说明: + +- 如果目标是“尽量复刻旧抓包里的真实命令触发点”,`TriggerSampleIndexOffsetCycles` 应该选 `6` +- 当前写入配置的 `7`,更偏向“再往后推一拍”,但它并不更接近这份旧抓包 + +### 14.2 逐点对照表 + +| 触发序号 | waypoint_index | 最佳sample frame | 偏移6 frame | 偏移6最大误差 | 偏移6RMS | 偏移7 frame | 偏移7最大误差 | 偏移7RMS | 偏移8 frame | 偏移8最大误差 | 偏移8RMS | 本点最优偏移 | +| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | +| 1 | 1 | 1941 | 1955 | 3.406527 | 2.468439 | 1958 | 4.180932 | 2.935578 | 1961 | 5.004552 | 3.418447 | `6` | +| 2 | 2 | 2135 | 2151 | 2.816723 | 1.656246 | 2153 | 3.526760 | 2.060948 | 2155 | 4.301479 | 2.503393 | `6` | +| 3 | 3 | 2207 | 2223 | 11.130744 | 5.955608 | 2227 | 13.165517 | 7.035338 | 2229 | 15.212756 | 8.119717 | `6` | +| 4 | 4 | 2321 | 2336 | 2.311684 | 1.373158 | 2339 | 3.083186 | 1.743718 | 2341 | 3.952016 | 2.158078 | `6` | +| 5 | 5 | 2457 | 2477 | 2.171828 | 1.404704 | 2479 | 2.585320 | 1.667010 | 2483 | 3.004922 | 1.950746 | `6` | +| 6 | 6 | 2649 | 2665 | 1.408994 | 0.842403 | 2667 | 1.770170 | 1.066919 | 2669 | 2.303903 | 1.330975 | `6` | +| 7 | 7 | 2795 | 2811 | 7.194107 | 4.618545 | 2814 | 8.248680 | 5.331516 | 2817 | 9.275559 | 6.035874 | `6` | +| 8 | 8 | 2892 | 2908 | 5.939884 | 3.533133 | 2910 | 6.922169 | 4.090128 | 2912 | 7.916950 | 4.649087 | `6` | +| 9 | 9 | 3096 | 3112 | 2.524071 | 1.630593 | 3114 | 2.830116 | 1.857618 | 3118 | 3.101311 | 2.072647 | `6` | +| 10 | 10 | 3154 | 3170 | 3.392997 | 2.105986 | 3172 | 4.172522 | 2.562682 | 3175 | 4.999007 | 3.042228 | `6` | +| 11 | 11 | 3334 | 3350 | 2.984824 | 2.013493 | 3352 | 3.668105 | 2.458222 | 3355 | 4.397696 | 2.932320 | `6` | +| 12 | 12 | 3455 | 3470 | 4.000958 | 1.990246 | 3474 | 4.929097 | 2.393668 | 3476 | 5.929425 | 2.827363 | `6` | +| 13 | 13 | 3611 | 3627 | 6.656209 | 4.109282 | 3629 | 7.722974 | 4.798702 | 3633 | 8.793828 | 5.501948 | `6` | +| 14 | 14 | 3694 | 3710 | 2.935180 | 1.567983 | 3712 | 3.224395 | 1.721797 | 3714 | 3.454963 | 1.844232 | `6` | +| 15 | 15 | 3780 | 3796 | 2.573147 | 1.686690 | 3798 | 3.074261 | 2.037358 | 3801 | 3.579433 | 2.398059 | `6` | +| 16 | 16 | 3854 | 3870 | 3.810522 | 2.197783 | 3872 | 4.756826 | 2.710410 | 3874 | 5.783033 | 3.263711 | `6` | +| 17 | 18 | 4121 | 4138 | 6.848522 | 4.036140 | 4140 | 8.296536 | 4.890438 | 4142 | 9.827793 | 5.794164 | `6` | + +### 14.3 推荐 + +因此这里可以分成两个不同目标: + +1. 如果目标是“尽量复刻旧程序抓包” + - 推荐:`TriggerSampleIndexOffsetCycles = 6` + +2. 如果目标是“主观上想往实时反馈更靠后推一点,再试效果” + - 可以继续保留 `7` + - 但要明确:这已经不是“最接近旧抓包”的取值,而是主动做新的补偿尝试 + +当前基于旧抓包证据,我更推荐: + +- 先把配置从 `7` 改回 `6` +- 再拿新程序实际导出的 `ActualSendJointTraj.txt + ShotEvents.json + 现场图像效果` 做下一轮闭环 diff --git a/docs/camera-trigger-actual-offset-pcap-record-20260508.md b/docs/camera-trigger-actual-offset-pcap-record-20260508.md new file mode 100644 index 0000000..2f5f309 --- /dev/null +++ b/docs/camera-trigger-actual-offset-pcap-record-20260508.md @@ -0,0 +1,125 @@ +# 相机触发实际偏差抓包触发点记录 + +## 数据来源 + +- 抓包文件:`D:/Dev/Codes/rvbust-code/FlyingShotPkg_3.15_VDA/Rvbust/相机触发实际偏差.pcap` +- 对照配置:`D:/Dev/Codes/rvbust-code/FlyingShotPkg_3.15_VDA/flyshot-replacement/Config/RobotConfig.json` 中 `flying_shots.UTTC_MS11` +- 提取口径:只看 `192.168.10.5:50843 -> 192.168.10.11:60015` 的 64B J519 命令帧。 +- 触发判定:`write_io_mask > 0 && write_io_value > 0` 视为 IO 置位。 +- 去重规则:由于 `io_keep_cycles=2`,每次触发会连续保持两帧高电平;本文档每组只记录第一帧,最终得到 17 次真实触发。 +- 关节单位:抓包中的 J519 目标关节为 `deg`。 + +## 统计 + +- 真实触发次数:17 +- 平均单点最大单轴误差:0.142224 deg +- 最大单轴误差:0.309618 deg +- 平均 RMS 误差:0.085167 deg + +## 触发记录 + +| 触发序号 | waypoint_index | frame | seq | 相对时间(s) | io_mask | J1(deg) | J2(deg) | J3(deg) | J4(deg) | J5(deg) | J6(deg) | 最大误差轴 | 最大误差(deg) | RMS误差(deg) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | 1 | 2445 | 4641 | 7.536586 | 10 | 48.879890 | 2.237859 | -11.024109 | 0.101012 | 6.253663 | 2.604609 | J6 | 0.309618 | 0.179380 | +| 2 | 2 | 2689 | 4739 | 8.320342 | 14 | 55.367146 | 11.804994 | -7.005956 | -70.935097 | 6.014911 | 74.180328 | J4 | 0.079235 | 0.043714 | +| 3 | 3 | 2779 | 4769 | 8.560284 | 14 | 55.135155 | 8.783901 | -8.500860 | -41.282757 | 10.067441 | 42.164478 | J6 | 0.207545 | 0.110824 | +| 4 | 4 | 2882 | 4811 | 8.896328 | 10 | 43.623173 | -1.579988 | -17.651855 | 6.007176 | 32.012070 | -22.635672 | J5 | 0.159648 | 0.078436 | +| 5 | 5 | 3004 | 4860 | 9.288442 | 10 | 64.717529 | 4.395389 | -15.566289 | -29.885765 | 29.651941 | 45.777378 | J5 | 0.198499 | 0.137260 | +| 6 | 6 | 3169 | 4921 | 9.776381 | 12 | 60.502796 | 23.212698 | -5.056696 | 36.065346 | 5.528813 | -31.387259 | J2 | 0.143916 | 0.073287 | +| 7 | 7 | 3286 | 4968 | 10.152455 | 12 | 70.484123 | 16.315718 | -13.455399 | -28.008068 | 14.532272 | 31.833994 | J6 | 0.122061 | 0.075811 | +| 8 | 8 | 3360 | 4998 | 10.392386 | 10 | 69.575752 | -17.207413 | -8.691541 | -58.617191 | 7.602061 | 64.591766 | J6 | 0.154034 | 0.096014 | +| 9 | 9 | 3542 | 5063 | 10.912377 | 10 | 73.584053 | -6.468980 | -9.609648 | -128.917969 | 14.704389 | 140.109039 | J4 | 0.109321 | 0.066535 | +| 10 | 10 | 3585 | 5081 | 11.056321 | 10 | 75.474472 | -14.468846 | -7.325953 | -131.159668 | 17.608482 | 142.113190 | J4 | 0.236635 | 0.165806 | +| 11 | 11 | 3739 | 5137 | 11.504390 | 10 | 86.261429 | -14.617929 | -13.661033 | -69.765862 | 26.736444 | 88.871201 | J1 | 0.167931 | 0.107930 | +| 12 | 12 | 3835 | 5176 | 11.816490 | 10 | 61.744911 | -4.247795 | -9.792937 | -108.367577 | 22.170429 | 118.159653 | J1 | 0.024179 | 0.016111 | +| 13 | 13 | 3961 | 5227 | 12.224448 | 12 | 80.019569 | 4.632708 | -15.454964 | -55.831528 | 26.270802 | 52.205910 | J1 | 0.262486 | 0.159680 | +| 14 | 14 | 4031 | 5255 | 12.448371 | 10 | 109.016167 | 1.357394 | -33.634285 | -48.365601 | 44.002415 | 85.139641 | J6 | 0.100380 | 0.053296 | +| 15 | 15 | 4119 | 5285 | 12.688396 | 10 | 110.607498 | -3.936621 | -32.371201 | -51.189194 | 44.234520 | 87.651657 | J4 | 0.076603 | 0.047511 | +| 16 | 16 | 4183 | 5311 | 12.896459 | 10 | 118.107513 | -17.420839 | -31.061703 | -59.539654 | 48.746391 | 94.106598 | J2 | 0.044452 | 0.024439 | +| 17 | 18 | 4466 | 5419 | 13.760487 | 12 | 60.277737 | -22.937906 | -10.334841 | 77.472717 | 35.583603 | -69.647720 | J6 | 0.021260 | 0.011799 | + +## 逐点示教差值 + +| 触发序号 | waypoint_index | diff_j1(deg) | diff_j2(deg) | diff_j3(deg) | diff_j4(deg) | diff_j5(deg) | diff_j6(deg) | +| --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | 1 | -0.006920 | 0.038873 | -0.003092 | -0.309197 | 0.005281 | 0.309618 | +| 2 | 2 | 0.019390 | -0.002046 | 0.003139 | 0.079235 | 0.002846 | -0.069204 | +| 3 | 3 | 0.025347 | 0.024404 | 0.017357 | -0.165506 | -0.041047 | 0.207545 | +| 4 | 4 | -0.030420 | 0.049671 | 0.063898 | 0.011967 | -0.159648 | -0.061698 | +| 5 | 5 | 0.135085 | 0.132409 | 0.102928 | 0.067162 | -0.198499 | 0.150851 | +| 6 | 6 | 0.023314 | 0.143916 | -0.045432 | -0.041509 | 0.003132 | -0.084695 | +| 7 | 7 | 0.008286 | -0.078131 | 0.001549 | -0.115749 | -0.003390 | 0.122061 | +| 8 | 8 | -0.006712 | -0.101699 | 0.019049 | -0.141497 | -0.028529 | 0.154034 | +| 9 | 9 | 0.012794 | -0.039135 | 0.018932 | -0.109321 | 0.033306 | 0.106991 | +| 10 | 10 | -0.094914 | 0.210460 | -0.031797 | -0.236635 | -0.079879 | 0.219682 | +| 11 | 11 | 0.167931 | -0.119596 | 0.020478 | 0.102434 | -0.006237 | -0.128213 | +| 12 | 12 | 0.024179 | -0.015005 | -0.008514 | 0.016506 | 0.009657 | 0.017589 | +| 13 | 13 | 0.262486 | -0.007523 | -0.143778 | 0.203785 | 0.137664 | 0.053653 | +| 14 | 14 | 0.060616 | -0.013366 | -0.039860 | 0.001760 | 0.039011 | 0.100380 | +| 15 | 15 | 0.022650 | -0.075722 | 0.025207 | -0.076603 | 0.005360 | 0.027657 | +| 16 | 16 | 0.011561 | -0.044452 | 0.007298 | 0.020885 | 0.013816 | -0.028173 | +| 17 | 18 | -0.004746 | 0.000174 | -0.001593 | -0.018925 | 0.000225 | 0.021260 | + +## 说明 + +- 本文档只记录“抓包中真实发 IO 的那一帧”的关节坐标。 +- 如果后续要和旧程序抓包或 `ShotEvents.json` 对比,应继续沿用同一口径:优先比较真实 IO 置位帧,而不是只比较 sample_index。 + +## 新旧程序真实触发点逐点对比 + +- 新抓包:`D:/Dev/Codes/rvbust-code/FlyingShotPkg_3.15_VDA/Rvbust/相机触发实际偏差.pcap` +- 旧抓包:`D:/Dev/Codes/rvbust-code/FlyingShotPkg_3.15_VDA/Rvbust/uttc-20260428/2026042802-1.pcap` +- 对比口径:两边都只取真实 IO 置位高电平的第一帧,共 17 次触发。 + +### 对比统计 + +- 新旧都成功提取到 17 次真实触发。 +- 平均单点最大单轴关节差:4.185609 deg +- 最大单轴关节差:11.338289 deg +- 平均 RMS 关节差:2.515726 deg +- 平均相对首触发时间漂移:0.220309 s +- 最大相对首触发时间漂移:0.432218 s + +### 新旧实际触发记录 + +| 触发序号 | 新frame | 新seq | 新相对时间(s) | 新io_mask | 新J1 | 新J2 | 新J3 | 新J4 | 新J5 | 新J6 | 旧frame | 旧seq | 旧相对时间(s) | 旧io_mask | 旧J1 | 旧J2 | 旧J3 | 旧J4 | 旧J5 | 旧J6 | 时间差(s) | 相对首点漂移(s) | 最大差轴 | 最大关节差(deg) | RMS差(deg) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | 2445 | 4641 | 7.536586 | 10 | 48.879890 | 2.237859 | -11.024109 | 0.101012 | 6.253663 | 2.604609 | 1955 | 1381124 | 5.888271 | 10 | 45.863270 | 2.972479 | -13.609342 | -2.185114 | 7.582361 | 5.701518 | 1.648315 | 0.000000 | J6 | 3.096909 | 2.341857 | +| 2 | 2689 | 4739 | 8.320342 | 14 | 55.367146 | 11.804994 | -7.005956 | -70.935097 | 6.014911 | 74.180328 | 2151 | 1381198 | 6.480252 | 14 | 56.343365 | 11.667239 | -6.600924 | -68.197609 | 6.058437 | 71.539284 | 1.840090 | 0.191775 | J4 | 2.737488 | 1.612807 | +| 3 | 2779 | 4769 | 8.560284 | 14 | 55.135155 | 8.783901 | -8.500860 | -41.282757 | 10.067441 | 42.164478 | 2223 | 1381226 | 6.704225 | 14 | 53.508320 | 7.412223 | -9.629740 | -32.305714 | 12.488306 | 30.826189 | 1.856059 | 0.207744 | J6 | 11.338289 | 6.066399 | +| 4 | 2882 | 4811 | 8.896328 | 10 | 43.623173 | -1.579988 | -17.651855 | 6.007176 | 32.012070 | -22.635672 | 2336 | 1381269 | 7.048266 | 10 | 44.433006 | -2.115629 | -18.397774 | 4.837199 | 33.993725 | -20.262289 | 1.848062 | 0.199747 | J6 | 2.373383 | 1.439206 | +| 5 | 3004 | 4860 | 9.288442 | 10 | 64.717529 | 4.395389 | -15.566289 | -29.885765 | 29.651941 | 45.777378 | 2477 | 1381321 | 7.464289 | 10 | 65.849670 | 5.695003 | -14.563637 | -28.693426 | 27.678612 | 46.437157 | 1.824153 | 0.175838 | J5 | 1.973330 | 1.273254 | +| 6 | 3169 | 4921 | 9.776381 | 12 | 60.502796 | 23.212698 | -5.056696 | 36.065346 | 5.528813 | -31.387259 | 2665 | 1381391 | 8.024277 | 12 | 60.847530 | 24.477776 | -5.617044 | 34.804432 | 5.786255 | -31.225348 | 1.752104 | 0.103789 | J2 | 1.265078 | 0.786940 | +| 7 | 3286 | 4968 | 10.152455 | 12 | 70.484123 | 16.315718 | -13.455399 | -28.008068 | 14.532272 | 31.833994 | 2811 | 1381447 | 8.472317 | 12 | 70.825050 | 10.952600 | -13.136046 | -34.684597 | 14.022421 | 38.906040 | 1.680138 | 0.031823 | J6 | 7.072046 | 4.542963 | +| 8 | 3360 | 4998 | 10.392386 | 10 | 69.575752 | -17.207413 | -8.691541 | -58.617191 | 7.602061 | 64.591766 | 2908 | 1381483 | 8.760361 | 10 | 69.405167 | -20.066782 | -8.152936 | -63.932354 | 6.775912 | 70.377617 | 1.632025 | -0.016290 | J6 | 5.785851 | 3.437653 | +| 9 | 3542 | 5063 | 10.912377 | 10 | 73.584053 | -6.468980 | -9.609648 | -128.917969 | 14.704389 | 140.109039 | 3112 | 1381561 | 9.384332 | 10 | 73.905930 | -8.016125 | -9.002426 | -131.332718 | 15.526207 | 142.416687 | 1.528045 | -0.120270 | J4 | 2.414749 | 1.565121 | +| 10 | 3585 | 5081 | 11.056321 | 10 | 75.474472 | -14.468846 | -7.325953 | -131.159668 | 17.608482 | 142.113190 | 3170 | 1381583 | 9.560358 | 10 | 76.726883 | -16.518726 | -7.151471 | -127.530037 | 18.537889 | 138.789749 | 1.495963 | -0.152352 | J4 | 3.629631 | 2.268783 | +| 11 | 3739 | 5137 | 11.504390 | 10 | 86.261429 | -14.617929 | -13.661033 | -69.765862 | 26.736444 | 88.871201 | 3350 | 1381651 | 10.104322 | 10 | 83.716621 | -13.186573 | -13.738809 | -72.703178 | 26.667589 | 91.984238 | 1.400068 | -0.248247 | J6 | 3.113037 | 2.115595 | +| 12 | 3835 | 5176 | 11.816490 | 10 | 61.744911 | -4.247795 | -9.792937 | -108.367577 | 22.170429 | 118.159653 | 3470 | 1381697 | 10.472333 | 10 | 59.789558 | -2.709417 | -8.933828 | -108.248573 | 21.177492 | 114.141106 | 1.344157 | -0.304158 | J6 | 4.018547 | 2.003201 | +| 13 | 3961 | 5227 | 12.224448 | 12 | 80.019569 | 4.632708 | -15.454964 | -55.831528 | 26.270802 | 52.205910 | 3627 | 1381756 | 10.944371 | 12 | 86.413292 | 4.266173 | -19.159878 | -52.240280 | 29.853724 | 55.869072 | 1.280077 | -0.368238 | J1 | 6.393723 | 3.955873 | +| 14 | 4031 | 5255 | 12.448371 | 10 | 109.016167 | 1.357394 | -33.634285 | -48.365601 | 44.002415 | 85.139641 | 3710 | 1381787 | 11.192339 | 10 | 110.752319 | 0.875189 | -34.756535 | -48.143326 | 45.086559 | 87.974442 | 1.256032 | -0.392283 | J6 | 2.834801 | 1.514764 | +| 15 | 4119 | 5285 | 12.688396 | 10 | 110.607498 | -3.936621 | -32.371201 | -51.189194 | 44.234520 | 87.651657 | 3796 | 1381820 | 11.456361 | 10 | 111.791779 | -6.225441 | -31.958988 | -53.685738 | 44.761509 | 89.334549 | 1.232035 | -0.416280 | J4 | 2.496544 | 1.640818 | +| 16 | 4183 | 5311 | 12.896459 | 10 | 118.107513 | -17.420839 | -31.061703 | -59.539654 | 48.746391 | 94.106598 | 3870 | 1381848 | 11.680362 | 10 | 117.874741 | -19.607178 | -30.329103 | -56.600250 | 49.086555 | 90.324249 | 1.216097 | -0.432218 | J6 | 3.782349 | 2.176883 | +| 17 | 4466 | 5419 | 13.760487 | 12 | 60.277737 | -22.937906 | -10.334841 | 77.472717 | 35.583603 | -69.647720 | 4138 | 1381950 | 12.496365 | 12 | 59.935894 | -21.102140 | -9.684239 | 70.643120 | 32.941307 | -63.348557 | 1.264122 | -0.384193 | J4 | 6.829597 | 4.025228 | + +### 新减旧逐轴关节差 + +| 触发序号 | diff_j1(deg) | diff_j2(deg) | diff_j3(deg) | diff_j4(deg) | diff_j5(deg) | diff_j6(deg) | +| --- | --- | --- | --- | --- | --- | --- | +| 1 | 3.016621 | -0.734620 | 2.585233 | 2.286127 | -1.328699 | -3.096909 | +| 2 | -0.976219 | 0.137754 | -0.405033 | -2.737488 | -0.043527 | 2.641045 | +| 3 | 1.626835 | 1.371678 | 1.128880 | -8.977043 | -2.420865 | 11.338289 | +| 4 | -0.809834 | 0.535641 | 0.745918 | 1.169977 | -1.981655 | -2.373383 | +| 5 | -1.132141 | -1.299614 | -1.002652 | -1.192339 | 1.973330 | -0.659779 | +| 6 | -0.344734 | -1.265078 | 0.560348 | 1.260914 | -0.257442 | -0.161911 | +| 7 | -0.340927 | 5.363118 | -0.319352 | 6.676529 | 0.509851 | -7.072046 | +| 8 | 0.170586 | 2.859369 | -0.538605 | 5.315163 | 0.826149 | -5.785851 | +| 9 | -0.321877 | 1.547145 | -0.607222 | 2.414749 | -0.821818 | -2.307648 | +| 10 | -1.252411 | 2.049880 | -0.174482 | -3.629631 | -0.929407 | 3.323441 | +| 11 | 2.544807 | -1.431356 | 0.077776 | 2.937317 | 0.068855 | -3.113037 | +| 12 | 1.955353 | -1.538378 | -0.859109 | -0.119003 | 0.992937 | 4.018547 | +| 13 | -6.393723 | 0.366534 | 3.704914 | -3.591248 | -3.582922 | -3.663162 | +| 14 | -1.736153 | 0.482206 | 1.122250 | -0.222275 | -1.084145 | -2.834801 | +| 15 | -1.184280 | 2.288819 | -0.412212 | 2.496544 | -0.526989 | -1.682892 | +| 16 | 0.232773 | 2.186338 | -0.732599 | -2.939404 | -0.340164 | 3.782349 | +| 17 | 0.341843 | -1.835766 | -0.650601 | 6.829597 | 2.642296 | -6.299164 | diff --git a/docs/controller-client-api-compatibility-requirements.md b/docs/controller-client-api-compatibility-requirements.md new file mode 100644 index 0000000..9078c23 --- /dev/null +++ b/docs/controller-client-api-compatibility-requirements.md @@ -0,0 +1,143 @@ +# ControllerClient API 兼容逆向约束 + +> 记录时间:2026-04-24 +> 适用仓库:`flyshot-replacement` +> 当前阶段:已落地 HTTP-only `ControllerClientCompat` 服务,并已将轨迹执行接入规划、触发时间轴和最小运行时骨架;不实现 `50001/TCP+JSON` 监听 + +## 1. 当前目标 + +本轮目标不是直接实现 `50001/TCP+JSON` 兼容网关,而是先把旧 `ControllerClient` 暴露的公开 API 做成可执行的逆向合同。 + +本轮交付物固定为两份文档: + +- `docs/controller-client-api-compatibility-requirements.md` +- `docs/controller-client-api-reverse-engineering.md` + +后续继续扩展 `Flyshot.ControllerClientCompat` 的方法覆盖、兼容测试矩阵或真实控制器联动时,必须以这两份文档为准,不再重新口头约定接口语义。 + +## 2. 范围边界 + +本轮只覆盖 `../FlyingShot/FlyingShot/Include/ControllerClient/ControllerClient.h` 中的 32 个公开方法。 + +分组如下: + +- 传输与版本:`ConnectServer`、`GetServerVersion`、`GetClientVersion` +- 机器人初始化:`SetUpRobot`、`SetUpRobotFromEnv`、`IsSetUp`、`SetShowTCP`、`GetName`、`GetDoF` +- 控制器状态:`SetActiveController`、`Connect`、`Disconnect`、`EnableRobot`、`DisableRobot`、`StopMove` +- 参数与 IO:`GetSpeedRatio`、`SetSpeedRatio`、`GetTCP`、`SetTCP`、`GetIO`、`SetIO` +- 运动与求解:`GetJointPosition`、`GetPose`、`GetNearestIK`、`MoveJoint`、`ExecuteTrajectory` +- 飞拍轨迹:`UploadFlyShotTraj`、`DeleteFlyShotTraj`、`ListFlyShotTraj`、`ExecuteFlyShotTraj`、`SaveTrajInfo`、`IsFlyShotTrajValid` + +明确不在本轮范围内: + +- `ControllerServer` 内部所有未公开 `_Xxx` 方法的完整实现复原 +- `50001` 网关代码、TCP server、JSON parser、命令路由实现 +- 真机 `10010 / 10012 / 60015` 联调 +- 抓包、hook、反汇编级协议完全坐实 +- 把当前 replacement 仓库的实现约束误写成旧系统事实 + +## 3. 证据源优先级 + +逆向结论必须按以下优先级交叉确认: + +1. `../FlyingShot/FlyingShot/Include/ControllerClient/ControllerClient.h` +2. `../FlyingShot/FlyingShot/Example/UseControllerClient.cpp` +3. `../FlyingShot/FlyingShot/Example/UseControllerClient.py` +4. `../FlyingShot/FlyingShot/Example/UseRealRobot.py` +5. `../FlyingShot/FlyingShot/Docs/用户手册/FANUC飞拍软件及SDK用户手册.md` +6. `../analysis/ControllerServer_analysis.md` +7. `../analysis/CommonMsg_protocol_analysis.md` +8. `../analysis/Trajectory_generation_algorithm_analysis.md` +9. `../FlyingShot/FlyingShot/Lib/libControllerClient.so` 与 `../FlyingShot/FlyingShot/Python/ControllerServer/ControllerServer.cpython-37m-x86_64-linux-gnu.so` 的字符串证据 + +使用规则: + +- 头文件负责定义“公开合同”:方法名、参数名、默认值、返回类型。 +- 示例和手册负责定义“典型调用方式”:调用顺序、Python 包装形态、用户侧常见用法。 +- 逆向分析文档和二进制字符串负责定义“服务端映射”和“协议线索”:`_Xxx` 方法名、错误文本、命令名、部分字段顺序。 +- 不能从当前 replacement 仓库的未来设计反推旧系统事实。 + +## 4. 文档填写规则 + +主归档文档 `docs/controller-client-api-reverse-engineering.md` 对每个 API 必须固定填写以下项目: + +- C++ 公开签名 +- Python 包装形态 +- 服务端归属 +- 协议 / 命令线索 +- 返回值与默认值 +- 典型工作流位置 +- 证据来源 +- 置信度 +- 待确认点 + +填写约束: + +- 只要精确 JSON 包结构还未恢复,就明确写成“命令名已知,JSON envelope 未恢复”。 +- 只要 Python 失败路径没有样例,就不能假装已经确认失败时的返回形态。 +- `GetServerVersion` 这类没有显式 `_GetServerVersion` 的接口,必须标成“协议分发层行为”,不能伪造一个服务端实现名。 +- `ConnectServer` 与 `GetClientVersion` 必须明确标成客户端侧行为,不写进服务端命令表。 +- 对 `GetJointPosition -> _GetJointPositions` 这种命名不一致,要单独注明。 +- 当同一结论同时来自头文件、示例和字符串时,置信度可标为“高”;只有示例间接体现时,最多“中”。 + +## 5. 验收条件 + +本轮逆向归档完成时,至少满足: + +- 32 个公开方法全部覆盖,且每个方法只出现一次 +- 29 个服务端相关 API 有明确映射或明确写成协议分发层行为 +- `ConnectServer`、`GetClientVersion` 两个客户端侧行为被明确排除在服务端命令表外 +- 四条工作流附录完整: + - 初始化工作流 + - 控制器状态工作流 + - 普通轨迹工作流 + - 飞拍轨迹工作流 +- 已知高置信协议字段至少记录: + - `GetSpeedRatio` + - `SetSpeedRatio` + - `GetIO` + - `SetIO` +- 仍未恢复的部分必须进入“待确认问题”清单,不能被静默略过 + +## 6. 当前已确认摘要 + +当前已确认的高价值结论如下: + +- `ControllerClient.h` 中共有 32 个公开方法。 +- 其中 29 个方法可与 `ControllerServer` 的公开 `_Xxx` 方法一一对齐。 +- `GetServerVersion` 能看到明确字符串证据,但未恢复到显式 `_GetServerVersion` 实现,更接近 `_ClientCB` / `_IsJsonValid` 所在的协议分发层。 +- `ConnectServer` 是客户端建立到 `127.0.0.1:50001` 的传输层动作。 +- `GetClientVersion` 更像客户端库自身版本查询,不进入服务端命令表。 +- `GetSpeedRatio` / `SetSpeedRatio`、`GetIO` / `SetIO` 已有较高置信度的底层字段顺序与 `MsgID` 证据。 +- `UploadFlyShotTraj`、`ListFlyShotTraj` 存在额外字符串线索: + - `StartUploadFlyShotTraj` + - `EndUploadFlyShotTraj` + - `GetNextListFlyShotTraj` +- `ExecuteFlyShotTraj`、`IsFlyShotTrajValid`、`SaveTrajInfo` 在工作流上有清晰分工,不能混成一个“执行轨迹”接口。 + +## 7. 下轮实现约束 + +当前 HTTP-only 兼容层已经可以承接公开 API 的主要服务端语义,并且 `ExecuteTrajectory` / `ExecuteFlyShotTraj` 已经进入 replacement 自身的规划与运行时链路。后续扩展必须遵守: + +- 先以逆向合同建命令表,再写 `TCP + JSON` 入口 +- 先做兼容测试矩阵,再补最小命令桩 +- 区分“旧系统事实”和“replacement 当前策略” +- 真机未接通前,允许实现层返回稳定错误或模拟状态,但不能反过来污染逆向文档 +- `50001/TCP+JSON` 抓包已经覆盖 `SetSpeedRatio` 与 `ExecuteFlyShotTraj(save_traj=true,use_cache=false)`,请求中没有显式 `JointLimits / acc_limit / jerk_limit / velocity / acceleration / jerk` 字段;因此规划限制的补齐必须作为 replacement-only 策略记录,不能写成旧公开 API 合同。 + +## 8. 当前 replacement 实现状态 + +以下内容是当前新实现的状态,不反推为旧系统事实: + +- `Flyshot.ControllerClientCompat` 继续作为 HTTP 控制器后端兼容服务,不启动 `50001/TCP+JSON` 监听。 +- `ExecuteTrajectory` 会先通过 `ICspPlanner` 规划普通轨迹,再把 `TrajectoryResult` 和最终关节位置交给 `IControllerRuntime`。 +- `ExecuteFlyShotTraj` 会从上传轨迹目录取出轨迹,通过 `SelfAdaptIcspPlanner` 规划并用 `ShotTimelineBuilder` 生成 `ShotEvent` / `TrajectoryDoEvent`。 +- HTTP 控制器已经按公开文档和抓包补齐 `ExecuteTrajectory(method, save_traj)` 与 `ExecuteFlyShotTraj(move_to_start, method, save_traj, use_cache, wait)` 参数,并继续兼容旧的裸 waypoint 数组和只传 `name` 的请求体。 +- 规划阶段会继续消费旧配置中的 `acc_limit / jerk_limit`。如果现场需要复现旧服务端不可见的保守约束,replacement 设计上使用内部 `planning_acceleration_scale` 限制规划加速度;该字段不属于旧 `ControllerClient` 公开 API,也不会通过 `50001` JSON 下发。 +- `method="icsp"` 与 `method="self-adapt-icsp"` 已接入当前规划器;`method="doubles"` 会被识别但返回显式未实现,不会静默降级成 ICSP。 +- `Flyshot.Runtime.Fanuc.Protocol` 已经固化 `10010` 状态帧、`10012` 命令帧和 `60015` J519 数据包的基础编解码,并使用逆向抓包样本覆盖最小测试;`10010` 当前现场确认固定 90B。 +- `Flyshot.Runtime.Fanuc` 已具备基础 Socket 客户端、程序启停、速度倍率/TCP/IO 参数命令和 J519 状态包驱动发送链路;稠密轨迹下发已按 `speed_ratio` 推进轨迹时间,并在收到机器人 UDP status 后按该 status sequence 回发命令。真实 R30iB 全流程现场联调仍需执行。 +- 2026-04-28 `UTTC_MS11` 抓包确认 J519 命令目标为 `deg`、导出 `JointDetialTraj.txt` 为 `rad`,`speed_ratio=0.5/0.7/1.0` 分别形成 `1851/1322/926` 个主运行 J519 包;实际执行不发送 464 行导出点,而是在 8ms 实发周期上按 `t_traj = t_send * speed_ratio` 采样,包数为 `floor(duration / (0.008 * speed_ratio)) + 1`。 +- 宿主已经提供只读 Web 状态页 `/status` 和状态快照 API `/api/status/snapshot`,用于查看兼容层初始化、机器人元数据和运行时快照。 +- `MoveJoint` 仍保持旧兼容语义中的直接运动接口,但状态写入已经统一经过运行时,而不是由兼容服务自己维护关节数组。 +- `GetNearestIK`、`SetUpRobotFromEnv` 当前已经暴露完整参数形状,但后端求解器 / 环境文件解析仍返回显式未实现。 diff --git a/docs/controller-client-api-reverse-engineering.md b/docs/controller-client-api-reverse-engineering.md new file mode 100644 index 0000000..fffaf64 --- /dev/null +++ b/docs/controller-client-api-reverse-engineering.md @@ -0,0 +1,562 @@ +# ControllerClient API 全量逆向归档 + +> 记录时间:2026-04-24 +> 适用仓库:`flyshot-replacement` +> 目标:为后续 `50001/TCP+JSON` 兼容网关实现提供只读合同 + +## 1. 总览 + +本次归档覆盖 `../FlyingShot/FlyingShot/Include/ControllerClient/ControllerClient.h` 中全部 32 个公开方法。 + +当前分类结果: + +- 29 个 API 可与 `ControllerServer` 公开 `_Xxx` 方法一一映射 +- 1 个 API 属于协议分发层行为:`GetServerVersion` +- 2 个 API 属于客户端侧行为:`ConnectServer`、`GetClientVersion` + +本文档中的“协议 / 命令线索”有两个层次: + +- 命令名:已从头文件、字符串或逆向文档坐实,例如 `SetUpRobot`、`GetIO` +- JSON envelope:请求 JSON 的精确键名、必填规则、整体结构;当前大多仍未完全恢复 + +因此,除 `GetSpeedRatio` / `SetSpeedRatio` / `GetIO` / `SetIO` 等已在命令通道层坐实字段顺序的接口外,其余接口默认只写“命令名已知,精确 JSON 包结构待确认”。 + +置信度定义: + +- 高:头文件 + 示例/手册 + 字符串/逆向文档三方交叉确认 +- 中:头文件 + 示例,或头文件 + 字符串,缺少第三方交叉 +- 低:仅有间接线索,未达到本轮归档主结论标准 + +## 2. 传输与版本 + +### `ConnectServer` + +- C++ 公开签名:`bool ConnectServer(const std::string &server_ip = "127.0.0.1", unsigned port = 50001);` +- Python 包装形态:`c.ConnectServer(server_ip="127.0.0.1", port=50001) -> bool` +- 服务端归属:客户端传输层行为,不进入 `ControllerServer._Xxx` 命令表 +- 协议 / 命令线索:建立到 `127.0.0.1:50001` 的 TCP 连接;JSON 负载只发生在连接建立之后 +- 返回值与默认值:默认 `server_ip="127.0.0.1"`、`port=50001`;成功返回 `true` +- 典型工作流位置:所有远程 API 的前置步骤 +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、`UseRealRobot.py` +- 置信度:高 +- 待确认点:重连、超时、握手细节未恢复 + +### `GetServerVersion` + +- C++ 公开签名:`bool GetServerVersion(std::string &version);` +- Python 包装形态:样例中表现为 `controller.GetServerVersion() -> str` +- 服务端归属:协议分发层行为;当前未恢复到显式 `_GetServerVersion` +- 协议 / 命令线索:命令名字符串 `GetServerVersion` 已坐实;服务端存在 `GetServerVersion success: {}.` 日志;精确 JSON envelope 未恢复 +- 返回值与默认值:C++ 为 `bool + out string`;Python 样例把结果当作直接字符串使用 +- 典型工作流位置:`ConnectServer` 之后、机器人初始化前后均可读取的元信息接口 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、`libControllerClient.so`、`ControllerServer.cpython-37m-x86_64-linux-gnu.so` +- 置信度:高 +- 待确认点:Python 失败路径的返回形态未看到样例;请求 JSON 的精确字段未恢复 + +### `GetClientVersion` + +- C++ 公开签名:`bool GetClientVersion(std::string &version);` +- Python 包装形态:未见公开样例;从包装库字符串看接口存在 +- 服务端归属:客户端本地行为,不进入服务端命令表 +- 协议 / 命令线索:`libControllerClient.so` 与 `PyControllerClient` 都有 `GetClientVersion` 符号;未见 `ControllerServer` 对应 `_Xxx` +- 返回值与默认值:C++ 为 `bool + out string`;Python 侧返回形态待确认 +- 典型工作流位置:客户端自检或 SDK 版本上报,不依赖服务端状态 +- 证据来源:`ControllerClient.h`、`libControllerClient.so`、`PyControllerClient.cpython-37m-x86_64-linux-gnu.so` +- 置信度:高 +- 待确认点:实际版本字符串来源与 Python 包装失败行为未恢复 + +## 3. 机器人初始化 + +### `SetUpRobot` + +- C++ 公开签名:`bool SetUpRobot(const std::string &robot_name);` +- Python 包装形态:`c.SetUpRobot("FANUC_LR_Mate_200iD") -> bool` +- 服务端归属:`ControllerServer._SetUpRobot` +- 协议 / 命令线索:命令名 `SetUpRobot` 已坐实;参数高概率是 `robot_name`;精确 JSON envelope 未恢复 +- 返回值与默认值:成功返回 `true` +- 典型工作流位置:创建客户端并连上 `50001` 后首先调用;旧说明明确它是最先执行的服务端初始化动作 +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:请求字段键名是否为 `robot_name` 仍需抓包或 hook 坐实 + +### `SetUpRobotFromEnv` + +- C++ 公开签名:`bool SetUpRobotFromEnv(const std::string &env_file);` +- Python 包装形态:未见公开样例;服务端与客户端字符串均存在 +- 服务端归属:`ControllerServer._SetUpRobotFromEnv` +- 协议 / 命令线索:命令名 `SetUpRobotFromEnv` 已坐实;参数高概率是环境文件绝对路径;精确 JSON envelope 未恢复 +- 返回值与默认值:成功返回 `true` +- 典型工作流位置:与 `SetUpRobot` 二选一;当现场通过环境文件而不是型号名初始化时使用 +- 证据来源:`ControllerClient.h`、`ControllerServer_analysis.md`、`ControllerServer` 字符串、`libControllerClient.so` +- 置信度:高 +- 待确认点:环境文件路径是否必须为绝对路径由注释可见,但服务端是否做额外归一化未恢复 + +### `IsSetUp` + +- C++ 公开签名:`bool IsSetUp();` +- Python 包装形态:`c.IsSetUp() -> bool` +- 服务端归属:`ControllerServer._IsSetUp` +- 协议 / 命令线索:命令名 `IsSetUp` 已坐实;精确 JSON envelope 未恢复 +- 返回值与默认值:直接返回布尔值 +- 典型工作流位置:`SetUpRobot` 或 `SetUpRobotFromEnv` 之后的状态确认 +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、`ControllerServer_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:服务端返回是否仅受机器人对象存在性控制,还是还依赖模型/控制器更深层状态,当前未完全恢复 + +### `SetShowTCP` + +- C++ 公开签名:`bool SetShowTCP(bool is_show = true, double axis_length = 0.1, size_t axis_size = 2);` +- Python 包装形态:`c.SetShowTCP(is_show=True, axis_length=0.1, axis_size=2) -> bool` +- 服务端归属:`ControllerServer._SetShowTCP` +- 协议 / 命令线索:命令名 `SetShowTCP` 已坐实;字符串中可见 `SetShowTCP is_show success/failed`;精确 JSON envelope 未恢复 +- 返回值与默认值:默认 `is_show=true`、`axis_length=0.1`、`axis_size=2` +- 典型工作流位置:初始化完成后、切换控制器前的仿真显示配置 +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、`ControllerServer_analysis.md`、`ControllerServer` 字符串 +- 置信度:高 +- 待确认点:仅仿真控制器生效还是真实控制器也接受该命令,当前未见明确负例 + +### `GetName` + +- C++ 公开签名:`std::string GetName();` +- Python 包装形态:`c.GetName() -> str` +- 服务端归属:`ControllerServer._GetName` +- 协议 / 命令线索:命令名 `GetName` 已坐实;精确 JSON envelope 未恢复 +- 返回值与默认值:直接返回机器人名称字符串 +- 典型工作流位置:完成机器人初始化后读取名称确认 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:失败时是返回空串还是异常/默认值,Python 样例未覆盖 + +### `GetDoF` + +- C++ 公开签名:`int GetDoF();` +- Python 包装形态:`c.GetDoF() -> int` +- 服务端归属:`ControllerServer._GetDoF` +- 协议 / 命令线索:命令名 `GetDoF` 已坐实;精确 JSON envelope 未恢复 +- 返回值与默认值:直接返回自由度整数 +- 典型工作流位置:完成机器人初始化后读取自由度 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:失败时的客户端行为未恢复 + +## 4. 控制器状态 + +### `SetActiveController` + +- C++ 公开签名:`bool SetActiveController(bool sim = true);` +- Python 包装形态:`c.SetActiveController(sim=True) -> bool`;真机样例使用 `sim=False` +- 服务端归属:`ControllerServer._SetActiveController` +- 协议 / 命令线索:命令名 `SetActiveController` 已坐实;字符串中可见 `SetActiveController sim success/failed` +- 返回值与默认值:默认 `sim=true` +- 典型工作流位置:机器人初始化后、`Connect` 前,用于在仿真控制器和真实控制器之间切换 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、`UseRealRobot.py`、SDK 手册、`ControllerServer_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:切换控制器时是否会隐式断开旧控制器,当前只有 `_DisconnectAll` 的间接线索 + +### `Connect` + +- C++ 公开签名:`bool Connect(const std::string &robot_ip);` +- Python 包装形态:`c.Connect("192.168.10.101") -> bool` +- 服务端归属:`ControllerServer._Connect` +- 协议 / 命令线索:命令名 `Connect` 已坐实;字符串中可见 `Connect ip success/failed` +- 返回值与默认值:成功返回 `true` +- 典型工作流位置:选定活动控制器后,通知服务端连接真实机器人 IP +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、`UseRealRobot.py`、SDK 手册、`ControllerServer_analysis.md`、`FANUC_realtime_comm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:JSON 键名是否为 `ip` 或 `robot_ip` 未恢复;仿真模式下是否接受同一命令仍需实现侧验证 + +### `Disconnect` + +- C++ 公开签名:`bool Disconnect();` +- Python 包装形态:`c.Disconnect() -> bool` +- 服务端归属:`ControllerServer._Disconnect` +- 协议 / 命令线索:命令名 `Disconnect` 已坐实;字符串中可见 `Disconnect success/failed` +- 返回值与默认值:直接返回布尔值 +- 典型工作流位置:真实控制器断开或工作流结束时调用 +- 证据来源:`ControllerClient.h`、`ControllerServer_analysis.md`、`ControllerServer` 字符串、`libControllerClient.so` +- 置信度:高 +- 待确认点:是否同步清理状态通道 / 命令通道 / 伺服通道,当前只在 FANUC 分析文档看到对象链,并未完全坐实释放顺序 + +### `EnableRobot` + +- C++ 公开签名:`bool EnableRobot(unsigned buffer_size = 2);` +- Python 包装形态:`c.EnableRobot() -> bool` +- 服务端归属:`ControllerServer._EnableRobot` +- 协议 / 命令线索:命令名 `EnableRobot` 已坐实;字符串中可见 `EnableRobot success/failed` +- 返回值与默认值:默认 `buffer_size=2` +- 典型工作流位置:`Connect` 后使能机器人;多数运动 API 前置条件 +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、`UseRealRobot.py`、`ControllerServer_analysis.md`、`FANUC_realtime_comm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:`buffer_size` 在旧服务端具体如何映射到伺服缓冲区参数,当前未完全恢复 + +### `DisableRobot` + +- C++ 公开签名:`bool DisableRobot();` +- Python 包装形态:`c.DisableRobot() -> bool` +- 服务端归属:`ControllerServer._DisableRobot` +- 协议 / 命令线索:命令名 `DisableRobot` 已坐实;字符串中可见 `DisableRobot success/failed` +- 返回值与默认值:直接返回布尔值 +- 典型工作流位置:停机或退出前关闭机器人使能 +- 证据来源:`ControllerClient.h`、`ControllerServer_analysis.md`、`FANUC_realtime_comm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:与 `StopMove` 的先后顺序约束未恢复 + +### `StopMove` + +- C++ 公开签名:`bool StopMove();` +- Python 包装形态:`c.StopMove() -> bool` +- 服务端归属:`ControllerServer._StopMove` +- 协议 / 命令线索:命令名 `StopMove` 已坐实;字符串中可见 `StopMove success/failed` +- 返回值与默认值:直接返回布尔值 +- 典型工作流位置:当前运动中止;Python 示例中调用后会再次 `EnableRobot()` +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、`ControllerServer_analysis.md`、`FANUC_realtime_comm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:是否会清空控制器执行队列、是否必须重新使能,当前只从示例看到倾向性而非硬约束 + +## 5. 参数与 IO + +### `GetSpeedRatio` + +- C++ 公开签名:`double GetSpeedRatio();` +- Python 包装形态:`c.GetSpeedRatio() -> float` +- 服务端归属:`ControllerServer._GetSpeedRatio` +- 协议 / 命令线索:命令通道 `MsgID = 0x2206`;响应字段为 `ratio_int` 与 `result_code`;成功后客户端将 `ratio_int / 100.0` +- 返回值与默认值:直接返回 `0~1` 之间的倍率 +- 典型工作流位置:连机并使能后读取当前速度倍率 +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、`CommonMsg_protocol_analysis.md` +- 置信度:高 +- 待确认点:服务端 JSON 层到命令通道层的参数桥接结构未恢复 + +### `SetSpeedRatio` + +- C++ 公开签名:`bool SetSpeedRatio(double ratio);` +- Python 包装形态:`c.SetSpeedRatio(0.8) -> bool` +- 服务端归属:`ControllerServer._SetSpeedRatio` +- 协议 / 命令线索:命令通道 `MsgID = 0x2207`;请求字段为 `ratio_int_0_100`;输入 `double` 先乘 `100` 并夹到 `[0,100]` +- 返回值与默认值:成功返回 `true` +- 典型工作流位置:连机并使能后修改控制器速度倍率 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、`UseRealRobot.py`、SDK 手册、`ControllerServer_analysis.md`、`CommonMsg_protocol_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:ratio 越界时客户端是裁剪还是直接失败,当前更偏向裁剪,但缺少公开负例 + +### `GetTCP` + +- C++ 公开签名:`bool GetTCP(Pose &tcp);` +- Python 包装形态:`res, tcp = c.GetTCP()` +- 服务端归属:`ControllerServer._GetTCP` +- 协议 / 命令线索:控制器命令通道底层接口 `GetTCP` 已有 `MsgID = 0x2200` 证据,但 `ControllerClient` JSON 层请求结构未完全恢复 +- 返回值与默认值:C++ 为 `bool + out Pose`;Python 为 `(bool, Pose)` +- 典型工作流位置:连机后读取当前控制器 TCP +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、`ControllerServer_analysis.md`、`CommonMsg_protocol_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:JSON 层是否支持多个 TCP ID,目前只看到底层命令通道有 `tcp_id` + +### `SetTCP` + +- C++ 公开签名:`bool SetTCP(const Pose &tcp);` +- Python 包装形态:`c.SetTCP(tcp) -> bool` +- 服务端归属:`ControllerServer._SetTCP` +- 协议 / 命令线索:控制器命令通道底层接口 `SetTCP` 已有 `MsgID = 0x2201` 证据;JSON 层精确字段未恢复 +- 返回值与默认值:成功返回 `true` +- 典型工作流位置:连机后修改控制器 TCP +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、`ControllerServer_analysis.md`、`CommonMsg_protocol_analysis.md`、`FANUC_realtime_comm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:JSON 层是否也暴露 `tcp_id` 概念尚未恢复 + +### `GetIO` + +- C++ 公开签名:`bool GetIO(unsigned port, bool &value, IOType type = kIOTypeDI);` +- Python 包装形态:`res, value = c.GetIO(port=1, io_type=IOType.kIOTypeDI)` +- 服务端归属:`ControllerServer._GetIO` +- 协议 / 命令线索:命令通道 `MsgID = 0x2208`;请求字段顺序是 `io_type`、`io_index`;响应字段是 `result_code`、`io_value` +- 返回值与默认值:默认 `type = kIOTypeDI`;C++ 为 `bool + out bool`;Python 为 `(bool, bool)` +- 典型工作流位置:连机后读取 DI/DO/RI/RO 等 IO 值 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、`CommonMsg_protocol_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:高层 JSON 是否把字段命名为 `port` / `value` / `type`,还是 `io_index` / `io_type`,当前未抓到完整包 + +### `SetIO` + +- C++ 公开签名:`bool SetIO(unsigned port, bool value, IOType type = kIOTypeDO);` +- Python 包装形态:`c.SetIO(port=1, value=True, io_type=IOType.kIOTypeDO) -> bool` +- 服务端归属:`ControllerServer._SetIO` +- 协议 / 命令线索:命令通道 `MsgID = 0x2209`;请求字段顺序是 `io_type`、`io_index`、`io_value` +- 返回值与默认值:默认 `type = kIOTypeDO` +- 典型工作流位置:连机后设置数字输出;飞拍链路之外的普通 IO 调试也会用到 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、`CommonMsg_protocol_analysis.md`、`FANUC_realtime_comm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:布尔值在高层 JSON 中是否以 `true/false` 传输、在命令通道中以 `float` 传输,当前只坐实了命令通道层 + +## 6. 运动与求解 + +### `GetJointPosition` + +- C++ 公开签名:`bool GetJointPosition(JointPositions &joint_position);` +- Python 包装形态:`res, joints = c.GetJointPosition()` +- 服务端归属:`ControllerServer._GetJointPositions` +- 协议 / 命令线索:命令名 `GetJointPositions` 已坐实;客户端公开 API 名与服务端方法名存在单复数差异 +- 返回值与默认值:C++ 为 `bool + out JointPositions`;Python 为 `(bool, JointPositions)` +- 典型工作流位置:读取当前关节角,常作为 `GetNearestIK` 的 seed 或 `MoveJoint` 的基准 +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:JSON 层返回数组字段键名未恢复 + +### `GetPose` + +- C++ 公开签名:`bool GetPose(Pose &pose);` +- Python 包装形态:`res, pose = c.GetPose()` +- 服务端归属:`ControllerServer._GetPose` +- 协议 / 命令线索:命令名 `GetPose` 已坐实;字符串中可见 `GetPose success/failed` +- 返回值与默认值:C++ 为 `bool + out Pose`;Python 为 `(bool, Pose)` +- 典型工作流位置:读取当前 TCP 位姿,常与 `GetNearestIK` 配套使用 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:位姿坐标系定义虽然在 `Types.h` 中体现为 7 元数组,但 JSON 层字段结构未恢复 + +### `GetNearestIK` + +- C++ 公开签名:`bool GetNearestIK(const Pose &pose, const JointPositions &seed, JointPositions &ik);` +- Python 包装形态:`res, ik = c.GetNearestIK(pose, joint_seed=joints)` +- 服务端归属:`ControllerServer._GetNearestIK` +- 协议 / 命令线索:命令名 `GetNearestIK` 已坐实;字符串中可见 `GetNearestIK success/failed` +- 返回值与默认值:C++ 为 `bool + out JointPositions`;Python 为 `(bool, JointPositions)` +- 典型工作流位置:先 `GetPose` 得到当前位姿,再构造目标位姿,并使用当前关节或邻近关节作为 seed 求最近 IK +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、`ControllerServer_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:JSON 层中 seed 参数键名在 Python 里表现为 `joint_seed`,但 C++ 注释写的是 `seed`;高层字段命名仍待确认 + +### `MoveJoint` + +- C++ 公开签名:`bool MoveJoint(const JointPositions &joint_position);` +- Python 包装形态:`c.MoveJoint(home_joint) -> bool` +- 服务端归属:`ControllerServer._MoveJoint` +- 协议 / 命令线索:命令名 `MoveJoint` 已坐实;字符串中可见 `MoveJoint waypoint success/failed` +- 返回值与默认值:成功返回 `true` +- 典型工作流位置:点到点回零或移动到飞拍轨迹起点 +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、`ControllerServer_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:移动速度/平滑参数是否固定在服务端内部,公开 API 未暴露 + +### `ExecuteTrajectory` + +- C++ 公开签名:`bool ExecuteTrajectory(const std::vector &waypoints, const std::string &method = "icsp", bool save_traj = false);` +- Python 包装形态:`c.ExecuteTrajectory(waypoints=[...], method="icsp", save_traj=True) -> bool` +- 服务端归属:`ControllerServer._ExecuteTrajectory` +- 协议 / 命令线索:命令名 `ExecuteTrajectory` 已坐实;`method` 至少支持 `icsp` 与 `doubles` +- 返回值与默认值:默认 `method="icsp"`、`save_traj=false` +- 典型工作流位置:普通多点轨迹执行,不带飞拍 IO;输入是关节空间稀疏 waypoint,而不是笛卡尔点 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、`Trajectory_generation_algorithm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:JSON 中 waypoint 列表的键名和序列化结构未恢复;`doubles` 的失败语义未在公开样例中体现 + +## 7. 飞拍轨迹 + +### `UploadFlyShotTraj` + +- C++ 公开签名:`bool UploadFlyShotTraj(const std::string &name, const std::vector &waypoints, const std::vector &shot_flags, const std::vector &offset_values, const std::vector> &addrs);` +- Python 包装形态:`c.UploadFlyShotTraj(name="test_traj", waypoints=..., shot_flags=..., offset_values=..., addrs=...) -> bool` +- 服务端归属:`ControllerServer._UploadFlyShotTraj` +- 协议 / 命令线索:命令名 `UploadFlyShotTraj` 已坐实;客户端字符串中可见 `StartUploadFlyShotTraj` 与 `EndUploadFlyShotTraj`,说明上传阶段很可能分为开始 / 传输 / 结束三个子步骤 +- 返回值与默认值:成功返回 `true`;无默认参数 +- 典型工作流位置:运行时动态构造飞拍轨迹时,先把轨迹定义登记到服务端 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、`Trajectory_generation_algorithm_analysis.md`、`libControllerClient.so`、`ControllerServer` 字符串 +- 置信度:高 +- 待确认点:JSON 上传是否一次性发送完整 payload,还是像字符串暗示的那样分片上传,当前未完全恢复 + +### `DeleteFlyShotTraj` + +- C++ 公开签名:`bool DeleteFlyShotTraj(const std::string &name);` +- Python 包装形态:`c.DeleteFlyShotTraj(name="test_traj") -> bool` +- 服务端归属:`ControllerServer._DeleteFlyShotTraj` +- 协议 / 命令线索:命令名 `DeleteFlyShotTraj` 已坐实;字符串中可见 `DeleteFlyShotTraj {} success/failed` +- 返回值与默认值:成功返回 `true` +- 典型工作流位置:删除已上传的临时飞拍轨迹定义 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、SDK 手册、`ControllerServer_analysis.md`、`PyControllerClient` 字符串、`ControllerServer` 字符串 +- 置信度:高 +- 待确认点:删除配置内固有轨迹名与删除运行时上传轨迹时是否走同一条路径,当前未恢复 + +### `ListFlyShotTraj` + +- C++ 公开签名:`std::vector ListFlyShotTraj();` +- Python 包装形态:`c.ListFlyShotTraj() -> list[str]` +- 服务端归属:`ControllerServer._ListFlyShotTraj` +- 协议 / 命令线索:命令名 `ListFlyShotTraj` 已坐实;客户端与服务端字符串都出现 `GetNextListFlyShotTraj`,说明底层列举很可能是迭代式获取,而不是一次性返回整个数组 +- 返回值与默认值:直接返回轨迹名称列表 +- 典型工作流位置:查看当前服务端已登记的飞拍轨迹 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py`、`ControllerServer_analysis.md`、`libControllerClient.so`、`ControllerServer` 字符串 +- 置信度:高 +- 待确认点:高层 JSON 是否真的直接返回数组,还是客户端内部循环拉取后再拼成数组,当前未完全恢复 + +### `ExecuteFlyShotTraj` + +- C++ 公开签名:`bool ExecuteFlyShotTraj(const std::string &name, const bool move_to_start = false, const std::string &method = "icsp", bool save_traj = false, bool use_cache = false);` +- Python 包装形态:`c.ExecuteFlyShotTraj(name="002", move_to_start=True, method="icsp", save_traj=True, use_cache=False) -> bool` +- 服务端归属:`ControllerServer._ExecuteFlyShotTraj` +- 协议 / 命令线索:命令名 `ExecuteFlyShotTraj` 已坐实;字符串中出现 `move_to_start`、`use_cache`;伪代码级逆向已恢复其主流程 +- 返回值与默认值:默认 `move_to_start=false`、`method="icsp"`、`save_traj=false`、`use_cache=false` +- 典型工作流位置:对已存在的飞拍轨迹定义执行“生成 + 挂接 DO 时间轴 + 执行” +- 证据来源:`ControllerClient.h`、`UseControllerClient.cpp`、`UseControllerClient.py`、`UseRealRobot.py`、SDK 手册、`ControllerServer_analysis.md`、`Trajectory_generation_algorithm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:JSON 包结构未恢复;`use_cache` 的缓存键是否只按 `name`,当前从伪代码判断是,但未见更底层证据 + +### `SaveTrajInfo` + +- C++ 公开签名:`bool SaveTrajInfo(const std::string &name, const std::string &method = "icsp");` +- Python 包装形态:未见运行样例,但包装库和服务端都有明确字符串;语义可由手册和分析文档确认 +- 服务端归属:`ControllerServer._SaveTrajInfo` +- 协议 / 命令线索:命令名 `SaveTrajInfo` 已坐实;字符串中可见 `SaveTrajInfo {} success/failed` +- 返回值与默认值:默认 `method="icsp"`;`self-adapt-icsp` 在执行时保存分析文件会回落成 `icsp` 导出语义 +- 典型工作流位置:按给定 `name + method` 生成并导出轨迹分析文件,例如 `JointTraj.txt`、`CartDetialTraj.txt` +- 证据来源:`ControllerClient.h`、`ControllerServer_analysis.md`、`Trajectory_generation_algorithm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:Python 包装是否直接暴露该方法并返回 `bool`,虽然字符串存在,但公开示例未调用 + +### `IsFlyShotTrajValid` + +- C++ 公开签名:`bool IsFlyShotTrajValid(double &time, const std::string &name, const std::string &method = "icsp", bool save_traj = false);` +- Python 包装形态:`valid, time_sec = c.IsFlyShotTrajValid("EOL9_EAU_90", "icsp", save_traj=True)` +- 服务端归属:`ControllerServer._IsFlyShotTrajValid` +- 协议 / 命令线索:命令名 `IsFlyShotTrajValid` 已坐实;作用是“生成 + 合法性检查 + 返回轨迹总时长” +- 返回值与默认值:默认 `method="icsp"`、`save_traj=false`;C++ 为 `bool + out double`;Python 为 `(bool, float)` +- 典型工作流位置:在执行飞拍轨迹前预校验;也可与 `save_traj=True` 结合导出分析文件 +- 证据来源:`ControllerClient.h`、`UseControllerClient.py` 中的注释样例、`ControllerServer_analysis.md`、`Trajectory_generation_algorithm_analysis.md`、二进制字符串 +- 置信度:高 +- 待确认点:`method="self-adapt-icsp"` 是否被该接口完整支持,分析文档显示公开手册更强调 `icsp` / `doubles`,而示例注释里又出现 `self-adapt-icsp`,这里仍需后续抓包或真环境验证 + +## 8. 四条旧 SDK 工作流 + +### 8.1 初始化工作流 + +```text +ControllerClient() + -> ConnectServer("127.0.0.1", 50001) + -> SetUpRobot("FANUC_LR_Mate_200iD") 或 SetUpRobotFromEnv(...) + -> IsSetUp() + -> GetName() + -> GetDoF() + -> SetShowTCP(...) +``` + +说明: + +- 这是最常见的仿真或真机前置流程。 +- `SetUpRobot` 是旧说明里明确要求首先执行的服务端初始化动作。 + +### 8.2 控制器状态工作流 + +```text +SetActiveController(sim=True/False) + -> Connect(robot_ip) + -> EnableRobot(buffer_size=2) + -> GetSpeedRatio() / SetSpeedRatio(...) + -> GetTCP() / SetTCP(...) + -> GetIO() / SetIO(...) + -> StopMove() + -> DisableRobot() + -> Disconnect() +``` + +说明: + +- 真机样例会使用 `sim=False`。 +- `StopMove()` 之后,Python 示例里会再次 `EnableRobot()`,说明停止运动后常伴随重新使能。 + +### 8.3 普通轨迹工作流 + +```text +GetJointPosition() + -> GetPose() + -> GetNearestIK(pose, seed) + -> MoveJoint(home_joint) + -> ExecuteTrajectory(waypoints, method="icsp", save_traj=True) +``` + +说明: + +- 输入是关节空间稀疏 waypoint。 +- `ExecuteTrajectory` 至少支持 `icsp` 与 `doubles`。 + +### 8.4 飞拍轨迹工作流 + +#### 方案 A:轨迹名已在配置中存在 + +```text +name + -> IsFlyShotTrajValid(name, "icsp", save_traj=True) + -> ExecuteFlyShotTraj(name, move_to_start=True, method="icsp", save_traj=True, use_cache=False) + -> SaveTrajInfo(name, "icsp") +``` + +#### 方案 B:客户端动态上传飞拍轨迹 + +```text +UploadFlyShotTraj(name, waypoints, shot_flags, offset_values, addrs) + -> ListFlyShotTraj() + -> IsFlyShotTrajValid(name, "icsp", save_traj=True) + -> ExecuteFlyShotTraj(name, move_to_start=True, method="self-adapt-icsp", save_traj=True, use_cache=True) + -> DeleteFlyShotTraj(name) +``` + +说明: + +- `UploadFlyShotTraj` 只负责登记轨迹定义。 +- `IsFlyShotTrajValid` 负责“生成 + 合法性检查”。 +- `ExecuteFlyShotTraj` 负责“生成 + 挂接 `TrajectoryDO` + 执行”。 +- `SaveTrajInfo` 负责导出分析文件。 + +## 9. 已知高置信协议线索 + +当前能直接用于后续实现的高置信协议结论如下: + +- 传输层为 `TCP + JSON` 风格协议,不是 HTTP/gRPC。 +- JSON 层至少存在 `method` 字段,服务端存在 `_ClientCB` 与 `_IsJsonValid`。 +- 命令通道字段已确认: + - `GetSpeedRatio`:`MsgID = 0x2206` + - `SetSpeedRatio`:`MsgID = 0x2207` + - `GetIO`:`MsgID = 0x2208` + - `SetIO`:`MsgID = 0x2209` +- 2026-04-28 `UTTC_MS11` 抓包中,`speed_ratio=0.7` 的效果能从 UDP 60015 主运行段时间尺度反推出来,但机器人侧 `TCP 10012` 未出现 `0x2207 SetSpeedRatio`;兼容实现不能只依赖一次 10012 命令来表达执行倍率,还要在 J519 发送时间轴上应用当前倍率。J519 实发时间为 `t_send = k * 0.008`,原轨迹采样时间为 `t_traj = t_send * speed_ratio`,包数为 `floor(duration / (0.008 * speed_ratio)) + 1`。 +- 飞拍轨迹相关额外字符串线索: + - `StartUploadFlyShotTraj` + - `EndUploadFlyShotTraj` + - `GetNextListFlyShotTraj` + - `move_to_start` + - `use_cache` + - `shot_flags` + - `offset_values` + - `addr` / `addrs` + +## 10. 待确认问题 + +以下问题本轮故意保留,不冒充已确认结论: + +1. `50001/TCP+JSON` 请求 JSON 的精确 envelope 结构、字段必填规则、响应包统一格式。 +2. `GetServerVersion` 在高层 JSON 中的完整请求 / 响应字段。 +3. `GetClientVersion` 的实际版本字符串来源,以及 Python 包装失败路径。 +4. `ListFlyShotTraj` 是高层一次性返回数组,还是客户端内部循环 `GetNextListFlyShotTraj` 后再拼装列表。 +5. `UploadFlyShotTraj` 是否采用开始 / 数据 / 结束的多阶段上传协议。 +6. `IsFlyShotTrajValid` 对 `self-adapt-icsp` 的真实支持边界。 +7. `SetTCP` / `GetTCP` 在高层 JSON 中是否暴露 `tcp_id` 概念。 +8. `SetActiveController` 切换控制器时是否会隐式触发 `_DisconnectAll`。 + +## 11. 后续实现使用方式 + +等继续扩展 `Flyshot.ControllerClientCompat` 时,建议按以下顺序使用本文档: + +1. 先把 32 个 API 按本文档拆成命令表。 +2. 先实现高置信、状态简单的接口: + - `GetServerVersion` + - `SetUpRobot` + - `IsSetUp` + - `GetName` + - `GetDoF` + - `GetSpeedRatio` + - `SetSpeedRatio` + - `GetIO` + - `SetIO` +3. 再实现返回复杂结构的接口: + - `GetTCP` + - `GetJointPosition` + - `GetPose` + - `GetNearestIK` +4. 最后实现飞拍轨迹相关接口,并把本文档中的“待确认问题”逐项收敛成兼容测试。 diff --git a/docs/fanuc-10010-state-frame.md b/docs/fanuc-10010-state-frame.md new file mode 100644 index 0000000..476bb02 --- /dev/null +++ b/docs/fanuc-10010-state-frame.md @@ -0,0 +1,165 @@ +# FANUC TCP 10010 状态帧字段说明 + +本文档整理当前现场真实抓包中 `TCP 10010` 状态通道的已确认字段布局,并明确哪些结论已经由抓包和代码验证,哪些仍然只是工作假设。 + +读取时间:2026-05-03 + +## 1. 结论范围 + +本文基于以下证据整理: + +- `../analysis/UTTC_20260428_packet_validation.md` +- `../analysis/J519_stream_motion_analysis.md` +- `Rvbust/uttc-20260428/20260428.pcap` +- `src/Flyshot.Runtime.Fanuc/Protocol/FanucStateProtocol.cs` +- `tests/Flyshot.Core.Tests/FanucProtocolTests.cs` + +当前结论仅覆盖现场已确认的 `R30iB + RVBUSTSM` 这一路状态通道行为,不提前推广为所有 FANUC 机型或所有旧版本协议的通用结论。 + +## 2. 通道性质 + +真实抓包显示,`TCP 10010` 是控制柜到上位机的单向状态流: + +- 上位机先主动建立 TCP 连接。 +- 建连后,带应用层 payload 的业务包全部来自 `192.168.10.11:10010 -> 192.168.10.10:41726`。 +- 上位机在该通道上只回 TCP `ACK`,没有观察到应用层请求体。 + +因此当前实现应把 `10010` 当作“持续推送的固定长度状态帧”处理,而不是像 `TCP 10012` 那样按请求/响应语义建模。 + +## 3. 整体布局 + +当前现场抓包确认,状态帧固定为 `90B`: + +```text +doz 3 bytes +length u32 = 90 +msg_id u32 +pose[6] f32 +joint_or_ext[9] f32 +tail[4] u32 +zod 3 bytes +``` + +其中: + +- 帧头 magic 固定为 `doz` +- 帧尾 magic 固定为 `zod` +- 长度字段固定为 `0x5a`,即 `90` +- 当前全量抓包中 `msg_id` 恒为 `0` +- `tail[4]` 当前全量抓包中恒为 `(2, 0, 0, 1)` + +## 4. 样例帧 + +以下样例帧来自 `20260428.pcap` 中首条 `tcp.port == 10010 && tcp.len > 0` 的 payload: + +```text +646f7a0000005a000000004388a23243f1ed7f43e9de6bc265031ec2b33cc3c278e0153f8742f53c3f128dbc929529bc7861d63cb0184c3c1ca1a7000000000000000000000000000000020000000000000000000000017a6f64 +``` + +对应解析值: + +- `pose[6]` + - `273.26715` + - `483.85544` + - `467.73764` + - `-57.253044` + - `-89.618675` + - `-62.21883` +- `joint_or_ext[9]` + - `1.0567309` + - `0.011662138` + - `-0.01789339` + - `-0.015160045` + - `0.02149596` + - `0.009560025` + - `0` + - `0` + - `0` +- `tail[4]` + - `2` + - `0` + - `0` + - `1` + +## 5. 正式字段表 + +| 偏移 | 长度 | 类型 | 样例值(hex) | 样例值(解析后) | 当前推断含义 | +| --- | --- | --- | --- | --- | --- | +| `0x00` | `3` | `char[3]` | `64 6f 7a` | `"doz"` | 固定帧头 magic | +| `0x03` | `4` | `u32 be` | `00 00 00 5a` | `90` | 帧总长度 | +| `0x07` | `4` | `u32 be` | `00 00 00 00` | `0` | `msg_id`,当前抓包全为 `0` | +| `0x0B` | `4` | `f32 be` | `43 88 a2 32` | `273.26715` | `pose[0]`,推断为 TCP `X(mm)` | +| `0x0F` | `4` | `f32 be` | `43 f1 ed 7f` | `483.85544` | `pose[1]`,推断为 TCP `Y(mm)` | +| `0x13` | `4` | `f32 be` | `43 e9 de 6b` | `467.73764` | `pose[2]`,推断为 TCP `Z(mm)` | +| `0x17` | `4` | `f32 be` | `c2 65 03 1e` | `-57.253044` | `pose[3]`,推断为姿态角 `W(deg)` | +| `0x1B` | `4` | `f32 be` | `c2 b3 3c c3` | `-89.618675` | `pose[4]`,推断为姿态角 `P(deg)` | +| `0x1F` | `4` | `f32 be` | `c2 78 e0 15` | `-62.21883` | `pose[5]`,推断为姿态角 `R(deg)` | +| `0x23` | `4` | `f32 be` | `3f 87 42 f5` | `1.0567309` | `joint_or_ext[0]`,推断为 `J1(rad)` | +| `0x27` | `4` | `f32 be` | `3c 3f 12 8d` | `0.011662138` | `joint_or_ext[1]`,推断为 `J2(rad)` | +| `0x2B` | `4` | `f32 be` | `bc 92 95 29` | `-0.01789339` | `joint_or_ext[2]`,推断为 `J3(rad)` | +| `0x2F` | `4` | `f32 be` | `bc 78 61 d6` | `-0.015160045` | `joint_or_ext[3]`,推断为 `J4(rad)` | +| `0x33` | `4` | `f32 be` | `3c b0 18 4c` | `0.02149596` | `joint_or_ext[4]`,推断为 `J5(rad)` | +| `0x37` | `4` | `f32 be` | `3c 1c a1 a7` | `0.009560025` | `joint_or_ext[5]`,推断为 `J6(rad)` | +| `0x3B` | `4` | `f32 be` | `00 00 00 00` | `0` | `joint_or_ext[6]`,扩展轴槽位,当前样本恒 `0` | +| `0x3F` | `4` | `f32 be` | `00 00 00 00` | `0` | `joint_or_ext[7]`,扩展轴槽位,当前样本恒 `0` | +| `0x43` | `4` | `f32 be` | `00 00 00 00` | `0` | `joint_or_ext[8]`,扩展轴槽位,当前样本恒 `0` | +| `0x47` | `4` | `u32 be` | `00 00 00 02` | `2` | `tail[0]`,诊断状态字,物理语义未坐实 | +| `0x4B` | `4` | `u32 be` | `00 00 00 00` | `0` | `tail[1]`,诊断状态字,物理语义未坐实 | +| `0x4F` | `4` | `u32 be` | `00 00 00 00` | `0` | `tail[2]`,诊断状态字,物理语义未坐实 | +| `0x53` | `4` | `u32 be` | `00 00 00 01` | `1` | `tail[3]`,诊断状态字,物理语义未坐实 | +| `0x57` | `3` | `char[3]` | `7a 6f 64` | `"zod"` | 固定帧尾 magic | + +## 6. 已确认结论 + +### 6.1 已由真实抓包确认 + +1. `TCP 10010` 是独立状态流,不是 `TCP 10012` 的请求/响应复用。 +2. 当前现场状态帧固定为 `90B`,不是早期静态分析里出现过的 `134B`。 +3. `msg_id` 在 `20260428.pcap` 当前全量样本中恒为 `0`。 +4. `tail[4]` 在 `20260428.pcap` 当前全量样本中恒为 `(2, 0, 0, 1)`。 +5. `pose[6]` 的量纲表现符合 `X/Y/Z(mm) + W/P/R(deg)`。 +6. `joint_or_ext[6..8]` 在当前现场样本中恒为 `0`。 + +### 6.2 已由数值范围和交叉对照强支持 + +1. `joint_or_ext[0..5]` 更像关节角 `rad`,而不是 `deg`。 +2. 该判断与 `../analysis/UTTC_20260428_packet_validation.md` 的结论一致。 +3. 该判断也与 `UDP 60015` 响应包中的关节 `deg` 形成互补关系:二者不能简单视作同单位直接复用。 + +## 7. 待确认项 + +以下内容当前不要写死为最终协议真义: + +1. `tail[4]` 四个 `u32` 分别代表什么控制器语义。 +2. `msg_id` 是否在其他控制柜版本、程序状态或异常态下会出现非零值。 +3. `pose[3..5]` 是否可以严格命名为 FANUC 标准 `W/P/R`,还是只是与其数值表现一致。 +4. `joint_or_ext[6..8]` 在带外部轴的现场是否仍复用同一布局。 + +## 8. 与当前代码实现的对齐情况 + +当前仓库里 `Flyshot.Runtime.Fanuc` 已按 `90B` 固定帧解析: + +- `src/Flyshot.Runtime.Fanuc/Protocol/FanucStateProtocol.cs` +- `tests/Flyshot.Core.Tests/FanucProtocolTests.cs` + +当前实现已经与抓包对齐的部分: + +1. 固定长度 `90B` +2. `doz ... zod` 帧头帧尾校验 +3. `pose[6] + joint_or_ext[9] + tail[4]` 的字节布局 +4. `tail[4]` 原样保留到 `ControllerStateSnapshot.StateTailWords` + +当前仍建议后续关注的点: + +1. `FanucStateFrame` 已把该字段从 `JointDegrees` 更正为 `JointRadians`,后续新增代码应继续沿用弧度制命名。 +2. 如果后续状态页或运行时逻辑需要直接展示该通道关节值,仍需明确标注这是 `10010` 的弧度值,避免和 `UDP 60015` 的 degree 语义混淆。 + +## 9. 建议用法 + +在当前 replacement 实现里,`TCP 10010` 更适合作为以下用途: + +1. 提供机器人当前笛卡尔位姿和关节反馈快照。 +2. 提供状态通道是否健康、是否陈旧的连接诊断依据。 +3. 保留 `tail[4]` 原始状态字,供现场排错或后续继续逆向。 + +当前不建议直接用 `tail[4]` 去驱动明确业务判断,除非后续拿到新的现场对照证据。 diff --git a/docs/fanuc-field-runtime-workflow.md b/docs/fanuc-field-runtime-workflow.md new file mode 100644 index 0000000..77ce683 --- /dev/null +++ b/docs/fanuc-field-runtime-workflow.md @@ -0,0 +1,182 @@ +# FANUC Field Runtime Workflow + +本文档记录当前现场主链路的 HTTP 调用顺序,以及每一步在 FANUC 三条真机通道上的动作。它替代旧 `ControllerClient` 工作流说明;旧 `50001/TCP+JSON` 入口不再作为运行目标。 + +## 1. 初始化 + +推荐使用聚合端点完成当前现场的一次性初始化: + +```bash +POST /init_mpc_robt +{ + "server_ip": "127.0.0.1", + "port": 50001, + "robot_name": "FANUC_LR_Mate_200iD", + "robot_ip": "192.168.10.11", + "sim": false +} +``` + +该端点内部顺序: + +1. `ConnectServer(server_ip, port)`:兼容旧参数形状,仅记录服务连接语义。 +2. `SetUpRobot(robot_name)`:加载机器人配置、关节限制和伺服周期。 +3. `SetActiveController(sim)`:选择仿真或 FANUC 真机运行时。 +4. `Connect(robot_ip)`:真机模式下依次建立 `TCP 10010` 状态通道、`TCP 10012` 命令通道、`UDP 60015` J519 运动通道。 +5. `EnableRobot(2)`:真机模式下执行 `StopProg("RVBUSTSM") -> Reset -> GetProgStatus("RVBUSTSM") -> StartProg("RVBUSTSM")`,随后允许 J519 在收到机器人 UDP status 包后回发下一帧命令。 + +也可以使用拆分端点按同样顺序调用: + +```text +POST /connect_server/?server_ip=127.0.0.1&port=50001 +POST /setup_robot/?robot_name=FANUC_LR_Mate_200iD +POST /set_active_controller/?sim=false +POST /connect_robot/?ip=192.168.10.11 +GET /enable_robot/?buffer_size=2 +``` + +## 2. 参数设置 + +规划约束参数: + +当前现场抓包已经确认,`50001/TCP+JSON` 的 `ExecuteFlyShotTraj(save_traj=true,use_cache=false)` 请求不会显式携带 `JointLimits / acc_limit / jerk_limit / velocity / acceleration / jerk`。因此新系统把规划约束分成两层处理: + +1. 旧 `RobotConfig.json` 中已有的 `acc_limit / jerk_limit` 继续作为模型加载时的基础倍率。 +2. 若旧系统导出的 `JointTraj.txt` 明显比当前 C# 规划更慢,使用 replacement-only 的内部校准参数限制规划阶段加速度,设计字段为 `planning_acceleration_scale`。 + +`planning_acceleration_scale` 只影响 `JointTraj.txt` 这类规划结果时间轴,不下发到 FANUC 控制柜,也不改变 J519 发送周期。若需要临时整体验证,也可以使用当前已有的 `planning_speed_scale`,但它是新系统兼容开关,不是旧抓包中出现的字段。 + +速度倍率: + +```bash +POST /set_speedRatio/ +{ "speed": 0.7 } +``` + +真机模式下会通过 `TCP 10012` 下发 `0x2207 SetSpeedRatio`,同时运行时保存当前倍率。`speed_ratio` 是执行期倍率,不参与 `IsFlyShotTrajValid` / `SaveTrajInfo` / `ExecuteFlyShotTraj(save_traj=true)` 的规划时长计算。J519 执行时仍按机器人 8ms 节拍更新目标,`speed_ratio` 只缩放原轨迹采样时间: + +```text +t_send = k * 0.008 +t_traj = t_send * speed_ratio +send_count = floor(duration / (0.008 * speed_ratio)) + 1 +``` + +TCP 和普通 IO: + +```text +POST /set_tcp/ body: { "x": 0, "y": 0, "z": 0 } +GET /get_tcp/ +POST /set_io/?port=7&value=true&io_type=DO +GET /get_io/?port=7&io_type=DO +``` + +飞拍触发 IO 不走独立 `TCP 10012 SetIO`,而是嵌入 `UDP 60015` J519 命令包的 `write_io_type/index/mask/value` 字段。 + +## 3. 点到点 MoveJoint + +```bash +POST /move_joint/ +{ "joints": [0.8532358, 0.03837953, -0.19235304, 0.0071595116, 0.109054826, 0.040055145] } +``` + +`MoveJoint` 不再直接把最终关节写成单个 J519 目标,而是按现场抓包确认的 PTP 临时轨迹执行: + +1. 从当前运行时状态读取当前关节坐标,单位为 `rad`。 +2. 以当前关节和目标关节构造关节空间直线。 +3. 用五次 smoothstep `10u^3 - 15u^4 + 6u^5` 生成起停平滑的进度。 +4. 真机执行时仍由 J519 层把 `rad` 转成 `deg`,并按当前 `speed_ratio` 重采样。 + +已确认抓包按响应 `status=15` 运动窗口统计: + +| 抓包 | speed_ratio | 运动窗口点数 | 运动窗口时长 | +|------|-------------|----------------------|----------| +| `2026042802-mvpoint.pcap` | 1.0 | 40 | 约 0.312s | +| `2026042802-mvpoint0.7.pcap` | 0.7 | 55 | 约 0.432s | +| `2026042802-mvpoint0.5.pcap` | 0.5 | 77 | 约 0.608s | + +抓包命令流在运动窗口前后还会持续发送保持不变的起点/终点目标;功能复刻以 `status=15` 运动窗口为点数口径,并把最后一个采样点压到目标关节。实际目标几乎严格位于“起点 -> 终点”的同一条关节空间直线上,`speed_ratio` 体现为 J519 发送时间轴上的减速重采样,而不是改变路径形状。 + +## 4. 飞拍轨迹 + +上传: + +```bash +POST /upload_flyshot/ +{ + "name": "UTTC_MS11", + "waypoints": [[...]], + "shot_flags": [false, true], + "offset_values": [0, 0], + "addrs": [[1, 3]] +} +``` + +校验: + +```bash +POST /is_flyShotTrajValid/ +{ + "name": "UTTC_MS11", + "method": "self-adapt-icsp", + "save_traj": false +} +``` + +执行: + +```bash +POST /execute_flyshot/ +{ + "name": "UTTC_MS11", + "move_to_start": true, + "method": "self-adapt-icsp", + "save_traj": false, + "use_cache": true, + "wait": true +} +``` + +执行链路: + +1. 从上传缓存读取 waypoint、shot flag、offset、IO 地址组。 +2. 使用 `icsp` 或 `self-adapt-icsp` 规划关节轨迹;规划阶段先应用 `acc_limit / jerk_limit`,再应用 replacement-only 的规划加速度校准参数。 +3. 生成 `TrajectoryDoEvent`,把拍照触发绑定到轨迹时间。 +4. 若 `move_to_start=true`,先从运行时当前关节位置生成临时 PTP 稠密轨迹移动到规划轨迹起点,并等待运行时 `IsInMotion=false` 后再启动飞拍轨迹,避免第一帧 J519 目标从当前位置跳到起点。 +5. 真机模式下把规划输出的 `rad` 稠密轨迹按 J519 轨迹时间步长重采样并转成 `deg`,命令实际发包由机器人 UDP status 包驱动。 +6. 若 `wait=true`,正式飞拍轨迹启动后继续等待运行时 `IsInMotion=false`,机器人执行完整条飞拍轨迹后 HTTP 才返回;`wait=false` 时启动后立即返回。 +7. 启动前若已有 J519 响应且 `accept_cmd` 或 `sysrdy` 未就绪,则拒绝执行。 +8. 周期命令中嵌入 IO 脉冲;当前 UTTC 抓包确认 mask 集合为 `10/12/14`,共 17 个 set 脉冲和 17 个 clear 帧。 + +`method="doubles"` 当前明确返回未实现;现场主链路使用 `icsp` / `self-adapt-icsp`。 + +## 5. 停止与断开 + +```text +GET /stop_move/ +GET /disable_robot/ +POST /disconnect_robot/ +``` + +真机模式下: + +- `StopMove()` 取消当前稠密轨迹生成任务并停止 J519 状态包驱动发送。 +- `DisableRobot()` 发送 J519 packet type 2 状态输出停止包,然后 `StopProg("RVBUSTSM")`。 +- `Disconnect()` 关闭状态、命令和 J519 三条通道,并清理本地运行状态。 + +## 6. 现场抓包覆盖 + +`tests/Flyshot.Core.Tests/UttcJ519GoldenTests.cs` 直接解析以下抓包并与 `Rvbust/uttc-20260428/Data/JointDetialTraj.txt` 对比: + +| 抓包 | 速度 | 运行 J519 点数 | 发送时长 | +|------|------|----------------|----------| +| `2026042802-0.5.pcap` | 0.5 | 1851 | 14.800309s | +| `2026042802-0.7.pcap` | 0.7 | 1322 | 10.568313s | +| `2026042802-1.pcap` | 1.0 | 926 | 7.400125s | + +测试同时检查: + +- 主运行窗口命令序号连续,无重复 seq;J519 客户端单元测试覆盖按最新 status sequence 回发命令。 +- 响应 `status=15` 段覆盖主运行窗口,响应相对命令滞后 2 到 8 帧。 +- 实发点位相对重采样期望的全局 RMS 小于 `0.012deg`,最大绝对误差小于 `0.07deg`。 +- `lastData=0`,结束运动当前依赖 J519 packet type 2 状态输出停止包;`../j519 协议.pcap` 中另有 1 个 `LastData=1` 后紧跟 type 2 的样本,停止语义后续单独验证。 +- IO 脉冲数量和 mask 集合 `10/12/14` 与抓包一致。 diff --git a/docs/fanuc-socket-implementation-plan.md b/docs/fanuc-socket-implementation-plan.md new file mode 100644 index 0000000..7654301 --- /dev/null +++ b/docs/fanuc-socket-implementation-plan.md @@ -0,0 +1,187 @@ +# FANUC 真机协议 Socket 通信层实现计划 + +## 上下文 + +状态更新:本计划中的 Socket 客户端和 `FanucControllerRuntime` 改造已经落地;当前事实以 `README.md` 和 `docs/fanuc-field-runtime-workflow.md` 为准。本文保留为实现过程记录。 + +计划制定时 `flyshot-replacement` 项目已完成: +- 三条 FANUC 通信链路的二进制协议编解码(`FanucCommandProtocol`、`FanucStateProtocol`、`FanucJ519Protocol`) +- 抓包样本验证的协议测试(5 个 FanucProtocolTests 全部通过) +- TCP 10012 的 `Get/SetSpeedRatio`、`Get/SetTCP`、`Get/SetIO` 参数命令封包、响应解析和本地模拟器测试 +- HTTP 兼容层控制器和状态监控页 +- 轨迹规划与飞拍触发编排层 + +2026-04-28 `Rvbust/uttc-20260428/20260428.pcap` 新增约束: + +- `TCP 10010` 状态帧继续确认为固定 `90B`。 +- `UDP 60015` 命令 `target[0..5]` 为关节角 `deg`,而 `JointDetialTraj.txt` 为 `rad`。 +- `speed_ratio=0.7` 在本抓包中表现为 UDP 下发时间轴约 `1.427730x` 拉伸;机器人侧 `TCP 10012` 未抓到 `0x2207 SetSpeedRatio`。 +- `UTTC_MS11` 的 17 个飞拍触发点与 17 个 UDP IO 脉冲一一对齐,`io_keep_cycles=2` 对应约两周期清零。 + +**历史缺失项(已完成)**:计划制定时 `FanucControllerRuntime` 仍是纯内存状态桩。当前实现已经改为持有 `FanucCommandClient`、`FanucStateClient` 和 `FanucJ519Client`,真机模式会建立三条通道并从状态/J519 响应读取运行状态。 + +## 目标 + +将 `FanucControllerRuntime` 从内存桩改造为具备真实 FANUC R30iB 通信能力的运行时,使 HTTP 层的每个指令真正下发到控制柜。 + +## 架构设计 + +### 分层结构 + +``` +LegacyHttpApiController / StatusController (HTTP 适配层,保持不动) + ↓ 调用同步接口 +IControllerRuntime / ControllerClientCompatService (兼容层,保持不动) + ↓ 调用同步接口 +FanucControllerRuntime (改造:从内存桩 → 委托给三个 Socket 客户端) + ↓ 内部持有并调度 +FanucCommandClient (TCP 10012,Req/Res 同步命令通道) +FanucStateClient (TCP 10010,持续接收状态帧后台循环) +FanucJ519Client (UDP 60015,8ms 周期发送 + 接收响应) + ↓ 使用现有编解码 +FanucCommandProtocol / FanucStateProtocol / FanucJ519Protocol (已有,不改动) +``` + +### 关键设计决策 + +1. **接口保持同步**:`IControllerRuntime` 现有 18 个方法全为同步签名。内部 Socket I/O 采用 `Task` + `.GetAwaiter().GetResult()` 短时间阻塞,或后台线程 + 锁同步状态快照。避免一次性推翻整个兼容层。 + +2. **三个独立客户端**:每条物理通道一个类,各自管理连接生命周期,便于单独测试和故障定位。 + +3. **状态通道后台循环**:`FanucStateClient` 内部启动 `Task` 持续 `ReadAsync(90)`,解析状态帧后写入线程安全的 `ControllerStateSnapshot` 缓存。 + +4. **J519 周期发送器**:`FanucJ519Client` 内部用 `PeriodicTimer` 或 `Task.Delay` 实现约 8ms 周期的发送循环。命令通过线程安全的队列/缓冲区注入。 + +5. **RVBUSTSM 程序生命周期隐式管理**:`EnableRobot()` 时自动走 `StopProg→Reset→GetProgStatus→StartProg("RVBUSTSM")` 序列(与抓包一致)。`DisableRobot()` 时发送 `StopProg`。 + +6. **连接顺序**:`Connect()` 按顺序建立三条通道 — 先 TCP 10010(状态),再 TCP 10012(命令),最后 UDP 60015(运动)。 + +## 实现步骤 + +### Phase 1: TCP 10012 命令客户端 + +**新建文件**:`src/Flyshot.Runtime.Fanuc/Protocol/FanucCommandClient.cs` + +职责: +- `Connect(string ip, int port = 10012)` — 建立 TcpClient 连接 +- `SendCommandAsync(uint messageId, ReadOnlyMemory body)` — 发送并等待响应 +- `SendProgramCommandAsync(uint messageId, string programName)` — 封装程序名命令 +- `Disconnect()` — 关闭连接 +- 线程安全(单个命令通道同一时间只处理一个请求) + +需要封装的具体命令方法: +- `StopProgramAsync(string name)` → `PackProgramCommand(0x2103, name)` +- `ResetRobotAsync()` → `PackEmptyCommand(0x2100)` +- `GetProgramStatusAsync(string name)` → `PackProgramCommand(0x2003, name)` +- `StartProgramAsync(string name)` → `PackProgramCommand(0x2102, name)` +- `GetTcpAsync()` / `SetTcpAsync()` — 已按 `tcp_id + f32[7] pose` 字段布局实现 +- `GetSpeedRatioAsync()` / `SetSpeedRatioAsync()` — 已按 `ratio_int / 100.0` 与 `ratio_int_0_100` 字段布局实现;注意 2026-04-28 真实运行抓包未出现机器人侧 `0x2207`,执行链路仍必须在 UDP 发送时间尺度上应用当前速度倍率 +- `GetIoAsync()` / `SetIoAsync()` — 已按 `io_type / io_index / f32 io_value` 字段布局实现 + +**测试**:`tests/Flyshot.Core.Tests/FanucCommandClientTests.cs` +- 用 `TcpListener` 本地模拟控制器,验证帧收发与解析 + +### Phase 2: TCP 10010 状态客户端 + +**新建文件**:`src/Flyshot.Runtime.Fanuc/Protocol/FanucStateClient.cs` + +职责: +- `Connect(string ip, int port = 10010)` — 建立 TcpClient 连接 +- 内部启动后台 `Task` 循环 `ReadAsync(FanucStateProtocol.StateFrameLength)` +- 每收到一帧调用 `FanucStateProtocol.ParseFrame()` +- 将解析结果写入线程安全的最新状态缓存 +- 单帧接收超时后标记状态陈旧,不再把旧帧当作当前位姿/关节状态使用 +- EOF、坏帧、Socket 异常或超时后关闭当前连接,并按退避策略自动重连 TCP 10010 +- `GetLatestFrame()` — 返回最近一次解析的状态帧 +- `GetStatus()` — 返回连接阶段、陈旧状态、最近异常和重连次数 +- `Disconnect()` — 取消后台循环并关闭连接 + +**测试**:`tests/Flyshot.Core.Tests/FanucStateClientTests.cs` +- 用 `TcpListener` 本地发送抓包样本 hex,验证后台循环能正确解析。 +- 用本地模拟控制器验证无状态帧超时、EOF 后退避重连和重连后的继续收帧。 +- `FanucStateProtocol` 已用 `j519 协议.pcap` 中多条 90B 样本锁定 `pose[6]`、`joint[6]`、`external_axes[3]` 和 `raw_tail_words[4]`。 +- `Rvbust/uttc-20260428/20260428.pcap` 再次确认 `10010` 状态帧固定 90B,平均间隔约 25.6ms。 +- 尾部状态字当前只作为 `ControllerStateSnapshot.stateTailWords` 诊断字段保留,不从 `[2,0,0,1]` 推断使能或运动状态。 + +### Phase 3: UDP 60015 J519 运动客户端 + +**新建文件**:`src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Client.cs` + +职责: +- `Connect(string ip, int port = 60015)` — 创建 UdpClient +- 发送 init packet (`PackInitPacket()`) +- 内部启动发送循环(约 8ms 周期) +- `UpdateCommand(FanucJ519Command command)` — 原子更新下一周期要发送的命令 +- `StartMotion()` — 启动发送循环 +- `StopMotion()` — 发送 end packet,停止循环 +- 接收线程:持续 `ReceiveAsync()` 解析 132B 响应,更新反馈状态 +- `Disconnect()` — 清理 + +执行注意事项: + +- 规划层输出关节角为 `rad`,J519 命令 `target[0..5]` 必须转为 `deg`。 +- 发送循环不能只按 `JointDetialTraj` 行号逐行发;需要保持约 8ms 的 J519 实发周期,同时按当前 `speed_ratio` 对原轨迹时间轴做缩放。 +- 实发规则:第 `k` 个 J519 周期的发送时间为 `t_send = k * 0.008`,轨迹采样时间为 `t_traj = t_send * speed_ratio`,命令包数为 `floor(duration / (0.008 * speed_ratio)) + 1`。`UTTC_MS11` 中 `7.403046 / (0.008 * 0.7) = 1321.9725`,因此主运行实发 `1322` 个运行包,而不是 `JointDetialTraj.txt` 的 `464` 行。 +- 飞拍 IO 事件应嵌入 `write_io_type/index/mask/value`,不要用独立 `TCP 10012 SetIO` 模拟拍照触发。 +- 响应 `joints_deg` 相对命令目标存在约 7 帧 / 56ms 滞后,闭环判断要容忍该延迟。 + +**测试**:`tests/Flyshot.Core.Tests/FanucJ519ClientTests.cs` +- 用本地 UDP socket 模拟控制器收发 + +### Phase 4: 重写 FanucControllerRuntime + +**改造文件**:`src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs` + +将当前内存桩替换为真实运行时: + +- 持有三个客户端实例:`FanucCommandClient`、`FanucStateClient`、`FanucJ519Client` +- `Connect(robotIp)` — 顺序连接 10010 → 10012 → 60015 +- `EnableRobot(bufferSize)` — 走完整 StartProg 序列(Stop→Reset→Status→Start RVBUSTSM),然后启动 J519 +- `DisableRobot()` — 停止 J519,发送 StopProg +- `Disconnect()` — 断开三条通道 +- `ExecuteTrajectory(result, finalJointPositions)` — 将规划后的稠密路点经 `rad -> deg` 转换,并按 `t_send = k * 0.008`、`t_traj = t_send * speed_ratio` 重采样后,通过 J519 逐周期发送 +- `StopMove()` — 立即停止 J519 发送循环 +- `GetSnapshot()` — 优先从 `FanucStateClient` 读取最新状态;若状态通道未连接,回退到内存值 +- `GetJointPositions()` / `GetPose()` / `GetTcp()` / `GetSpeedRatio()` / `GetIo()` — 优先从真实通道读取 +- `SetTcp()` / `SetSpeedRatio()` / `SetIo()` — 通过命令通道发送 + +### Phase 5: 端到端集成测试 + +**改造/新建测试**: +- `tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs` — 补充真实连接流程(可用本地模拟器) +- `tests/Flyshot.Core.Tests/FanucControllerRuntimeSocketTests.cs` — 用本地 TCP/UDP 模拟器验证完整链路 + +**验证命令**: +```bash +cd flyshot-replacement +dotnet build FlyshotReplacement.sln -v minimal +dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj -v minimal +dotnet test tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj -v minimal +``` + +## 风险与回退策略 + +1. **真机连接风险**:第一版 Socket 实现可能有超时/重连问题。`FanucControllerRuntime` 保留 `_simulationMode` 路径,仿真模式下仍走内存桩。 +2. **性能风险**:同步接口内部阻塞 Socket 可能影响 HTTP 并发。若实测有问题,后续将 `IControllerRuntime` 改为 async。 +3. **现场验证风险**:TCP 10012 参数命令已按逆向结论实现,但仍需在真实 R30iB 控制柜上确认默认 `tcp_id=1`、IO 类型/地址和错误码语义。 + +## 关键文件清单 + +| 文件 | 动作 | +|------|------| +| `src/Flyshot.Runtime.Fanuc/Protocol/FanucCommandClient.cs` | 新建 | +| `src/Flyshot.Runtime.Fanuc/Protocol/FanucStateClient.cs` | 新建 | +| `src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Client.cs` | 新建 | +| `src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs` | 重写 | +| `tests/Flyshot.Core.Tests/FanucCommandClientTests.cs` | 新建 | +| `tests/Flyshot.Core.Tests/FanucStateClientTests.cs` | 新建 | +| `tests/Flyshot.Core.Tests/FanucJ519ClientTests.cs` | 新建 | +| `tests/Flyshot.Core.Tests/FanucControllerRuntimeSocketTests.cs` | 新建 | + +## 下一步验证标准 + +- `FanucControllerRuntime` 的 `Connect()` 能成功建立三条 TCP/UDP 连接 +- `EnableRobot()` 能走完 `RVBUSTSM` 启动序列 +- `ExecuteTrajectory()` 能按 8ms 周期通过 J519 发送路点,并按当前 `speed_ratio` 推进原始轨迹时间 +- `GetSnapshot()` 返回的值来自 TCP 10010 真实状态帧而非内存 +- 现有 10 个集成测试和 25 个核心测试仍然通过 diff --git a/docs/fanuc-stream-motion-manual-notes.md b/docs/fanuc-stream-motion-manual-notes.md new file mode 100644 index 0000000..80df8ab --- /dev/null +++ b/docs/fanuc-stream-motion-manual-notes.md @@ -0,0 +1,232 @@ +# FANUC Stream Motion 文档要点与实现差异 + +本文记录 `../FANUC_stream_motion.pdf` 中与本仓库 `Flyshot.Runtime.Fanuc` 直接相关的重点,并对照当前实现状态。 + +读取时间:2026-05-03 + +## 1. 文档定位 + +`FANUC_stream_motion.pdf` 对应 FANUC `Stream motion` 功能,选项号为 `A05B-2600-J519`。它描述的是外部设备通过以太网实时发送期望位置,让机器人按外部生成路径运动的控制方式。 + +文档明确要求外部设备自行生成满足机器人约束的路径,包括速度、加速度、jerk、可达性、姿态连续性等。FANUC 不提供完整运动学、逆解和碰撞检测公式。 + +## 2. 使用前提与示教程序 + +1. 机器人侧需要安装 J519 stream motion 选项。 +2. 物理网口通过 `$STMO.$PHYS_PORT` 选择,`1` 表示 `CD38A`,`2` 表示 `CD38B`。 +3. 机器人程序必须包含成对的 `IBGN start[*]` 和 `IBGN end[*]` 指令,二者编号必须一致,`start` 必须在 `end` 前一行。 +4. `IBGN start[*]` 执行期间,机器人根据外部设备发来的期望位置运动;`IBGN end[*]` 之后程序继续执行。 +5. 执行时要求 `AUTO` 模式和 `100% OVERRIDE`。 + +当前实现中的 `FanucControllerRuntime.EnableRobot()` 会按现场抓包流程启动 `RVBUSTSM` 程序,并随后允许 J519 在收到机器人 UDP status 包后回发命令。是否满足 `AUTO / 100% OVERRIDE / IBGN start` 已到位,当前只通过 J519 状态位和现场程序行为间接判断,没有在代码里读取或设置这些控制器状态。 + +## 3. UDP 60015 协议结构 + +协议使用 UDP,大端字节序,机器人侧端口为 `60015`。通信周期通常为 `8ms`,部分机型支持 `4ms`。状态包输出可以在任意时间通过 start/stop 控制包启停,不要求已经进入 `IBGN start[*]`。 + +### 3.1 状态输出 start 包 + +外部设备发给机器人: + +| 字段 | 长度 | 值 | +| --- | --- | --- | +| Packet type | 4B | `0` | +| Version | 4B | `1` | + +当前实现:`FanucJ519Protocol.PackInitPacket()` 已按 8B 大端控制包实现,`FanucJ519Client.ConnectAsync()` 连接后立即发送。 + +### 3.2 状态包 + +机器人发给外部设备,长度为 `132B`: + +| 偏移 | 字段 | 含义 | +| --- | --- | --- | +| `0x00` | Packet type | `0` | +| `0x04` | Version | `1` | +| `0x08` | Sequence No. | 状态包序号,发送 start 包后从 `1` 重新开始 | +| `0x0c` | Status | bit0 接受命令、bit1 已收到命令、bit2 SYSRDY、bit3 运动中 | +| `0x0d..0x12` | Read I/O 回显和值 | 回显命令包中的读取 IO 类型、索引、掩码,并返回 16 点 IO 值 | +| `0x14` | Timestamp | ms 单位,2ms 分辨率 | +| `0x18..0x38` | Cartesian / external axis | `X/Y/Z/W/P/R` 加 3 个扩展轴 | +| `0x3c..0x5c` | Joint | `J1..J9`,单位 degree | +| `0x60..0x80` | Motor current | `J1..J9` 电流,单位 A | + +当前实现:`FanucJ519Protocol.ParseResponse()` 已解析 `132B` 状态包,并暴露 `AcceptsCommand`、`ReceivedCommand`、`SystemReady`、`RobotInMotion` 四个状态位。`FanucControllerRuntime.GetSnapshot()` 也会把最新 J519 状态写进快照。 + +### 3.3 命令包 + +外部设备发给机器人,长度为 `64B`: + +| 偏移 | 字段 | 含义 | +| --- | --- | --- | +| `0x00` | Packet type | `1` | +| `0x04` | Version | `1` | +| `0x08` | Sequence No. | 第一包应等于刚收到的状态包序号,后续逐包递增 | +| `0x0c` | Last data | 正常为 `0`;结束外部控制时最后一包设为 `1` | +| `0x0d..0x11` | Reading I/O | 可读取最多 16 个连续 IO 点 | +| `0x12` | Data format | `0` 笛卡尔,`1` 关节 | +| `0x13..0x19` | Writing I/O | 可写入最多 16 个连续 IO 点 | +| `0x1a` | unused | 2B | +| `0x1c..0x3c` | target[9] | 9 个 f32 目标值;关节格式时单位 degree | + +当前实现:`FanucJ519Protocol.PackCommandPacket()` 已按上述布局打包,默认 `dataStyle=1`,也就是关节格式。运行时会把规划输出的弧度制关节轨迹转换为 degree 后下发。 + +### 3.4 状态输出 stop 包 + +外部设备发给机器人: + +| 字段 | 长度 | 值 | +| --- | --- | --- | +| Packet type | 4B | `2` | +| Version | 4B | `1` | + +文档把它定义为“停止状态包输出”的控制包,不是命令流正常终止的首选动作。命令流结束应通过 command packet 的 `Last data=1` 表达。 + +当前实现:`FanucJ519Client.StopMotionAsync()` 当前会停止状态包驱动发送并发送 packet type `2`,而稠密轨迹执行期间保持 `LastData=0`。这是与 FANUC 文档最明显的语义差异之一;已有多数 UTTC 抓包显示主运行窗口 `LastData=0`,但 `../j519 协议.pcap` 中存在 1 个 `LastData=1` 后紧跟 packet type `2` 的样本,后续应单独校准停止语义。 + +## 4. 通信时序重点 + +文档推荐的时序是: + +1. 外部设备发送状态输出 start 包。 +2. 机器人每个通信周期输出状态包。 +3. 机器人程序执行到 `IBGN start[*]` 后,状态包 bit0 变为 `1`,表示等待命令包。 +4. 外部设备收到 bit0 为 `1` 的状态包后,立即发送第一帧命令包,第一帧命令序号应等于刚收到的状态包序号。 +5. 后续每收到一个状态包,外部设备应立即发送下一帧命令包。 +6. 结束命令通信时,发送 `Last data=1` 的最后一帧命令包。 + +当前实现对照: + +1. `FanucJ519Client` 已改为收到机器人 132B status 包后立即回发当前命令,不再由上位机本地固定 8ms 发送循环主动发包。 +2. 命令包 sequence 已按刚收到的 status packet sequence 写入,避免第一帧从本地 `0` 起步。 +3. `FanucControllerRuntime.ExecuteTrajectory()` 启动前会检查已有 J519 响应中的 `AcceptsCommand` 和 `SystemReady`;但如果还没收到状态包,则会放行,后续命令仍要等第一帧 status 到达才会发出。 +4. 当前稠密轨迹结束不发送 `LastData=1`,而是依赖停止 J519 状态包驱动发送和 packet type `2` stop 控制包。 + +序号和节拍已经按手册方向校准;停止语义仍需在真实 R30iB 联调中继续确认。 + +## 5. 命令缓冲 + +文档说明机器人可以缓冲提前到达的 command packet。默认启用,缓冲上限为 `$STMO.$PKT_STACK - 1`,`$PKT_STACK` 默认 `10`,可配置范围 `2..10`。`$STMO.$START_MOVE` 决定积累多少未处理命令包后开始运动,默认 `1`。 + +注意事项: + +1. 只有 command packet 会进入缓冲。 +2. status output stop packet 会立即处理。 +3. 如果 command buffer 中还有未处理包,不应发送 status output stop packet。 +4. 使用 `Last data=1` 时,机器人会先处理完缓冲里的命令包,再结束外部控制。 + +当前实现没有显式预填 `$PKT_STACK` 缓冲,也没有读取 `$START_MOVE`。`FanucJ519Client` 只保存一个“当前命令”,由后台循环持续发送;`FanucControllerRuntime.SendDenseTrajectory()` 另一个 8ms 循环负责按轨迹时间更新这条当前命令。这与文档的“按状态包响应并可提前发多包缓冲”模型不同。 + +## 6. 可执行运动条件 + +文档列出的主要运动约束: + +1. 目标点必须可达。 +2. 笛卡尔格式下目标点对应的关节解必须唯一,且 configuration 要与 `IBGN start` 开始时一致。 +3. 各轴必须满足上下限。 +4. 不能发生自碰撞。 +5. 必须考虑 FANUC J3 轴定义:J3 不是相对 J2 臂的夹角,而是机器人视角下相对水平面的 J3 臂角度。 +6. 外部设备必须控制每轴速度、加速度、jerk 不超过 `$STMO_GRP` 下的限制。 +7. 状态包中的当前位置是 servo feedback position,不是 command position。轨迹起点应平滑连接到机器人 command position,而不能简单用当前 servo position 直接起步。 +8. reducer load 超限也会导致停机,负载相关计算不公开。 + +当前实现对这些条件的覆盖: + +| 条件 | 当前状态 | +| --- | --- | +| 关节格式下发 | 已实现,当前现场链路默认只使用关节格式 | +| `rad -> deg` | 已实现,并由 UTTC J519 golden tests 覆盖 | +| `speed_ratio` 下发时间轴缩放 | 已实现,J519 实发时间为 `t_send = k * 0.008`,原轨迹采样时间为 `t_traj = t_send * speed_ratio` | +| IO 触发嵌入 J519 命令包 | 已实现,使用 `write_io_type/index/mask/value` | +| 速度、加速度、jerk 约束 | 规划层有 `acc_limit / jerk_limit` 等兼容参数,但未从 FANUC `$STMO_GRP` 在线读取,也未实现手册附录中的 20 档速度/负载插值 | +| J3 轴定义 | 当前文档未见专门处理;需要确认 `.robot` 模型与现场导出轨迹是否已经采用 FANUC J3 定义 | +| command position 起步 | `MoveJoint` 会用当前运行时记录的关节作为起点生成 PTP 稠密轨迹;但没有通过 FANUC HMI 通信读取 command position | +| reducer load | 未建模,依赖保守规划和现场报警反馈 | +| 笛卡尔格式限制 | 运行时不走笛卡尔 J519 目标格式,暂不覆盖 configuration 变化报警 | + +## 7. 系统变量 + +与本仓库后续最相关的变量: + +| 变量 | 默认/含义 | +| --- | --- | +| `$STMO.$PHYS_PORT` | 物理口,`1=CD38A`,`2=CD38B` | +| `$STMO.$COM_INT` | 通信周期,单位 ms,通常 `8`,只读 | +| `$STMO.$PKT_STACK` | command buffer 最大保留量,默认 `10` | +| `$STMO.$START_MOVE` | 缓冲中积累多少未处理命令后开始运动,默认 `1` | +| `$STMO_GRP.$JNT_VEL_LIM[*]` | 各轴速度上限,degree/s,只读 | +| `$STMO_GRP.$JNT_ACC_LIM[*]` | 各轴加速度上限,degree/s^2,只读 | +| `$STMO_GRP.$JNT_JRK_LIM[*]` | 各轴 jerk 上限,degree/s^3,只读 | +| `$STMO_GRP.$LMT_MODE` | 加速度/jerk 限制计算模式 | +| `$STMO_GRP.$WARN_LIM` | 接近限制时报警阈值,默认 `80%` | +| `$STMO_GRP.$FLTR_LN` | 命令目标移动平均滤波窗口 | +| `$STMO_GRP.$MAX_SPD` | 用于限制计算的 flange center 最大速度 | + +当前实现没有读取或设置上述系统变量。`RobotProfile.ServoPeriod` 当前决定运行时发送周期;对当前现场而言应继续确认它与 `$COM_INT` 一致。 + +## 8. 附录 B:加速度和 jerk 限制 + +文档说明,在 `$STMO_GRP[].$LMT_MODE=0` 时,加速度和 jerk 的允许上限会根据 flange center speed 与 payload 计算: + +1. 以 `$MAX_SPD` 分成 20 档速度区间。 +2. 每个轴、每种限制类型都有无负载和最大负载两张 20 档表。 +3. 实际 payload 通过线性插值得到限制表。 +4. 实际 flange center speed 在相邻速度档之间线性插值。 +5. 限制值不是每个通信周期都更新,而是在超过 `Vmax/20` 到再次跌回阈值的整段时间内,以该段观测到的 `Vpeak` 决定。 +6. 如果长时间不跌回阈值,会按中间检查时间做临时判断。 + +文档还提供了 packet type `3` 的限制表查询协议: + +| 包 | 方向 | 重点字段 | +| --- | --- | --- | +| 请求 | 外部设备 -> 机器人 | packet type `3`、version `1`、axis `1..9`、limit type `0=velocity/1=acceleration/2=jerk` | +| 响应 | 机器人 -> 外部设备 | packet type `3`、version `1`、axis、limit type、`Vmax`、中间检查时间、无负载 20 档、最大负载 20 档 | + +当前实现没有 packet type `3` 查询,也没有实现手册描述的动态限制表算法。现阶段规划时长和保守程度主要依赖 replacement 自身参数与现场抓包对齐。 + +## 9. 报警与诊断 + +文档中与实现最相关的报警: + +| 报警 | 含义 | +| --- | --- | +| `MOTN-600` | 命令序号与机器人期望不一致 | +| `MOTN-602` | data format 非法 | +| `MOTN-603` | 后续命令包未在通信周期内到达 | +| `MOTN-604` | 命令包过多,超出缓冲 | +| `MOTN-605` | 目标位置包含 NaN 或 infinity | +| `MOTN-606` | 非 AUTO 或 override 不是 100% | +| `MOTN-607` | 协议版本不匹配 | +| `MOTN-609/610/611` | 速度、加速度、jerk 超限 | +| `MOTN-612/613/614` | 接近速度、加速度、jerk 限制 | +| `MOTN-617` | 目标点与当前位置不连续 | +| `MOTN-619` | 当前机型不支持笛卡尔目标格式 | +| `PRIO-023` | 读写的 IO 类型或索引未分配 | + +当前 `ControllerStateSnapshot.ActiveAlarms` 仍为空,Web 状态页也尚未接入 FANUC 报警列表。后续现场联调如果出现报警,应优先按上述表格关联 J519 包序号、目标数据、IO 字段、发送间隔和状态包 bit。 + +## 10. 与当前代码的结论 + +已基本对齐: + +1. UDP 60015、大端、start/stop 控制包、64B command packet、132B status packet 的基础二进制布局。 +2. `Data format=1` 的关节目标下发。 +3. 状态位 bit0..bit3 的解析和快照暴露。 +4. 规划输出 `rad` 转 J519 `deg`。 +5. 根据 `speed_ratio` 做运行期时间轴缩放,而不是改变规划文件时间。 +6. 飞拍 IO 触发通过 command packet 的写 IO 字段下发。 +7. 命令发送按机器人 UDP status 包驱动,并使用最新 status sequence 回发。 + +主要差异/风险: + +1. 当前未实现命令缓冲预填,也未读取 `$PKT_STACK / $START_MOVE`。 +2. 当前停止运动依赖 packet type `2` stop 控制包,没有稳定发送 `LastData=1` 的最后 command packet;这与手册标准结束语义不同。 +3. 当前未实现 packet type `3` 的速度/加速度/jerk 限制表查询,也未实现 payload/speed 20 档动态限制算法。 +4. 当前没有自动校验 `AUTO / 100% OVERRIDE / brake control / resume offset / payload` 等控制器前置状态。 +5. 当前没有报警码读取和 `MOTN-* / PRIO-*` 映射。 + +建议后续联调优先级: + +1. 验证运动结束是否必须补 `LastData=1`;如果当前 stop 控制包能稳定工作,也应在文档中标为现场兼容路径,而不是手册标准路径。 +2. 抓一次报警现场包,确认 `MOTN-600/603/617` 等是否能从包序号与状态位直接定位。 +3. 如果后续追求更稳的真实机运行,补 packet type `3` 限制表查询,并把规划器的速度、加速度、jerk 校验与 FANUC 手册算法靠近。 diff --git a/docs/flyshot-speedratio-execution-design-20260509.md b/docs/flyshot-speedratio-execution-design-20260509.md new file mode 100644 index 0000000..b3d8504 --- /dev/null +++ b/docs/flyshot-speedratio-execution-design-20260509.md @@ -0,0 +1,460 @@ +# 飞拍任务 `speedRatio` 执行侧调速设计 + +## 1. 背景 + +当前系统同时存在两个速度相关参数: + +- `planning_speed_scale` + - 规划阶段参数。 + - 作用是让规划结果在时长与整体动力学上更接近现场旧系统或当前配置要求。 + - 启动后固定,不希望在运行中再修改语义。 +- `speedRatio` + - 运行阶段参数。 + - 期望成为统一在线调速入口。 + - 本次范围内只要求对“下一次开始执行的飞拍任务”生效,不要求对正在执行中的任务中途热切换。 + +本次任务已经明确收敛: + +- 只修改飞拍任务链路。 +- 不修改 `move_joint`。 +- 不通过放宽阈值、关闭校验或只看平均速度来规避问题。 + +## 2. 现状问题 + +当前飞拍执行链路的核心语义是: + +1. 规划层先生成 `DenseJointTrajectory`。 +2. 运行时保持 `8ms` 物理发送周期不变。 +3. 对第 `k` 个发送点,按 `trajectoryTime = sendTime * speedRatio` 回映射到规划轨迹时间。 +4. 再在原始稠密轨迹上做线性插值,得到实际要发的关节目标。 +5. 最后再用离散差分检查 `vel/acc/jerk`。 + +该结构在 `speedRatio < 1` 时有天然风险: + +- 物理发送周期固定为 `8ms`,但轨迹时间推进被压缩。 +- 线性插值会把规划轨迹中原本“看起来连续”的局部变化映射成新的离散点列。 +- 这些新点列在三阶后向差分上容易形成尖峰。 +- 该风险在 `speedRatio = 0.5` 时已经暴露为明显的 Jerk 超限告警。 + +问题本质不是“平均速度是否合理”,而是: + +- 最终 `8ms` 发送点列本身没有先被当作一等公民建模。 +- `speedRatio` 的执行语义落在“发送前临场回采样”阶段。 +- 离散约束校验发生得太晚,只能报错或事后分析。 + +## 3. 目标 + +本设计目标如下: + +- 保持 `planning_speed_scale` 只属于规划层。 +- `speedRatio` 是运行时统一执行倍率,可影响下一次启动的飞拍、普通轨迹和 `MoveJoint`。 +- 飞拍链路在任意有效 `speedRatio` 下,最终真实发送的 `8ms` 点列必须满足逐周期 `vel/acc/jerk` 约束。 +- 不依赖“插值后自然会平滑”的经验假设。 +- 当请求 `speedRatio` 下的最终发送队列不满足约束时,拒绝执行并输出超限诊断;不在执行侧偷偷改写本次请求倍率。 +- 不破坏现有 `shot timeline / sample offset` 语义。 +- `speedRatio = 1.0` 时行为应与当前基线一致或可解释等价。 + +## 4. 非目标 + +本次设计明确不包含以下内容: + +- 不恢复旧的发送阶段临场回采样机制;普通轨迹和 `MoveJoint` 允许把 `speedRatio` 前移到规划或临时轨迹生成阶段折算。 +- 不讨论 GUI、多机器人或旧 `50001/TCP+JSON` 网关恢复。 +- 不修改 `planning_speed_scale` 的含义。 +- 不在任务执行中途支持 `speedRatio` 热切换。 +- 不通过调大容差、降低限值检查强度或直接跳过 Jerk 检查来“解决问题”。 + +## 5. 设计总览 + +飞拍执行链路调整为“两段式”: + +1. 规划段 + - 维持当前实现。 + - 仍由 `planning_speed_scale` 参与规划时长和规划层轨迹生成。 + - 输出 `TrajectoryResult.DenseJointTrajectory`、`TriggerTimeline`、`ShotEvents` 等规划结果。 +2. 执行准备段 + - 新增“飞拍执行侧最终发送序列生成器”。 + - 输入规划层稠密轨迹、执行时读取到的 `speedRatio`、机器人关节限位和触发时间轴。 + - 生成最终 `8ms` 发送队列。 + - 对最终队列做逐周期 `vel/acc/jerk` 校验。 + - 如果不通过,则拒绝执行,提示降低 `planning_speed_scale` 重新规划或显式设置更低的 `speedRatio`。 +3. 发送段 + - 运行时只消费已经准备好的最终 `8ms` 队列。 + - 运行时不再根据 `speedRatio` 对 `DenseJointTrajectory` 临场做线性回采样。 + +核心变化是: + +- `speedRatio` 不再直接驱动“发送前临场插值”。 +- `speedRatio` 改为驱动“执行侧最终队列构建”。 +- 最终队列一旦生成并校验通过,运行时只负责按 J519 状态包节奏出队发送。 + +## 6. 统一语义 + +本次以飞拍任务为主线,但 `speedRatio` 允许作为运行时统一执行倍率影响普通轨迹和 `MoveJoint`,需要把三类入口的边界说清楚,避免再次混淆规划倍率和执行倍率。 + +### 6.1 `planning_speed_scale` + +- 只影响规划结果。 +- 影响 `DenseJointTrajectory` 的时间轴与规划层速度/加速度/Jerk 使用方式。 +- 飞拍开始执行前就已经固定。 +- 在线设置 `speedRatio` 时不应反向修改它。 + +### 6.2 `speedRatio` + +- 影响下一次启动的运动任务,不在任务执行中途热切换。 +- 对飞拍任务,表示用户期望的执行层时间推进速度,可前移到规划/执行准备阶段生成最终发送队列。 +- 对普通轨迹和 `MoveJoint`,允许在规划或临时 PTP 轨迹生成阶段折算进有效关节限位,使运行时只消费已经生成好的 `8ms` 稠密点列。 +- `speedRatio = 0.8` 表示执行节奏接近把同几何轨迹时间轴整体拉长 `1 / 0.8`。 +- 例如 `planning_speed_scale = 0.9` 且 `speedRatio = 0.8` 时,最终执行节奏应接近同几何轨迹在 `planning_speed_scale ≈ 0.72` 下的结果。 + +也就是说: + +- `speedRatio` 是确定的执行倍率,不是校验失败后的自动搜索变量。 +- `finalSpeedRatio` 正常必须等于请求值。 +- 若该倍率下最终队列超限,问题暴露给调用方,而不是由执行侧自动改成另一个倍率。 + +## 7. 最终发送序列生成 + +### 7.1 新增执行侧生成器 + +建议新增一个飞拍专用生成器,命名可类似: + +- `FlyshotExecutionSendSequenceBuilder` +- 或 `FlyshotExecutionQueueBuilder` + +职责: + +- 输入规划层连续 `PlannedTrajectory`,优先使用经过执行侧定时处理后的 `ExecutionTrajectory`。 +- 输入 `servoPeriodSeconds`、`speedRatio`。 +- 输出最终真实发送的 `8ms` 点列。 +- 输出与该点列一致的触发绑定结果。 +- 输出请求倍率、最终倍率和历史倍率改写次数等诊断信息;当前语义下最终倍率等于请求倍率、改写次数为 0。 + +### 7.2 最终请求倍率队列 + +最终队列按固定时间轴语义生成: + +- `sendTime = sampleIndex * 0.008` +- `trajectoryTime = min(sendTime * speedRatio, durationSeconds)` +- 关节目标从连续样条直接取点,并从 `rad` 转为 J519 需要的 `deg` + +该队列就是本次请求倍率下的最终发送队列,后续只做校验和触发绑定,不再进行二次滤波或倍率搜索。 + +### 7.3 校验失败策略 + +当最终队列的离散 `vel/acc/jerk` 校验失败时: + +- 不修改规划层轨迹。 +- 不修改 `planning_speed_scale`。 +- 不放宽校验阈值。 +- 不降低本次 `speedRatio`。 +- 直接拒绝执行并保留首个超限诊断。 + +这样可以避免把 `speedRatio` 误实现成安全优化器。现场若希望从 `planning_speed_scale = 0.9` 的 10s 轨迹降到接近 `planning_speed_scale = 0.7` 的执行效果,应显式设置类似 `speedRatio = 0.8`,而不是让系统在失败后自动猜一个更低倍率。 + +## 8. 逐周期约束校验 + +最终发送队列必须在执行前通过离散检查。 + +### 8.1 校验对象 + +校验对象必须是最终真实发送的 `8ms` 点列,而不是: + +- 规划层原始稠密轨迹, +- 中间候选插值轨迹, +- 或平均时长推导。 + +### 8.2 校验指标 + +保留现有逐周期离散差分口径: + +- 速度 +- 加速度 +- Jerk + +并继续使用当前机器人模型和运行时限值: + +- `velocity_eff = velocity_base` +- `acceleration_eff = acceleration_base * acc_limit` +- `jerk_eff = jerk_base * jerk_limit` + +### 8.3 校验失败的处理 + +飞拍链路按当前修正语义采用“确定倍率 + 失败拒绝”: + +- 若请求倍率下失败,立即拒绝执行。 +- 不进行执行侧自动倍率搜索。 +- 调用方可以选择降低 `planning_speed_scale` 重新规划,或显式设置更低的 `speedRatio` 后再次执行。 + +拒绝执行时必须输出: + +- 首次失败的轴 +- 时间窗 +- 指标类型 +- `actual / limit / ratio` +- 请求 `speedRatio` +- 当前 `finalSpeedRatio`,正常等于请求值 + +## 9. 触发时序与绑定 + +### 9.1 保持现有语义 + +以下语义不变: + +- `TriggerTimeline` +- `ShotEvents` +- `TriggerSampleIndexOffsetCycles` +- `io_keep_cycles` + +### 9.2 绑定对象改为最终发送队列 + +当前实现中,触发通常绑定到由回采样生成的 `samples`。 + +本次改为: + +- 先生成最终发送队列。 +- 再用该最终队列做 `TriggerSampleBinder.Bind(...)`。 + +这样可以保证: + +- 触发绑定和真实发送完全一致。 +- 导出工件中的 `ShotEvents.json` 与真实执行时序一致。 +- 触发点可被精确追溯到本次请求倍率下的最终发送点索引。 + +### 9.3 关于触发数量与顺序 + +请求倍率改变最终发送时长时: + +- 触发数量不能变。 +- 触发顺序不能变。 +- 触发在最终发送队列中的绑定索引应随时间轴拉长后移或保持可解释等价。 + +## 10. 运行时职责调整 + +本次需要显式收紧 `FanucControllerRuntime` 的职责。 + +### 10.1 改造前 + +`FanucControllerRuntime.SendDenseTrajectory(...)` 当前同时负责: + +- 读取 `DenseJointTrajectory` +- 根据 `_speedRatio` 回采样 +- 绑定触发 +- 生成命令队列 +- 写出 `ActualSend*` 工件 + +### 10.2 改造后 + +`FanucControllerRuntime` 只负责: + +- 接收已经准备好的最终发送队列 +- 接收与该队列一致的触发绑定结果 +- 预构建命令队列并交给 J519 客户端 +- 记录真实发送过程与导出工件 + +不再负责: + +- 按 `_speedRatio` 从 `DenseJointTrajectory` 临场生成发送点 + +这样做的目的是让: + +- 运行时负责发送。 +- 执行准备层负责离散稳定性。 + +两层边界更清晰,也更利于测试。 + +## 11. 导出工件与日志 + +### 11.1 旧规划导出和实发诊断必须分开 + +`saveTrajectory` 目录中存在两类不同用途的文件,不能再混成同一条时间轴: + +第一类是旧格式规划导出,用于和旧 RVBUST 导出的规划轨迹、离线分析脚本做对比。它们只受 `planning_speed_scale` 和规划/整形参数影响,不受运行时 `speedRatio` 影响: + +- `JointTraj.txt` +- `JointDetialTraj.txt` +- `CartTraj.txt` +- `CartDetialTraj.txt` +- `JointDetialTraj.analysis.txt` + +第二类是实发诊断,用于观察当前 `speedRatio` 下真实准备发送的 8ms 队列和触发绑定。它们必须全部从最终发送队列生成: + +- `ActualSendJointTraj.txt` +- `ActualSendTiming.txt` +- `ActualSendJerkStats.txt` +- `ShotEvents.json` + +不允许再出现: + +- 旧规划导出被运行时 `speedRatio` 改写, +- `ActualSend*` 基于一套样本, +- 真实发送队列又基于另一套样本。 + +### 11.2 推荐新增日志字段 + +每次飞拍执行建议至少记录: + +- 请求 `speedRatio` +- 规划层轨迹时长 +- 最终发送队列点数 +- 最终发送队列是否通过校验 +- 若失败,首个失败窗口的: + - 关节轴 + - 时间区间 + - 指标类型 + - `actual` + - `limit` + - `ratio` +- 倍率改写次数,当前正常为 0 +- 最终采用的执行时长 +- 最终发送队列点数 +- 最终触发绑定数量 +- 最终校验通过结论 + +### 11.3 日志级别建议 + +- `Information` + - 记录执行请求、最终采用结果、最终通过结论 +- `Warning` + - 记录请求倍率下最终发送队列校验失败 +- `Debug` + - 记录最终发送队列的时间映射与详细差分统计 + +## 12. 验收口径 + +### 12.1 功能验收 + +- `speedRatio` 可在线设置。 +- 该值对下一次启动的飞拍、普通轨迹或 `MoveJoint` 生效。 +- 不需要修改 `planning_speed_scale`。 + +### 12.2 约束验收 + +在 `speedRatio = {1.0, 0.8, 0.5}` 下: + +- 最终发送队列逐周期 `vel/acc/jerk` 不超限。 +- 不出现固定区间正负交替的大幅 Jerk 尖峰。 + +### 12.3 工件与日志验收 + +- `JointTraj.txt / JointDetialTraj.txt / CartTraj.txt / CartDetialTraj.txt` 不随运行时 `speedRatio` 改变。 +- `ActualSend*` 文件能反映最终真实发送点位与时间映射。 +- 日志能定位请求倍率、最终发送队列和校验失败窗口。 + +### 12.4 回归验收 + +- `speedRatio = 1.0` 时不退化。 +- 触发事件数量不异常漂移。 +- 触发绑定顺序不异常漂移。 + +## 13. 测试方案 + +### 13.1 单元测试 + +建议新增: + +- 飞拍执行侧最终发送序列生成器测试 +- `speedRatio` 非法边界值测试 +- 请求倍率下队列超限时拒绝执行测试 +- `speedRatio = 1.0 / 0.8 / 0.5` 的逐周期限值通过测试 +- `planning_speed_scale = 0.9` 加 `speedRatio = 0.8` 的执行时间轴等价于约 `0.72` 规划倍率的回归测试 +- 普通轨迹和 `MoveJoint` 在倍率乘积相同时生成等价或可解释等价的稠密执行轨迹 +- 触发绑定始终与最终发送队列一致的测试 + +### 13.2 编排测试 + +建议补充: + +- `ExecuteFlyShotTraj` 进入运行时前,已经拿到最终发送队列 +- `FanucControllerRuntime` 不再自行按 `_speedRatio` 对飞拍轨迹回采样 +- `ExecuteTrajectory` 和 `MoveJoint` 的调速在规划/生成阶段完成,运行时不再做临场倍率重采样 +- `SaveTrajectoryInfo` 的旧格式规划导出不随运行时 `speedRatio` 改变,而 `ActualSend*` 仍随运行时 `speedRatio` 改变 + +### 13.3 集成与黄金样本 + +建议至少覆盖: + +- `UTTC_MS11` +- `speedRatio = 1.0` +- `speedRatio = 0.8` +- `speedRatio = 0.5` + +检查项: + +- 最终发送点数 +- 最终发送时长 +- `ActualSendTiming` +- `ActualSendJointTraj` +- `ActualSendJerkStats` +- `ShotEvents` 绑定结果 +- 是否消除当前已知 Jerk 尖峰 + +## 14. 代码落点建议 + +建议实现边界如下: + +### 14.1 `src/Flyshot.Core.Planning/Sampling/` + +新增飞拍执行侧最终发送序列生成器及相关结果类型,负责: + +- 候选队列构建 +- 离散校验 +- 触发绑定输入准备 + +### 14.2 `src/Flyshot.ControllerClientCompat/` + +在飞拍执行准备阶段调用该生成器,把最终发送队列放入执行结果或新的执行上下文对象。 +普通轨迹规划入口和 `MoveJoint` 临时轨迹生成入口可以读取当前 `speedRatio`,并在进入运行时前折算成已调速的稠密轨迹。 + +### 14.3 `src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs` + +调整 `SendDenseTrajectory(...)`: + +- 不再自行根据 `_speedRatio` 从 `DenseJointTrajectory` 临场重采样 +- 直接消费已经构建好的最终发送队列 + +### 14.4 `src/Flyshot.ControllerClientCompat/FlyshotTrajectoryArtifactWriter.cs` + +`FlyshotTrajectoryArtifactWriter` 接收两份视图: + +- 旧格式规划导出视图:写 `JointTraj.txt`、`JointDetialTraj.txt`、`CartTraj.txt`、`CartDetialTraj.txt` +- 实发诊断视图:写 `ActualSendJointTraj.txt`、`ActualSendTiming.txt`、`ActualSendJerkStats.txt`、`ShotEvents.json` + +### 14.5 `tests/Flyshot.Core.Tests/` + +补飞拍专项单测、UTTC 回归和工件一致性测试。 + +## 15. 风险与注意事项 + +### 15.1 不能把 speedRatio 做成安全优化器 + +对外语义必须保持: + +- 用户请求的是某个 `speedRatio` +- 系统按这个 `speedRatio` 构建最终发送队列 +- 若队列超限,则拒绝执行并要求调用方显式选择新的规划倍率或执行倍率 + +不能因为校验失败就在执行侧自动把 `0.8` 改成 `0.2`;这会让 `speedRatio` 和 `planning_speed_scale` 的关系失去可解释性。 + +### 15.2 不能让触发绑定脱离最终队列 + +只要最终发送点数变化,触发绑定就必须重新基于最终队列计算,否则工件与执行会再次分叉。 + +### 15.3 `speedRatio = 1.0` 必须优先做回归保护 + +这是最容易被“执行侧重构”误伤的路径,必须作为首要回归项。 + +## 16. 结论 + +本次飞拍调速设计的核心结论是: + +- `planning_speed_scale` 继续只属于规划层。 +- `speedRatio` 是运行时统一执行倍率,并且只对下一次启动的运动任务生效。 +- 普通轨迹和 `MoveJoint` 允许把 `speedRatio` 前移到规划或临时轨迹生成阶段,运行时只发送已生成好的 `8ms` 点列。 +- 飞拍执行必须先生成最终 `8ms` 发送队列,再对该队列做逐周期 `vel/acc/jerk` 校验。 +- `speedRatio` 按 `trajectoryTime = sendTime * speedRatio` 确定性拉长执行时间轴,`finalSpeedRatio` 不应被自动改写。 +- 若请求倍率下最终队列不满足约束,则拒绝执行并提示降低 `planning_speed_scale` 重新规划或显式设置更低 `speedRatio`。 +- 运行时不再对飞拍轨迹做发送前临场回采样。 +- 旧格式规划导出必须保持不受运行时 `speedRatio` 影响;`ActualSend*` 和 `ShotEvents.json` 必须统一到真实发送队列上。 + +该方案可以在不放宽校验阈值、不中断现有触发语义的前提下,把 `speedRatio` 调速从“发送阶段经验插值”收敛为“规划/准备阶段完成、运行时确定发送”的执行机制。 diff --git a/docs/legacy-fit-uttc-ms11-plan.md b/docs/legacy-fit-uttc-ms11-plan.md new file mode 100644 index 0000000..9b20962 --- /dev/null +++ b/docs/legacy-fit-uttc-ms11-plan.md @@ -0,0 +1,70 @@ +# UTTC_MS11 Legacy Fit 计划 + +## 目标 + +把 `Rvbust/前两个点正常 飞拍失败的运行` 中的旧 1x 轨迹拟合逻辑收敛到当前 replacement 实现里,让 `UTTC_MS11` 在新系统中尽量复现旧系统的轨迹时间轴和中间点形状。 + +当前已确认的事实: + +- `Config/RobotConfig.json` 里的 `UTTC_MS11` 示教点与旧样本一致。 +- 旧 `1倍速度 角度坐标点/waypoint.txt` 已给出 20 个示教点的 legacy 时间节点。 +- 旧 1x 的节点时间与当前规划时间存在稳定比例,约为 `0.742277`。 +- 当前运行时 `ApplySmoothStartStopTiming` 会再次改写时间轴,这会破坏旧 waypoint time 的拟合结果。 + +## 拟合策略 + +优先分两层处理,不把不同问题混成一个旋钮: + +1. 时间轴拟合 + - 先把 `UTTC_MS11` 的规划时间拉回旧 1x 的节点时间。 + - 通过 `planning_speed_scale` 复现旧 `waypoint.txt` 的时间比例。 + - 对 legacy-fit 轨迹,禁止运行时二次平滑起停时间重映射。 + +2. 空间曲线拟合 + - 保持原始示教点不变。 + - 先用当前插补器 + legacy 时间轴做第一版对齐。 + - 如果中间点仍和旧轨迹差异明显,再用旧 `JointDetialTraj.txt` 在 knot 附近反推速度/加速度,升级为 Hermite 拟合。 + +## 计划分解 + +### 1. 配置层 + +- 为 `RobotConfig.json` 增加明确的 legacy-fit 运行开关。 +- 当前现场的 `UTTC_MS11` 显式启用: + - `planning_speed_scale = 0.742277` + - `smooth_start_stop_timing = false` +- 保留默认行为为兼容旧实现,以免影响其他轨迹。 + +### 2. 编排层 + +- `ControllerClientTrajectoryOrchestrator` 读取运行配置后,按开关决定是否调用 `ApplySmoothStartStopTiming`。 +- 缓存键必须包含该开关,避免 legacy-fit 和普通飞拍共用一份结果。 +- `saveTrajectory` / `IsFlyshotTrajectoryValid` 仍然输出规划结果,只是 legacy-fit 轨迹不再被二次改写时间轴。 + +### 3. 运行层 + +- `FanucControllerRuntime` 继续使用 8ms 物理发送周期。 +- DenseSend 实发点数仍按 `duration / (8ms * speedRatio)` 计算。 +- 终点要保留完整落点,不因为非整周期而丢掉最后一个点。 + +### 4. 测试层 + +- 增加配置测试,确认新开关可解析,默认值不破坏旧行为。 +- 增加编排测试,确认 UTTC_MS11 的规划时刻与旧 `waypoint.txt` 一致。 +- 增加运行测试,确认 legacy-fit 目录能写出稳定的 DenseSend 诊断文件。 +- 继续保留原有平滑起停测试,作为“显式开启平滑时”的回归保护。 + +## 验收标准 + +- `UTTC_MS11` 的 waypoint time 与旧 `waypoint.txt` 对齐。 +- `UTTC_MS11` 运行时不再额外套一层平滑重映射。 +- DenseSend 输出稳定,且不再受旧 bin 目录残留影响。 +- 现有默认轨迹和非 UTTC 场景不被破坏。 + +## 后续可能的第二阶段 + +如果时间轴对齐后,中间点仍和旧轨迹有明显偏差,再做第二阶段: + +- 从旧 `JointDetialTraj.txt` 提取 knot 附近速度/加速度。 +- 用 Hermite / quintic Hermite 继续逼近旧曲线形状。 +- 将空间曲线拟合与时间轴拟合分开验收。 diff --git a/docs/move-joint-jerk-comparison-20260505.md b/docs/move-joint-jerk-comparison-20260505.md new file mode 100644 index 0000000..126476c --- /dev/null +++ b/docs/move-joint-jerk-comparison-20260505.md @@ -0,0 +1,160 @@ +# MoveJoint 失败样本六轴限值与 ActualSendJerkStats 对比 + +记录时间:2026-05-05 + +## 1. 目的 + +本文档固定记录以下三类证据,避免后续继续混用测试基线、旧文档结论和当前运行目录中的真实模型数据: + +- 当前运行目录 `.robot` 模型中的六轴基础 `velocity / acceleration / jerk` +- 现场示教器读取到的六轴 `velocity / acceleration / jerk` +- 当前运行目录 `RobotConfig.json` 中的 `acc_limit / jerk_limit` +- 当前失败样本 `ActualSendJerkStats.txt` 中的逐轴实发跃度峰值 + +本次样本对应目录: + +- `.robot`:`src/Flyshot.Server.Host/bin/Debug/net8.0/Config/Models/LR_Mate_200iD_7L.robot` +- 配置:`src/Flyshot.Server.Host/bin/Debug/net8.0/Config/RobotConfig.json` +- 实发跃度:`src/Flyshot.Server.Host/bin/Debug/net8.0/Config/Data/move-joint/DenseSend/20260505_203416_563/ActualSendJerkStats.txt` +- 抓包:`src/Flyshot.Server.Host/bin/Debug/net8.0/Config/Data/move-joint/DenseSend/20260505_203416_563/移动点 跃度过大.pcap` + +## 2. 当前运行模型的真实六轴限值 + +当前仓库运行时通过 `RobotModelLoader.LoadProfile(...)` 从 `.robot` 中读取每轴 `limit.velocity / limit.acceleration / limit.jerk`,然后只对加速度和 jerk 叠加 `RobotConfig.json` 的全局倍率: + +- `velocity_eff = velocity_base` +- `acceleration_eff = acceleration_base * acc_limit` +- `jerk_eff = jerk_base * jerk_limit` + +当前运行目录 `RobotConfig.json` 中: + +- `acc_limit = 0.74` +- `jerk_limit = 0.74` + +按当前运行目录真实模型解出的六轴基础值与生效值如下: + +| Joint | vel_base | acc_base | jerk_base | vel_eff | acc_eff | jerk_eff(rad/s^3) | jerk_eff(deg/s^3) | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| Joint1 | 6.45 | 26.90 | 224.22 | 6.45 | 19.9060 | 165.9228 | 9506.6762 | +| Joint2 | 5.41 | 22.54 | 187.86 | 5.41 | 16.6796 | 139.0164 | 7965.0530 | +| Joint3 | 7.15 | 29.81 | 248.46 | 7.15 | 22.0594 | 183.8604 | 10534.4249 | +| Joint4 | 9.59 | 39.99 | 333.30 | 9.59 | 29.5926 | 246.6420 | 14131.5457 | +| Joint5 | 9.51 | 39.63 | 330.27 | 9.51 | 29.3262 | 244.3998 | 14003.0771 | +| Joint6 | 17.45 | 72.72 | 606.01 | 17.45 | 53.8128 | 448.4474 | 25694.1434 | + +重要结论: + +- 当前运行目录中,`Joint1.jerk_base` 不是测试基线里常见的 `272.7`,而是 `224.22`。 +- 因此当前样本的 `Joint1` 生效 jerk 上限应按 `224.22 * 0.74 = 165.9228 rad/s^3` 计算。 + +## 3. 示教器读取到的实际机器人限值 + +2026-05-06 现场从机器人示教器读取到的速度、加速度与加加速度限制如下。示教器显示口径为角度制;下表同时记录换算后的弧度制,便于与 `.robot` / `RobotProfile` 中的基础限值直接对照。 + +换算公式: + +- `rad = deg * π / 180` + +| Joint | velocity(deg/s) | velocity(rad/s) | acceleration(deg/s^2) | acceleration(rad/s^2) | jerk(deg/s^3) | jerk(rad/s^3) | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| Joint1 | 370 | 6.457718 | 1541.667 | 26.907165 | 12847.223 | 224.226341 | +| Joint2 | 310 | 5.410521 | 1291.667 | 22.543842 | 10763.889 | 187.865303 | +| Joint3 | 410 | 7.155850 | 1708.333 | 29.816036 | 14236.111 | 248.467010 | +| Joint4 | 550 | 9.599311 | 2291.667 | 39.997135 | 19097.223 | 333.309419 | +| Joint5 | 545 | 9.512044 | 2270.833 | 39.633513 | 18923.611 | 330.279318 | +| Joint6 | 1000 | 17.453293 | 4166.667 | 72.722058 | 34722.223 | 606.017115 | + +这组数据的含义: + +- 示教器读取值与当前运行 `.robot` 中的 `velocity_base / acceleration_base / jerk_base` 基本一致,可作为实际机器人基础限值证据。 +- 它还没有叠加当前 `RobotConfig.json` 的 `acc_limit = 0.74` 与 `jerk_limit = 0.74`;若用于本次失败样本比较,仍应使用第 2 节中的 `acc_eff / jerk_eff` 作为生效上限。 + +## 4. ActualSendJerkStats 的单位边界 + +`ActualSendJerkStats.txt` 的真实输入不是弧度,而是 J519 下发用的角度制关节目标: + +1. `SampleDenseJointTrajectoryDegrees(...)` 先把轨迹点从 `rad` 转成 `deg` +2. `BuildDenseSendJointRow(...)` 把这组角度制关节写入 `ActualSendJointTraj.txt` +3. `BuildDenseSendJerkRow(...)` 再直接基于这组角度制关节做三阶差分 + +2026-05-06 之后,`ActualSendJointTraj.txt` 第一列和 `ActualSendJerkStats.txt` 的 `dt` 都使用 J519 实发时间;若需要查看被 `speed_ratio` 缩放后的原轨迹采样时间,应读取同目录的 `ActualSendTiming.txt`。因此当前这份 `ActualSendJerkStats.txt` 的逐轴跃度应按以下方式理解: + +- 文本中的数值口径:`deg/s^3` +- 若要与 `.robot` / `RobotProfile` 中的 jerk limit 比较,需要先换算为 `rad/s^3` +- 换算公式:`jerk_rad = jerk_deg * π / 180` + +## 5. 全文件逐轴最大跃度对比 + +扫描整份 `ActualSendJerkStats.txt` 后,各轴绝对值最大跃度如下: + +| Joint | peak window(s) | peak line | peak actual(deg/s^3) | peak actual(rad/s^3) | jerk_eff(rad/s^3) | peak/limit | +| --- | --- | ---: | ---: | ---: | ---: | ---: | +| Joint1 | `1.056 -> 1.064` | 133 | 21868.115990 | 381.670625 | 165.922800 | 2.3003 | +| Joint2 | `1.056 -> 1.064` | 133 | 40.271793 | 0.702875 | 139.016400 | 0.0051 | +| Joint3 | `1.056 -> 1.064` | 133 | 98.314401 | 1.715910 | 183.860400 | 0.0093 | +| Joint4 | `1.056 -> 1.064` | 133 | 0.207266 | 0.003617 | 246.642000 | 0.0000 | +| Joint5 | `1.056 -> 1.064` | 133 | 26.759688 | 0.467045 | 244.399800 | 0.0019 | +| Joint6 | `1.056 -> 1.064` | 133 | 2.328736 | 0.040644 | 448.447400 | 0.0001 | + +结论非常明确: + +- 全文件范围内,只有 `Joint1` 的实发跃度显著超过当前生效 jerk 上限。 +- 其余 5 个轴即使取全文件峰值,也远低于各自当前生效 jerk limit。 +- 当前样本本质上是一个“J1 主导”的跃度问题,而不是六轴普遍同时逼近上限。 + +## 6. 报警窗口逐轴对比 + +结合抓包与 J519 序号,报警前最后一个关键窗口是: + +- `seq=41552` +- 轨迹时间窗口:`0.296 -> 0.304s` +- `ActualSendJerkStats.txt` 行号:38 + +该窗口逐轴跃度如下: + +| Joint | alarm window jerk(deg/s^3) | alarm window jerk(rad/s^3) | jerk_eff(rad/s^3) | alarm/limit | +| --- | ---: | ---: | ---: | ---: | +| Joint1 | -20395.713579 | 355.972355 | 165.922800 | 2.1454 | +| Joint2 | -37.560252 | 0.655550 | 139.016400 | 0.0047 | +| Joint3 | 91.694793 | 1.600376 | 183.860400 | 0.0087 | +| Joint4 | -0.193310 | 0.003374 | 246.642000 | 0.0000 | +| Joint5 | 24.957931 | 0.435598 | 244.399800 | 0.0018 | +| Joint6 | 2.171939 | 0.037907 | 448.447400 | 0.0001 | + +该窗口的直接结论与全局扫描一致: + +- 机器人开始报警的 `0.296 -> 0.304s` 窗口里,真正越限的仍然只有 `Joint1` +- `Joint1` 在报警窗口内已经达到当前生效 jerk limit 的 `2.1454x` +- 其余 5 轴在同一窗口仍远低于生效 jerk 上限 + +## 7. 报警窗口与全局峰值窗口的关系 + +本次样本不能简单理解为“最大峰值出现的位置就是首次报警位置”。 + +当前证据表明: + +- 首次报警相关窗口在 `0.296 -> 0.304s` +- 全文件最大的 J1 跃度峰值出现在更后面的 `1.056 -> 1.064s` + +这说明至少有两件事需要分开: + +1. 机器人第一次进入异常态时,`Joint1` 已经在 `0.296 -> 0.304s` 超限约 `2.15x` +2. 即便忽略第一次报警,后续轨迹中仍存在更高的 J1 跃度峰值,说明当前 `MoveJoint` 临时轨迹整体都偏激,不只是单个孤立点异常 + +## 8. 当前可落地的结论 + +基于当前运行目录的真实模型、配置和实发跃度文件,本次失败样本可以先固定为下面这组结论: + +- 当前运行模型 `Joint1.jerk_base = 224.22`,不是 `272.7` +- 现场示教器读取到的 `Joint1.jerk = 12847.223 deg/s^3 = 224.226341 rad/s^3`,与当前运行模型基础值一致 +- 当前样本 `jerk_limit = 0.74`,所以 `Joint1.jerk_eff = 165.9228 rad/s^3` +- `ActualSendJerkStats.txt` 需要按 `deg/s^3` 理解,再换算成 `rad/s^3` 后与模型 jerk limit 对比 +- 无论看报警窗口还是看全文件峰值,越限主体都只有 `Joint1` +- 报警窗口 `0.296 -> 0.304s` 中,`Joint1` 已经约为当前生效 jerk 上限的 `2.1454x` +- 全文件最大峰值窗口 `1.056 -> 1.064s` 中,`Joint1` 约为当前生效 jerk 上限的 `2.3003x` + +因此当前最合理的根因指向仍然是: + +- `MoveJoint` 临时轨迹生成得过于激进 +- 当前问题首先应按 `Joint1` 的 jerk 约束失配来处理 +- 暂时没有证据支持“六轴普遍一起逼近限制”或“网络链路导致跃度统计失真”这类解释 diff --git a/docs/planning-duration-mismatch-investigation.md b/docs/planning-duration-mismatch-investigation.md new file mode 100644 index 0000000..674c5c2 --- /dev/null +++ b/docs/planning-duration-mismatch-investigation.md @@ -0,0 +1,592 @@ +# 轨迹规划时长差异调查记录 + +## 背景 + +当前新 C# 规划链路在不额外缩放规划约束时,部分真实现场轨迹会比旧 RVBUST/FlyingShot 导出的 `JointTraj.txt` 更短。 + +最典型现象: + +- 真实 `Rvbust/uttc-20260428/Data/JointTraj.txt`:`UTTC_MS11` 总时长约 `7.403046s`。 +- 新 C# 当前默认规划输出:`src/Flyshot.Server.Host/bin/Debug/net8.0/Config/Data/UTTC_MS11/JointTraj.txt` 总时长约 `5.495112s`。 +- 实体机复核确认:修改运行时 `speed_ratio` 不影响 `IsFlyshotTrajectoryValid` / `SaveTrajectoryInfo` 生成的 `JointTraj.txt` 规划时长。 + +因此,本问题不应继续归因到运行时 `speed_ratio`,而应归到规划阶段的有效关节约束来源。 + +## 已确认事实 + +1. `speed_ratio` 是运行执行倍率。 + + UTTC 抓包和实体机测试都显示,`speed_ratio=0.7` 会拉伸 J519 实际下发时间和包数,但不会改变已生成的 `JointTraj.txt` 规划时间轴。 + +2. `JointTraj.txt` 是规划结果点位。 + + `saveTrajectory` / `SaveTrajInfo` / `IsFlyshotTrajectoryValid(saveTrajectory=true)` 生成的 `JointTraj.txt` 表示规划后的 sparse waypoint 时间轴,不是上传的原始飞拍路径,也不是 J519 逐周期下发点。 + +3. UTTC_MS11 的差异是整条时间轴等比例缩放。 + + 对 `UTTC_MS11`,真实时间和当前 C# 默认规划时间之间的比例在所有 waypoint 上都一致: + + ```text + C#默认规划时间 / 真实规划时间 = 5.495112 / 7.403046 = 0.742277 + ``` + + 这说明路点顺序、相对分段时间和 ICSP 主要逻辑基本一致,差异更像是规划时传入的有效 `vel/acc/jerk` joint limits 存在整体倍率差异。 + +4. 现场配置中没有找到显式倍率字段。 + + 已检查现场现有配置,未发现类似 `planning_speed_scale` 或等价字段保存了 `0.742277`、`0.7`、`0.9` 等规划倍率值。 + +## 样本对比 + +| 样本 | 真实 `JointTraj.txt` 时长 | 当前/已有新规划时长 | 等效倍率 `新规划/真实` | 说明 | +| --- | ---: | ---: | ---: | --- | +| `UTTC_MS11` | `7.403046s` | `5.495112s` | `0.742277` | 所有 waypoint 时间均为同一比例 | +| `UTTC_MS11_TEST01` | `7.805885s` | `5.814370s` | `0.744870` | `20260428 多点` 新增 1 个路径点后仍几乎是整条时间轴等比例缩放 | +| `EOL10_EAU_0` | `14.849788s` | `10.489800s` | `0.706394` | 同样表现为新规划偏快 | +| `EOL9_EAU_0` / `EOL9_EAU_90` | `6.400851s` | `5.651140s` | `0.882873` | `EOL9 EAU 0` 与 `EOL9 EAU 90` 的真实 `JointTraj.txt` 文件一致 | +| `EOL9_EAU_90` | `6.400851s` | `6.471610s` | `1.011055` | 使用 `speedRatio=0.9 + self-adapt-icsp` 的旧离线结果已接近真实 | + +这些样本说明差异不是 `UTTC_MS11` 的个案,也不是一个可以全局写死的常数。不同真实样本对应的等效规划倍率不同。 + +## `20260428 多点` 新样本对比 + +2026-04-30 追加现场新样本: + +```text +../Rvbust/20260428 多点/RobotConfig.json +../Rvbust/20260428 多点/JointTraj.txt +../Rvbust/20260428 多点/JointDetialTraj.txt +``` + +该样本中的飞拍程序名为: + +```text +UTTC_MS11_TEST01 +``` + +配置摘要: + +```text +waypoints=21 +shot_flags=21 +acc_limit=1 +jerk_limit=1 +``` + +实机导出的 `JointTraj.txt`: + +```text +rows=21 +duration=7.805885s +``` + +用当前 C# `ICspPlanner`、同一个 `LR_Mate_200iD_7L.robot`、同一份 `RobotConfig.json` 规划: + +```text +rows=21 +duration=5.814370s +C# / 实机 = 0.744870 +``` + +逐点/逐段统计: + +```text +point_ratio_std = 2.18e-7 +segment_ratio_std = 9.0e-7 +max_joint_diff = 5.0e-7 rad +``` + +这说明: + +1. 新样本的路点关节值与 C# 输入基本完全一致,不是解析错点或单位错位。 +2. 新增 1 个路径点后,C# 与旧系统仍然保持几乎严格的整条时间轴等比例差异。 +3. `UTTC_MS11_TEST01` 的倍率 `0.744870` 与原 `UTTC_MS11` 的 `0.742277` 非常接近,进一步支持“同一类 UTTC 现场导出使用了一组更保守的 effective JointLimits”这一判断。 + +和原 `UTTC_MS11` 对比: + +```text +原 UTTC_MS11 实机 rows=20 duration=7.403046s +新 UTTC_MS11_TEST01 实机 rows=21 duration=7.805885s +新增路径点后实机时长增加 0.402839s +``` + +当前观察不到新增点导致规划形状或局部段比例失真;它更像是在同一套旧系统规划约束下正常增加了一段路径时间。 + +## `20260430.pcap` 初始化抓包复核 + +2026-04-30 继续复核现场提供的完整初始化抓包: + +```text +../Rvbust/20260428 多点/20260430.pcap +``` + +抓包总览: + +```text +packet_count=4821 +tcp_payload_bytes=35302 +udp_payload_bytes=451946 +``` + +主要有效负载会话为: + +```text +UDP 192.168.10.11:60015 -> 192.168.10.10:56118 260700B +UDP 192.168.10.11:60015 -> 192.168.10.10:48455 127116B +UDP 192.168.10.10:48455 -> 192.168.10.11:60015 62088B +TCP 192.168.10.11:10010 -> 192.168.10.10:42106 35102B +TCP 192.168.10.11:10012 -> 192.168.10.10:33528 106B +TCP 192.168.10.10:33528 -> 192.168.10.11:10012 94B +``` + +全包搜索以下明文关键字没有命中: + +```text +Joint / joint / Limit / limit / vel / acc / jerk / Speed / speed / +Robot / JSON / Traj / ratio / Ratio / GetJoint / SetJoint +``` + +`TCP 10012` 命令通道按 `doz + length + message_id + body + zod` 解码后,只看到以下初始化/程序命令: + +| 方向 | 消息号 | 含义 | 请求体/结果 | +| --- | ---: | --- | --- | +| C->R | `0x0001` | 未知握手 | 空请求 | +| R->C | `0x0001` | 未知握手响应 | `result=0` | +| C->R | `0x2000` | 未知版本/状态查询 | 空请求 | +| R->C | `0x2000` | 未知版本/状态响应 | 包含 `0.6.0` 字符串 | +| C->R | `0x2100` | `ResetRobot` | 空请求 | +| R->C | `0x2100` | `ResetRobot` 响应 | `result=0` | +| C->R | `0x2003` | `GetProgramStatus("RVBUSTSM")` | 程序名 `RVBUSTSM` | +| R->C | `0x2003` | 程序状态响应 | `result=0, status=1` | +| C->R | `0x2102` | `StartProgram("RVBUSTSM")` | 程序名 `RVBUSTSM` | +| R->C | `0x2102` | 启动响应 | `result=0` | + +没有看到: + +- `0x2207 SetSpeedRatio` +- `0x2206 GetSpeedRatio` +- `0x2200/0x2201 GetTcp/SetTcp` +- `0x2208/0x2209 GetIo/SetIo` +- 任何疑似 `JointLimits / velocity / acceleration / jerk` 的参数帧 + +`TCP 10010` 状态通道只有机器人侧到上位机的状态帧: + +```text +390 个 90B 状态帧 +1 个 2B 连接前导 +``` + +这些帧与已逆向的 `pose[6] + joint[6] + external_axes[3] + raw_tail_words[4]` 状态布局一致,不携带规划约束。 + +`UDP 60015` J519 通道只出现既有三类长度: + +```text +C->R 8B 初始化包 1 个 +C->R 64B 目标关节命令包 970 个 +R->C 132B 反馈包 2938 个 +``` + +没有出现其他长度的 UDP 参数帧。64B 命令包是 J519 逐周期目标关节/IO 命令,132B 是机器人反馈;这条链路承载的是执行期 streaming motion,而不是旧 RVBUST 规划器的 joint limit 配置。 + +阶段结论: + +```text +20260430.pcap 只覆盖机器人侧 10010 / 10012 / 60015 通信。 +它没有 50001/TCP+JSON,也没有 ControllerServer/Python 客户端到旧服务端的配置调用。 +因此,这份抓包看不到旧规划阶段的 effective JointLimits。 +``` + +这并不否定“旧系统规划瞬间存在更保守的 effective JointLimits”这一方向;它只说明这份初始化抓包不是抓取该信息的位置。若要抓到这类限制,需要抓旧服务端内部 `_GetJointLimits/_SetJointLimits`,或者抓上层 Python/GUI 与 ControllerServer 之间的配置/规划调用,而不是只抓机器人控制柜侧的执行链路。 + +## `all-50001.pcap` 本机 50001 抓包复核 + +2026-04-30 追加复核本机所有网卡抓包: + +```text +../Rvbust/20260428 多点/all-50001.pcap +SHA256=C3543F314AE446CABA8E2097EFAFB36F39DD73FFE166F051A1F9387CFD15990F +``` + +该文件由 `tcpdump -i any` 生成,pcap linktype 为 `113`,即 Linux cooked capture。按 SLL 头解析后,确认抓到了本机到本机的 `50001` TCP JSON 通信: + +```text +192.168.1.100:35814 -> 192.168.1.100:50001 217B payload +192.168.1.100:50001 -> 192.168.1.100:35814 91B payload +``` + +客户端到服务端的完整 JSON 命令序列为: + +```json +{"reply_from_client":true} +{"cmd":"SetUpRobot","robot_name":"FANUC_LR_Mate_200iD_7L"} +{"cmd":"IsSetUp"} +{"cmd":"SetActiveController","sim":false} +{"cmd":"Connect","ip":"192.168.10.11"} +{"buffer_size":8,"cmd":"EnableRobot"} +``` + +服务端返回为: + +```json +{"test_from_server": true} +{"res": true} +{"res": true} +{"res": true} +{"res": true} +{"res": true} +``` + +这说明本次抓包确实覆盖到了旧 `50001` 控制链路,但当前只包含机器人初始化、连接和使能流程。里面没有看到: + +- `ExecuteFlyShotTraj` +- `SaveTrajInfo` +- `IsFlyShotTrajValid` +- `GetJointLimits / SetJointLimits` +- `SetVelocityLimit / SetAccelerationLimit / SetJerkLimit` +- 任何包含 `acc_limit / jerk_limit / JointLimits / velocity / acceleration / jerk` 的配置 JSON + +阶段结论: + +```text +all-50001.pcap 已经证明抓包接口选对了; +但这次只抓到了初始化链路,没有抓到规划/保存轨迹那一刻的 50001 请求。 +``` + +该待确认点已由下一节 `all-50001-plan.pcap` 覆盖:后续抓包确实抓到了 `ExecuteFlyShotTraj(save_traj=true,use_cache=false)`,仍未出现规划限制字段。 + +## `all-50001-plan.pcap` 规划执行抓包复核 + +2026-04-30 追加复核规划/执行动作期间的本机 50001 抓包: + +```text +../Rvbust/20260428 多点/all-50001-plan.pcap +SHA256=311DC45B4789ED11EBEAB7A396E2EE7A16EC8534E20F10127FB43BBAD823C21D +``` + +该抓包同样是 `tcpdump -i any` 生成的 Linux cooked capture,已按 SLL 头解析。有效 TCP JSON 流为: + +```text +192.168.1.100:35814 -> 192.168.1.100:50001 2612B payload +192.168.1.100:50001 -> 192.168.1.100:35814 516B payload +``` + +客户端到服务端的关键命令序列为: + +```json +{"cmd":"ListFlyShotTraj"} +{"cmd":"GetNextListFlyShotTraj","count":0} +{"cmd":"SetSpeedRatio","ratio":0.5} +{"cmd":"ExecuteFlyShotTraj","method":"icsp","move_to_start":true,"name":"UTTC_MS11_TEST01","save_traj":true,"use_cache":false,"wait":true} +{"cmd":"SetSpeedRatio","ratio":1.0} +{"cmd":"ExecuteFlyShotTraj","method":"icsp","move_to_start":true,"name":"UTTC_MS11_TEST01","save_traj":true,"use_cache":false,"wait":true} +{"cmd":"StartUploadFlyShotTraj","name":"UTTC_MS11"} +{"cmd":"UploadFlyShotTraj", "...":"4 批共 20 个 waypoint,每批包含 waypoints / shot_flags / offset_values / addrs"} +{"cmd":"EndUploadFlyShotTraj","name":"UTTC_MS11"} +{"cmd":"ListFlyShotTraj"} +{"cmd":"GetNextListFlyShotTraj","count":0} +``` + +两次执行请求均为: + +```json +{ + "cmd": "ExecuteFlyShotTraj", + "method": "icsp", + "move_to_start": true, + "name": "UTTC_MS11_TEST01", + "save_traj": true, + "use_cache": false, + "wait": true +} +``` + +它们前面的速度倍率分别为: + +```text +第一次:SetSpeedRatio ratio=0.5 +第二次:SetSpeedRatio ratio=1.0 +``` + +服务端对所有命令均返回: + +```json +{"res": true} +``` + +这份抓包确认了两点: + +1. 公开 50001 JSON 链路确实会把 `SetSpeedRatio` 和 `ExecuteFlyShotTraj(save_traj=true,use_cache=false)` 发给旧服务端。 +2. 即便覆盖到了实际执行/保存轨迹动作,请求中仍没有出现 `GetJointLimits / SetJointLimits`、`SetVelocityLimit / SetAccelerationLimit / SetJerkLimit`,也没有 `acc_limit / jerk_limit / velocity / acceleration / jerk / JointLimits` 等规划限制字段。 + +因此,当前能从 50001 抓包确认的是: + +```text +规划方法、是否保存轨迹、是否使用缓存、是否等待执行,都会显式发到旧服务端; +速度倍率通过 SetSpeedRatio 单独发到旧服务端; +但 effective JointLimits 没有通过这次公开 50001 JSON 请求显式传入。 +``` + +这进一步收敛了差异来源:如果旧系统规划时确实使用了更保守的 joint limits,它更可能来自旧服务端在 `SetUpRobot("FANUC_LR_Mate_200iD_7L")` 后加载/初始化的内部状态,或来自 GUI/服务端内部私有路径,而不是这次 50001 公开 JSON 在 `ExecuteFlyShotTraj` 请求中传入的字段。 + +## Joint3/Joint2 couple A/B 测试 + +2026-04-30 追加测试:为了验证 `.robot` 中 `Joint3` 对 `Joint2` 的 couple 是否是规划时长差异主因,使用 Python ICSP demo 做了多组只读 A/B。 + +测试模型来自: + +```text +flyshot-replacement/Config/Models/LR_Mate_200iD_7L.robot +``` + +其中 `Joint3` 的 couple 信息为: + +```text +q3_kin = q3_raw + q2_kin * 1.0 + 0.0 +``` + +测试变体: + +- `raw`:原始 6 轴路点直接规划。 +- `replace_q3=q3+q2`:规划输入中把第 3 轴替换为耦合后的运动学角。 +- `replace_q3=q3-q2`:反向符号试探,排除符号理解错误。 +- `raw+constraint(q3+q2)`:保留原始 6 轴,同时追加虚拟约束轴 `q3+q2`,用 Joint3 的 `vel/acc/jerk` 限值检查。 +- `raw+constraint(q3-q2)`:反向符号的虚拟约束轴试探。 + +结果: + +| 样本 | 真实时长 | 最接近变体 | 变体时长 | 变体/真实 | 与真实差值 | 结论 | +| --- | ---: | --- | ---: | ---: | ---: | --- | +| `UTTC_MS11` | `7.403046s` | `raw` | `5.495112s` | `0.742277` | `1.907934s` | couple 变体全部更短,且破坏原本严格等比例关系 | +| `EOL10_EAU_0` | `14.849788s` | `replace_q3=q3+q2` | `10.600711s` | `0.713863` | `4.249077s` | couple 只改善约 `0.11s`,距离真实仍差 `4.25s` | +| `EOL9_EAU_90` | `6.400851s` | `raw+constraint(q3+q2)` | `5.748560s` | `0.898093` | `0.652291s` | couple 约束有小幅影响,但仍不足以解释真实时长 | + +关键观察: + +1. `UTTC_MS11` 的 `raw` 规划时间和真实时间保持严格等比例,`point_ratio_std=0`、`segment_ratio_std≈0`;加入 couple 后反而出现分段比例波动。 +2. `EOL10_EAU_0` 与 `EOL9_EAU_90` 的 couple 变体只带来小幅时长变化,不能解释 10% 到 30% 级别的差异。 +3. 因此,当前证据不支持“只要把 Joint3/Joint2 couple 带入 ICSP,就能对齐旧 RVBUST 规划时长”。 + +阶段结论: + +`Joint3` couple 确实是 C# 与 Python demo 当前都没有进入规划约束的缺口,但它不像本轮时长 mismatch 的主因。它更可能影响 FK/运动学边界或少数局部段约束;当前主要时长差异仍更像有效 joint limits、旧系统运行期规划倍率、或 RPS 内部 ICSP 参数来源不同。 + +## 同模型复核与更可能的差异层 + +2026-04-30 继续复核: + +1. 当前仓库固化的模型与旧 `FlyingShot/FlyingShot/Models/LR_Mate_200iD_7L.robot` 字节哈希一致。 +2. `ControllerClientCompatRobotCatalog` 当前会把 `FANUC_LR_Mate_200iD` 和 `FANUC_LR_Mate_200iD_7L` 都映射到 `LR_Mate_200iD_7L.robot`。 +3. `LR_Mate_200iD.robot` 短臂模型的前三轴 `vel/acc/jerk` 比 `7L` 更高。用短臂模型试算会让轨迹更短,不会解释“旧系统真实导出更慢”。 + +模型 A/B: + +| 样本 | 真实时长 | `LR_Mate_200iD_7L.robot` | `LR_Mate_200iD.robot` | 结论 | +| --- | ---: | ---: | ---: | --- | +| `UTTC_MS11` | `7.403046s` | `5.495112s` | `5.345600s` | 短臂模型方向更错 | +| `EOL10_EAU_0` | `14.849788s` | `10.489800s` | `10.342456s` | 短臂模型方向更错 | + +因此,如果现场确认机器人模型确实一致,差异层就不应继续放在 `.robot` 静态文件本身,而应放在旧服务端规划时的运行态: + +- 服务端内部存在 `_GetJointLimits / _SetJointLimits`,说明规划消费的是一份可能被运行期覆写的 `current JointLimits`。 +- `ControllerClient.h` 的 `ExecuteFlyShotTraj(..., use_cache=false)` 明确说明旧服务端可以把计算好的轨迹保存在内存中并复用。 +- `SaveTrajInfo(name, method)` 没有 `use_cache` 参数,不能仅凭公开头文件判断它一定每次从当前配置重新规划。 + +当前更合理的解释是: + +```text +同一个 .robot + -> SetUpRobot 初始化基础 JointLimits + -> 旧服务端运行期间可能被 _SetJointLimits / 速度倍率联动 / 缓存轨迹 覆盖 + -> SaveTrajInfo 或 IsFlyShotTrajValid(save_traj=true) 导出的是真正规划时那份状态 + -> 当前 C# 每次用静态 .robot + RobotConfig 重新规划,所以时长更短 +``` + +尤其需要注意:`EOL10_EAU_0` 的 `新规划/真实` 比例为 `0.706394`,接近 `0.7`;`EOL9_EAU_90` 的比例为 `0.882873`,接近 `0.9`。这不像模型误差,更像历史导出时混入了某个运行态速度/限制倍率。`UTTC_MS11` 的 `0.742277` 不等于抓包确认的执行层 `0.7`,所以不能简单把所有样本都归因到 `SetSpeedRatio`,但“运行态规划约束不是静态模型值”仍是目前最强方向。 + +## 旧服务端与 GUI 二进制复核 + +2026-04-30 继续从旧系统二进制字符串中复核,重点看公开 Python/HTTP 层没有暴露出来的运行态对象。 + +### 服务端确实持有 runtime JointLimits + +`../FlyingShot/FlyingShot/Python/ControllerServer/ControllerServer.cpython-37m-x86_64-linux-gnu.so` 中能稳定看到以下方法和关键字: + +```text +ControllerServer.ControllerServer._GetJointLimits +ControllerServer.ControllerServer._SetJointLimits +ControllerServer.ControllerServer._IsWaypointInJointLimits +ControllerServer.ControllerServer._IsTrajInJointLimits +ControllerServer.ControllerServer._IsTrajInJerkLimits +ControllerServer.ControllerServer._ExecuteFlyShotTraj +ControllerServer.ControllerServer._SaveTrajInfo +ControllerServer.ControllerServer._IsFlyShotTrajValid +SetVelocityLimit +SetAccelerationLimit +SetJerkLimit +GetMaxVelocity +GetMaxAcceleration +GetMaxJerk +m_acc_limit +m_jerk_limit +save_traj_only +use_cache +``` + +这比公开 `ControllerClient.h` 暴露的信息更多。它说明旧服务端内部不是只把 `.robot` 静态值直接传给 `TrajectoryRnICSP`,而是存在一份可以查询、设置、校验、再用于规划的运行期 `JointLimits`。 + +### GUI 也直接接触规划约束与保存逻辑 + +旧 GUI 二进制里也能看到同一条链: + +- `../FlyingShot/FlyingShot/Python/GUI/Robot/RobotManager.cpython-37m-x86_64-linux-gnu.so` + - `GetJointLimits` + - `TrajectoryRnICSP` + - `IsTrajInJointLimits` + - `IsTrajInJerkLimits` + - `acc_limit` + - `jerk_limit` +- `../FlyingShot/FlyingShot/Python/GUI/Robot/RobotConfig.cpython-37m-x86_64-linux-gnu.so` + - `SaveTraj` + - `m_acc_limit` + - `m_jerk_limit` +- `../FlyingShot/FlyingShot/Python/GUI/Panels/FlyshotDockPanel.cpython-37m-x86_64-linux-gnu.so` + - `__SaveTraj` + - `IsTrajInJointLimits` + - `IsTrajInJerkLimits` + - `m_acc_limit` + - `m_jerk_limit` + +这说明旧 GUI 的“保存轨迹/检查轨迹”路径很可能不是简单调用公开 `ControllerClient.SaveTrajInfo` 后结束,而是直接拿当前 `JointLimits + acc_limit + jerk_limit` 做规划、合法性检查或保存。 + +### UAES 接口没有显式对齐 JointLimits + +`../flyshot-uaes-interface/main.py` 中 `/execute_flyshot/` 的执行路径是: + +```text +c.ExecuteFlyShotTraj(name=name, move_to_start=True, method="icsp", save_traj=True) +``` + +`/set_speedRatio/` 是单独接口: + +```text +c.SetSpeedRatio(speed) +``` + +同时,`../flyshot-uaes-interface/lib/PyControllerClient.cpython-37m-x86_64-linux-gnu.so` 和 `../flyshot-uaes-interface/lib/libControllerClient.so` 中只看到公开客户端侧的: + +```text +GetSpeedRatio +SetSpeedRatio +ExecuteFlyShotTraj +SaveTrajInfo +IsFlyShotTrajValid +JointLimits +``` + +没有看到客户端侧 `GetJointLimits / SetJointLimits` 符号。也就是说,UAES Python 服务本身大概率没有主动把旧服务端的 runtime JointLimits 设置成某个值;如果现场旧导出时的 limits 被改过,更可能来自: + +- 旧 GUI 初始化/保存路径; +- 旧服务端内部默认初始化; +- 服务端隐藏 TCP JSON 方法; +- 历史上某次执行/保存后留下的缓存结果。 + +### 样本文件与配置文件可能不是同一次运行态 + +新增一个需要警惕的现象: + +- `../Rvbust/EOL9 EAU 0/eol9_eau_0.json` 中 `acc_limit=1`、`jerk_limit=1`。 +- `../Rvbust/EOL9 EAU 90/eol9_eau_90.json` 中 `acc_limit=0.8`、`jerk_limit=0.8`。 +- 但两个目录下保存的真实 `JointTraj.txt` 内容和时长一致。 + +哈希复核: + +```text +EOL9 EAU 0 JointTraj.txt SHA256=DFD8E1130742CFB4ED72F70D0E8CA4E3A16F421E0D0D9D921B9F5177717536EC +EOL9 EAU 90 JointTraj.txt SHA256=DFD8E1130742CFB4ED72F70D0E8CA4E3A16F421E0D0D9D921B9F5177717536EC +eol9_eau_0.json SHA256=354D0D3F71499951976504802C4B2860132D1E4FF753738715A500529CD0BB68 +eol9_eau_90.json SHA256=7F854AA227D842CAE734AFA378FEEFA742D797F99FBE536E1B98DF981CD32B27 +``` + +这说明不能默认认为“某个 JSON 文件当前内容”就一定是旁边 `Data/JointTraj.txt` 的生成状态。旧系统的保存文件可能来自缓存、拷贝、历史运行态,或 GUI/服务端中未落盘到该 JSON 的当前 `JointLimits`。 + +本轮新增证据把方向进一步收敛为: + +```text +同一个 .robot 文件本身不是问题核心; +真正影响时长的是旧系统规划瞬间的 effective JointLimits; +但这份状态没有出现在现有配置、机器人侧抓包或 50001 公开 JSON 中。 +``` + +如果未来能直接进入旧服务端进程,仍可在 `SaveTrajInfo` / `IsFlyShotTrajValid(save_traj=true)` 前后抓取 `_GetJointLimits` 返回值,并把它与 `.robot` 原始 `vel/acc/jerk` 和当前 JSON 的 `acc_limit/jerk_limit` 做数值对比。但这不再阻塞 replacement 的现场对齐:当前设计默认用显式内部规划加速度参数补齐这份不可见状态。 + +## 当前判断 + +当前最可信的解释是: + +1. 旧 RVBUST/FlyingShot 生成真实 `JointTraj.txt` 时,规划阶段使用的有效 joint limits 并不总是 `.robot` 文件中的原始 `velocity / acceleration / jerk`。 +2. 这些有效 joint limits 可能来自服务运行期状态,例如旧服务端内部的 `_SetJointLimits`、上层 GUI/脚本初始化流程、机器人环境配置,或其他未落入当前 JSON 文件的运行时参数。 +3. 现有现场 JSON 中只明确保存了: + - `acc_limit` + - `jerk_limit` + - `adapt_icsp_try_num` + - IO 相关配置 +4. 已重新抓取机器人侧 `10010/10012/60015` 和本机 `50001/TCP+JSON`,仍没有看到 `JointLimits / velocity / acceleration / jerk / acc_limit / jerk_limit` 通过公开链路在规划时下发。 +5. 目前没有证据表明现场配置文件或公开 TCP JSON 显式保存了一个“规划速度倍率”或“规划加速度限制”。 + +因此,`0.742277` 不应被理解为固定业务常量。它只是 `UTTC_MS11` 在当前 C# 默认约束和真实导出结果之间反推出来的等效规划倍率。 + +## 兼容设计决策 + +由于重新抓包后仍抓不到旧系统的 effective limits,新系统后续不再继续假设公开链路会传入这份数据,而是采用 replacement-only 的显式规划约束参数补齐不可见状态。 + +参数分层如下: + +1. `acc_limit / jerk_limit` + - 来源:旧 `RobotConfig.json` 中已经存在的字段。 + - 语义:继续作为旧配置的基础倍率,参与 `.robot` 模型加载。 + - 限制:现场样本中 `acc_limit=1`、`jerk_limit=1` 时,不能解释旧导出轨迹更慢的问题。 + +2. `planning_acceleration_scale` + - 来源:新系统内部兼容参数,不声称来自旧 RVBUST 配置或抓包。 + - 语义:只用于规划阶段,额外缩放 `JointLimit.AccelerationLimit`,用于复现旧服务端不可见的保守加速度约束。 + - 默认值:`1.0`,表示不额外限制。 + - 现场校准:若按纯加速度限制解释 `UTTC_MS11_TEST01`,可先用 `(5.814370 / 7.805885)^2 ≈ 0.5548` 作为候选起点,再用真实 `JointTraj.txt` 对拍确认。 + +3. `planning_speed_scale` + - 来源:当前 C# 已支持的显式兼容字段。 + - 语义:把整条规划时间轴按速度倍率解释,联动缩放速度、加速度、jerk。 + - 定位:保留为临时整体验证开关;当后续落地 `planning_acceleration_scale` 后,现场默认优先使用加速度限制参数,而不是把 `planning_speed_scale` 当成旧系统事实。 + +当前 C# 已支持的 `planning_speed_scale` 形式为: + +```json +{ + "robot": { + "planning_speed_scale": 0.742277 + } +} +``` + +该字段只用于规划阶段: + +- `vel *= planning_speed_scale` +- `acc *= planning_speed_scale^2` +- `jerk *= planning_speed_scale^3` + +它不等同于运行时 `/set_speedRatio/`,也不改变 J519 的 8ms 发送周期。运行阶段仍按: + +```text +t_send = k * 0.008 +t_traj = t_send * speed_ratio +``` + +从已生成轨迹中重采样。 + +由于现场真实配置和本轮抓包中都没有找到这类倍率,所有 `planning_*` 字段都必须标注为 replacement-only 兼容校准参数,不能声称它们来自旧配置文件或公开 TCP JSON。 + +## 后续设计方向 + +1. 默认不再把运行时 `speed_ratio` 混入 `IsFlyshotTrajectoryValid` / `SaveTrajectoryInfo` 的规划时间计算。 +2. 后续实现优先新增 `planning_acceleration_scale`,只限制规划加速度,并将其写入 `RobotConfig.json` 的 `robot` 节点或当前现场默认配置。 +3. 若只需快速对齐整条时间轴,可临时使用现有 `planning_speed_scale`;但文档、日志和配置说明必须标注它是新系统校准值。 +4. 如果未来能直接调用旧服务端 `_GetJointLimits`,再用返回值替换当前反推参数;在此之前,显式内部参数是当前可控且可审计的兼容策略。 diff --git a/docs/python-client-interface-reverse-engineering.md b/docs/python-client-interface-reverse-engineering.md new file mode 100644 index 0000000..45723ee --- /dev/null +++ b/docs/python-client-interface-reverse-engineering.md @@ -0,0 +1,202 @@ +# Python ControllerClient 接口逆向记录 + +## 背景 + +本记录用于确认旧 `PyControllerClient` 对 Python 暴露了哪些接口,尤其确认是否能通过 Python client 直接查询或设置旧服务端运行态 `JointLimits`。 + +复核对象: + +```text +../flyshot-uaes-interface/lib/PyControllerClient.cpython-37m-x86_64-linux-gnu.so +../FlyingShot/FlyingShot/Lib/PyControllerClient.cpython-37m-x86_64-linux-gnu.so +../flyshot-uaes-interface/lib/libControllerClient.so +../FlyingShot/FlyingShot/Lib/libControllerClient.so +../FlyingShot/FlyingShot/Include/ControllerClient/ControllerClient.h +../FlyingShot/FlyingShot/Include/ControllerClient/Types.h +../flyshot-uaes-interface/UseControllerClient.py +../flyshot-uaes-interface/main.py +``` + +两份 Python 扩展与两份底层 client 库哈希一致: + +```text +PyControllerClient.cpython-37m-x86_64-linux-gnu.so +SHA256=648CC23CBC6DF83822B58AC4A10211EE1DF8029AD8933D31032187748DF7F4BC + +libControllerClient.so +SHA256=6D6FD3F20F0791F1CF11EEE5B1D479E2DCB6A1A2C8AB00A1165575BAB4B62813 +``` + +因此 `flyshot-uaes-interface/lib` 与 `FlyingShot/FlyingShot/Lib` 中的 Python client 可视为同一份接口。 + +## 暴露的 Python 类型 + +`PyControllerClient` 暴露以下类型: + +| 类型 | 来源 | 说明 | +| --- | --- | --- | +| `ControllerClient` | `ControllerClient.h` | TCP JSON client,高层控制入口 | +| `JointPositions` | `Types.h` | 关节位置容器,可用 6 维列表构造,也支持下标读写 | +| `Pose` | `Types.h` | TCP/末端位姿容器,C++ 侧为 7 元数组 | +| `JointLimits` | `Types.h` | 关节上下限、速度、加速度、jerk 容器 | +| `IOType` | `Types.h` | IO 枚举 | + +`IOType` 的枚举值: + +```text +IOType.kIOTypeDI = 1 +IOType.kIOTypeDO = 2 +IOType.kIOTypeRI = 8 +IOType.kIOTypeRO = 9 +``` + +## ControllerClient 暴露方法 + +二进制字符串和 C++ 公开头文件交叉确认,Python client 暴露的方法为: + +| Python 方法 | 典型调用 | 返回形态 | 说明 | +| --- | --- | --- | --- | +| `ConnectServer` | `c.ConnectServer(server_ip="127.0.0.1", port=50001)` | `bool` | 连接旧 `50001/TCP+JSON` 服务端 | +| `GetServerVersion` | `c.GetServerVersion()` | `str` | Python 包装层把 C++ out 参数折叠成返回值 | +| `GetClientVersion` | `c.GetClientVersion()` | `str` | 获取 client 版本 | +| `SetUpRobot` | `c.SetUpRobot("FANUC_LR_Mate_200iD")` | `bool` | 按机器人名称初始化服务端机器人模型 | +| `SetUpRobotFromEnv` | `c.SetUpRobotFromEnv(env_file)` | `bool` | 从环境文件初始化 | +| `IsSetUp` | `c.IsSetUp()` | `bool` | 判断服务端是否已经初始化机器人 | +| `SetShowTCP` | `c.SetShowTCP(is_show=True, axis_length=0.1, axis_size=2)` | `bool` | 仿真显示 TCP 坐标系 | +| `GetName` | `c.GetName()` | `str` | 获取机器人名称 | +| `GetDoF` | `c.GetDoF()` | `int` | 获取自由度 | +| `SetActiveController` | `c.SetActiveController(sim=True)` | `bool` | 切换仿真/真实控制器 | +| `Connect` | `c.Connect("192.168.10.101")` | `bool` | 连接机器人控制器 | +| `Disconnect` | `c.Disconnect()` | `bool` | 断开机器人控制器 | +| `EnableRobot` | `c.EnableRobot()` / `c.EnableRobot(8)` | `bool` | 使能机器人,参数为 buffer size | +| `DisableRobot` | `c.DisableRobot()` | `bool` | 下使能 | +| `GetSpeedRatio` | `c.GetSpeedRatio()` | `float` | 获取执行速度倍率 | +| `SetSpeedRatio` | `c.SetSpeedRatio(0.8)` | `bool` | 设置执行速度倍率 | +| `GetTCP` | `res, tcp = c.GetTCP()` | `(bool, Pose)` | 获取 TCP | +| `SetTCP` | `c.SetTCP(tcp)` | `bool` | 设置 TCP | +| `GetIO` | `res, value = c.GetIO(port=1, io_type=IOType.kIOTypeDI)` | `(bool, bool)` | 读取 IO | +| `SetIO` | `c.SetIO(port=1, value=True, io_type=IOType.kIOTypeDO)` | `bool` | 写 IO | +| `StopMove` | `c.StopMove()` | `bool` | 停止运动 | +| `GetJointPosition` | `res, joints = c.GetJointPosition()` | `(bool, JointPositions)` | 获取当前关节角 | +| `GetPose` | `res, pose = c.GetPose()` | `(bool, Pose)` | 获取当前末端位姿 | +| `GetNearestIK` | `res, ik = c.GetNearestIK(pose, joint_seed=joints)` | `(bool, JointPositions)` | 按 seed 求最近 IK | +| `MoveJoint` | `c.MoveJoint(joint_positions)` | `bool` | 关节运动 | +| `ExecuteTrajectory` | `c.ExecuteTrajectory(waypoints=[...], method="icsp", save_traj=True)` | `bool` | 执行普通轨迹 | +| `UploadFlyShotTraj` | `c.UploadFlyShotTraj(name, waypoints, shot_flags, offset_values, addrs)` | `bool` | 上传飞拍轨迹 | +| `DeleteFlyShotTraj` | `c.DeleteFlyShotTraj(name)` | `bool` | 删除飞拍轨迹 | +| `ListFlyShotTraj` | `c.ListFlyShotTraj()` | `list[str]` | 列出已上传飞拍轨迹 | +| `ExecuteFlyShotTraj` | `c.ExecuteFlyShotTraj(name, move_to_start=True, method="icsp", save_traj=True)` | `bool` | 执行飞拍轨迹 | +| `SaveTrajInfo` | `c.SaveTrajInfo(name, method="icsp")` | `bool` | 保存规划结果到 `~/Rvbust/Data` | +| `IsFlyShotTrajValid` | `valid, time = c.IsFlyShotTrajValid(name, method="icsp", save_traj=True)` | `(bool, float)` | 检查飞拍轨迹是否合法并返回规划时长 | + +## 没有暴露的关键接口 + +本轮重点确认:Python client 暴露方法中没有看到: + +```text +GetJointLimits +SetJointLimits +_GetJointLimits +_SetJointLimits +``` + +虽然 `PyControllerClient` 绑定了 `JointLimits` 类型,并且 `libControllerClient.so` 中存在 `JointLimits` 的输出运算符符号,但公开 `ControllerClient` 方法表中没有任何接收或返回 `JointLimits` 的 client 入口。 + +这和旧服务端二进制不同。旧服务端 `ControllerServer.cpython-37m-x86_64-linux-gnu.so` 中能看到: + +```text +ControllerServer.ControllerServer._GetJointLimits +ControllerServer.ControllerServer._SetJointLimits +ControllerServer.ControllerServer._IsWaypointInJointLimits +ControllerServer.ControllerServer._IsTrajInJointLimits +ControllerServer.ControllerServer._IsTrajInJerkLimits +``` + +因此当前判断是: + +```text +Python client 公开 API 不能直接抓 runtime JointLimits; +runtime JointLimits 查询能力存在于旧服务端内部,而不是 PyControllerClient 公开接口中。 +``` + +## UAES Python 服务实际使用的接口 + +`../flyshot-uaes-interface/main.py` 只使用了公开 client 方法: + +- `ConnectServer` +- `SetUpRobot` +- `IsSetUp` +- `EnableRobot` +- `DisableRobot` +- `SetActiveController` +- `Connect` +- `GetName` +- `GetServerVersion` +- `GetDoF` +- `GetSpeedRatio` +- `SetTCP` +- `GetTCP` +- `SetIO` +- `GetJointPosition` +- `MoveJoint` +- `ListFlyShotTraj` +- `UploadFlyShotTraj` +- `ExecuteFlyShotTraj` +- `SetSpeedRatio` +- `DeleteFlyShotTraj` +- `GetPose` + +其中 `/execute_flyshot/` 调用: + +```text +c.ExecuteFlyShotTraj(name=name, move_to_start=True, method="icsp", save_traj=True) +``` + +`/set_speedRatio/` 调用: + +```text +c.SetSpeedRatio(speed) +``` + +没有看到 UAES 服务通过 Python client 设置或查询 `JointLimits`。 + +2026-04-30 追加 `50001/TCP+JSON` 抓包复核后,这个判断进一步收敛。`all-50001-plan.pcap` 中已经抓到两次真实规划/执行请求: + +```json +{"cmd":"SetSpeedRatio","ratio":0.5} +{"cmd":"ExecuteFlyShotTraj","method":"icsp","move_to_start":true,"name":"UTTC_MS11_TEST01","save_traj":true,"use_cache":false,"wait":true} +{"cmd":"SetSpeedRatio","ratio":1.0} +{"cmd":"ExecuteFlyShotTraj","method":"icsp","move_to_start":true,"name":"UTTC_MS11_TEST01","save_traj":true,"use_cache":false,"wait":true} +``` + +请求中仍没有 `JointLimits / acc_limit / jerk_limit / velocity / acceleration / jerk`。因此公开 Python client 与公开 50001 JSON 都没有把规划限制作为参数传给 `ExecuteFlyShotTraj`。 + +另外,`main.py` 的 `/execute_trajectory/` 中出现: + +```text +c.yrxm(waypoints=joint_positions, method='icsp', save_traj=True) +``` + +`yrxm` 不在 `PyControllerClient` 暴露方法表中,按上下文应是 `ExecuteTrajectory` 的笔误;这条不影响飞拍主路径 `/execute_flyshot/`。 + +## 对当前时长差异调查的含义 + +如果要抓旧系统规划时使用的 effective `vel/acc/jerk`,优先级应调整为: + +1. 在旧服务端进程内直接调用或插桩 `_GetJointLimits`。 +2. 或者逆向 `50001/TCP+JSON` 的 hidden command envelope,再尝试发送 `GetJointLimits` / `_GetJointLimits`。 +3. 不应指望现有 `PyControllerClient.ControllerClient` 直接提供 `GetJointLimits`。 + +如果短期内无法进入旧服务端内部,新系统不再继续等待这份不可见状态;设计上使用 replacement-only 的内部规划约束参数补齐,优先限制规划加速度,例如 `planning_acceleration_scale`。该参数必须标注为新系统校准值,不能写成旧 Python client 或旧 50001 JSON 的公开字段。 + +最小现场验证脚本可以先确认 Python client 暴露面: + +```python +from PyControllerClient import ControllerClient + +c = ControllerClient() +names = [x for x in dir(c) if "Limit" in x or "limit" in x] +print(names) +``` + +按当前二进制逆向,预期不会出现 `GetJointLimits` / `SetJointLimits`。 diff --git a/docs/robot-joint-limit-table-20260505.md b/docs/robot-joint-limit-table-20260505.md new file mode 100644 index 0000000..d9c9274 --- /dev/null +++ b/docs/robot-joint-limit-table-20260505.md @@ -0,0 +1,62 @@ +# 机器人六轴限值提取表 + +记录时间:2026-05-05 + +## 1. 目的 + +本文档把当前机器人模型中的六轴基础限值整理成一份固定表格,明确区分以下几类信息: + +- 模型原始值:来自 `LR_Mate_200iD_7L_clean.json` 中每个关节的 `limit.velocity / limit.acceleration / limit.jerk / limit.effort` +- 配置倍率:来自当前运行配置 `Config/RobotConfig.json` 中的 `acc_limit / jerk_limit` +- 运行时有效值:模型原始值叠加当前配置倍率后的结果 + +本表只覆盖六个旋转关节 `Joint1` 到 `Joint6`。 +`JointEffector` 属于末端固定关节,不计入“每个轴”的速度、加速度、跃度统计。 + +## 2. 当前取值规则 + +当前仓库运行时对六轴限值的读取规则是: + +- `velocity_eff = velocity_base` +- `acceleration_eff = acceleration_base * acc_limit` +- `jerk_eff = jerk_base * jerk_limit` + +当前 `Config/RobotConfig.json` 中的倍率为: + +- `acc_limit = 1` +- `jerk_limit = 1` + +因此本次表格中的“基础值”和“有效值”数值相同。 + +## 3. 六轴限值表 + +| Joint | velocity_base | acceleration_base | jerk_base | effort_raw | acc_limit | jerk_limit | velocity_eff | acceleration_eff | jerk_eff | 备注 | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | +| Joint1 | 6.45 | 26.90 | 224.22 | 0.0 | 1 | 1 | 6.45 | 26.90 | 224.22 | 模型字段存在,`effort` 当前为 0 | +| Joint2 | 5.41 | 22.54 | 187.86 | 0.0 | 1 | 1 | 5.41 | 22.54 | 187.86 | 模型字段存在,`effort` 当前为 0 | +| Joint3 | 7.15 | 29.81 | 248.46 | 0.0 | 1 | 1 | 7.15 | 29.81 | 248.46 | 模型字段存在,`effort` 当前为 0 | +| Joint4 | 9.59 | 39.99 | 333.30 | 0.0 | 1 | 1 | 9.59 | 39.99 | 333.30 | 模型字段存在,`effort` 当前为 0 | +| Joint5 | 9.51 | 39.63 | 330.27 | 0.0 | 1 | 1 | 9.51 | 39.63 | 330.27 | 模型字段存在,`effort` 当前为 0 | +| Joint6 | 17.45 | 72.72 | 606.01 | 0.0 | 1 | 1 | 17.45 | 72.72 | 606.01 | 模型字段存在,`effort` 当前为 0 | + +## 4. 关于“电流信息”的说明 + +这份模型文件里确实有 `limit.effort` 字段,但当前证据只能说明: + +- 它是模型文件中的一个静态字段 +- 当前六轴以及 `JointEffector` 的 `effort` 都是 `0.0` +- 当前 `flyshot-replacement` 代码链路没有把它当作实时电流来源来使用 + +因此当前结论应固定为: + +- `velocity / acceleration / jerk` 可以从这份模型文档中提取 +- `effort` 只能当作模型原始字段记录 +- `effort` 不能直接解释为现场真实电流,也不能替代 J519 反馈中的电机电流数据 + +## 5. 后续使用约定 + +后续如果有人问“每个轴的速度、加速度、跃度是不是从这个文档来的”,默认回答应为: + +- 是,六轴基础限值来自模型文件中的 `joint.limit` +- 运行时有效加速度和有效跃度还要再乘 `RobotConfig.json` 的全局倍率 +- 不是,真实电流不要从这个文件里的 `effort` 去推断 diff --git a/docs/status-jog-joystick-demo.html b/docs/status-jog-joystick-demo.html new file mode 100644 index 0000000..849c8be --- /dev/null +++ b/docs/status-jog-joystick-demo.html @@ -0,0 +1,657 @@ + + + + + + 状态页摇杆点动 Demo + + + +
+
+

Flyshot Replacement 状态监控

+
Connected
+
+
+
+
+
+
机器人
+
FANUC
+
+
+
速度倍率
+
0.70
+
+
+
运动中
+
否
+
+
+
TCP 位姿
+
120.0, 80.0, 320.0
+
+
+ +
+

直角坐标点动

+
+ + +
等待点动输入
+
+ +
+
+
+

XYZ 平移摇杆

+ mm +
+
+ + +
+ + +
+ + +
+
+ X 左右 + Y 上下 + Z 中心 +
+
+ +
+
+

WPR 姿态摇杆

+ deg +
+
+ + +
+ + +
+ + +
+
+ W 左右 + P 上下 + R 中心 +
+
+
+
+ +
+
+

请求日志

+ +
+
+
+ --:--:-- + 等待输入 + 按住任意摇杆方向后,这里会记录每次模拟 MovePose 请求。 +
+
+
+
+ + + + diff --git a/docs/superpowers/plans/2026-04-24-minimal-runtime-orchestration.md b/docs/superpowers/plans/2026-04-24-minimal-runtime-orchestration.md new file mode 100644 index 0000000..6e974fb --- /dev/null +++ b/docs/superpowers/plans/2026-04-24-minimal-runtime-orchestration.md @@ -0,0 +1,272 @@ +# Minimal Runtime Orchestration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build the first real execution slice after the HTTP-only refactor by routing `ExecuteTrajectory` and `ExecuteFlyShotTraj` through planning, triggering, and a new minimal FANUC runtime project instead of in-memory last-point assignment. + +**Architecture:** Keep `Flyshot.Server.Host` as a pure HTTP adapter and keep uploaded program state in `Flyshot.ControllerClientCompat`, but move controller runtime state into a new `Flyshot.Runtime.Fanuc` project and add a focused planning/orchestration helper in `Flyshot.ControllerClientCompat`. Ordinary trajectory execution will use `ICspPlanner`; uploaded flyshot execution will use `SelfAdaptIcspPlanner` plus `ShotTimelineBuilder`, then hand the resulting `TrajectoryResult` to the runtime. + +**Tech Stack:** C#, .NET 8, xUnit, existing `Flyshot.Core.Domain`, `Flyshot.Core.Planning`, `Flyshot.Core.Triggering`, ASP.NET Core DI. + +--- + +### Task 1: Add Runtime Contracts And Minimal FANUC Runtime + +**Files:** +- Create: `src/Flyshot.Runtime.Common/IControllerRuntime.cs` +- Create: `src/Flyshot.Runtime.Fanuc/Flyshot.Runtime.Fanuc.csproj` +- Create: `src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs` +- Modify: `FlyshotReplacement.sln` +- Modify: `src/Flyshot.Server.Host/Flyshot.Server.Host.csproj` +- Modify: `src/Flyshot.Server.Host/Program.cs` +- Test: `tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs` + +- [x] **Step 1: Write the failing runtime test** + +```csharp +[Fact] +public void FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions() +{ + var runtime = new FanucControllerRuntime(); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + runtime.SetActiveController(sim: false); + runtime.Connect("192.168.10.101"); + runtime.EnableRobot(bufferSize: 2); + + var result = new TrajectoryResult( + programName: "demo", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(1.2), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4); + + runtime.ExecuteTrajectory(result, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + + var snapshot = runtime.GetSnapshot(); + Assert.Equal("Connected", snapshot.ConnectionState); + Assert.False(snapshot.IsInMotion); + Assert.Equal([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], snapshot.JointPositions); +} +``` + +- [x] **Step 2: Run test to verify it fails** + +Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions -v minimal -m:1 -nodeReuse:false` +Expected: FAIL because `FanucControllerRuntime` and `IControllerRuntime` do not exist. + +- [x] **Step 3: Write the minimal runtime contracts and implementation** + +```csharp +public interface IControllerRuntime +{ + void ResetRobot(RobotProfile robot, string robotName); + void SetActiveController(bool sim); + void Connect(string robotIp); + void Disconnect(); + void EnableRobot(int bufferSize); + void DisableRobot(); + void StopMove(); + double GetSpeedRatio(); + void SetSpeedRatio(double ratio); + IReadOnlyList GetTcp(); + void SetTcp(double x, double y, double z); + bool GetIo(int port, string ioType); + void SetIo(int port, bool value, string ioType); + IReadOnlyList GetJointPositions(); + IReadOnlyList GetPose(); + ControllerStateSnapshot GetSnapshot(); + void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList finalJointPositions); +} +``` + +```csharp +public sealed class FanucControllerRuntime : IControllerRuntime +{ + // Stage-1 runtime: owns controller state in one place so later sockets can replace internals without rewriting compat service. +} +``` + +- [x] **Step 4: Run test to verify it passes** + +Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions -v minimal -m:1 -nodeReuse:false` +Expected: PASS. + +### Task 2: Add Planning And Triggering Orchestration For Execution + +**Files:** +- Create: `src/Flyshot.ControllerClientCompat/PlannedExecutionBundle.cs` +- Create: `src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs` +- Modify: `src/Flyshot.ControllerClientCompat/Flyshot.ControllerClientCompat.csproj` +- Modify: `tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj` +- Test: `tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs` + +- [x] **Step 1: Write the failing orchestration tests** + +```csharp +[Fact] +public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_RejectsThreeTeachPoints() +{ + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + + Assert.Throws(() => + orchestrator.PlanOrdinaryTrajectory(robot, + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.5, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0] + ])); +} + +[Fact] +public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_BuildsShotTimeline() +{ + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + + var bundle = orchestrator.PlanUploadedFlyshot(robot, uploaded); + + Assert.True(bundle.Result.IsValid); + Assert.Single(bundle.Result.ShotEvents); + Assert.Single(bundle.Result.TriggerTimeline); +} +``` + +- [x] **Step 2: Run tests to verify they fail** + +Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter ControllerClientTrajectoryOrchestrator -v minimal -m:1 -nodeReuse:false` +Expected: FAIL because the orchestrator types do not exist. + +- [x] **Step 3: Write the minimal orchestration layer** + +```csharp +public sealed class PlannedExecutionBundle +{ + public PlannedExecutionBundle(PlannedTrajectory plannedTrajectory, ShotTimeline shotTimeline, TrajectoryResult result) + { + PlannedTrajectory = plannedTrajectory; + ShotTimeline = shotTimeline; + Result = result; + } + + public PlannedTrajectory PlannedTrajectory { get; } + public ShotTimeline ShotTimeline { get; } + public TrajectoryResult Result { get; } +} +``` + +```csharp +public sealed class ControllerClientTrajectoryOrchestrator +{ + public PlannedExecutionBundle PlanOrdinaryTrajectory(RobotProfile robot, IReadOnlyList> waypoints) { ... } + public PlannedExecutionBundle PlanUploadedFlyshot(RobotProfile robot, ControllerClientCompatUploadedTrajectory uploaded) { ... } +} +``` + +- [x] **Step 4: Run tests to verify they pass** + +Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter ControllerClientTrajectoryOrchestrator -v minimal -m:1 -nodeReuse:false` +Expected: PASS. + +### Task 3: Rewire ControllerClientCompatService To Runtime + Orchestrator + +**Files:** +- Modify: `src/Flyshot.ControllerClientCompat/ControllerClientCompatService.cs` +- Modify: `src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs` +- Modify: `src/Flyshot.ControllerClientCompat/IControllerClientCompatService.cs` +- Modify: `tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs` +- Modify: `tests/Flyshot.Server.IntegrationTests/ControllerClientCompatRegistrationTests.cs` +- Test: `tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs` +- Test: `tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs` + +- [x] **Step 1: Write the failing compat-service test** + +```csharp +[Fact] +public void ControllerClientCompatService_ExecuteTrajectory_RejectsThreeTeachPointsAfterPlanningIsIntroduced() +{ + var service = TestRobotFactory.CreateCompatService(); + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SetActiveController(sim: false); + service.Connect("192.168.10.101"); + service.EnableRobot(2); + + Assert.Throws(() => + service.ExecuteTrajectory( + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.5, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0] + ])); +} +``` + +- [x] **Step 2: Run test to verify it fails** + +Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter ControllerClientCompatService_ExecuteTrajectory_RejectsThreeTeachPointsAfterPlanningIsIntroduced -v minimal -m:1 -nodeReuse:false` +Expected: FAIL because current service still treats ordinary execution as "move to last waypoint". + +- [x] **Step 3: Rewire service to the runtime and orchestrator** + +```csharp +public sealed class ControllerClientCompatService : IControllerClientCompatService +{ + private readonly IControllerRuntime _runtime; + private readonly ControllerClientTrajectoryOrchestrator _trajectoryOrchestrator; + + public void ExecuteTrajectory(IReadOnlyList> waypoints) + { + var robot = RequireActiveRobot(); + var bundle = _trajectoryOrchestrator.PlanOrdinaryTrajectory(robot, waypoints); + _runtime.ExecuteTrajectory(bundle.Result, bundle.PlannedTrajectory.PlannedWaypoints[^1].Positions); + } + + public void ExecuteTrajectoryByName(string name) + { + var robot = RequireActiveRobot(); + var uploaded = RequireUploadedTrajectory(name); + var bundle = _trajectoryOrchestrator.PlanUploadedFlyshot(robot, uploaded); + _runtime.ExecuteTrajectory(bundle.Result, bundle.PlannedTrajectory.PlannedWaypoints[^1].Positions); + } +} +``` + +- [x] **Step 4: Run focused tests to verify green** + +Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter "ControllerClientCompatService|ControllerClientTrajectoryOrchestrator|FanucControllerRuntime" -v minimal -m:1 -nodeReuse:false` +Expected: PASS. + +- [x] **Step 5: Run integration verification** + +Run: `dotnet test tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj -v minimal -m:1 -nodeReuse:false` +Expected: PASS, with existing HTTP compatibility tests still green. + +### Task 4: Verify Solution Build And Update Progress Docs + +**Files:** +- Modify: `README.md` +- Modify: `AGENTS.md` +- Modify: `docs/controller-client-api-compatibility-requirements.md` + +- [x] **Step 1: Update docs to reflect the new stage** + +```markdown +- [x] 落地最小 FANUC 运行时骨架 +- [x] 将 ExecuteTrajectory / ExecuteFlyShotTraj 接入 Planning + Triggering + Runtime +- [x] 落地 Web 状态页 +- [x] 固化 10010 / 10012 / 60015 FANUC 基础协议帧编解码 +- [ ] 落地真实 10010 / 10012 / 60015 Socket 通讯与现场联调 +``` + +- [x] **Step 2: Run final build** + +Run: `dotnet build FlyshotReplacement.sln --no-restore -v minimal -m:1 -nodeReuse:false` +Expected: PASS with 0 errors. diff --git a/docs/uttc-ms11-jerk-validation-decision-20260511.md b/docs/uttc-ms11-jerk-validation-decision-20260511.md new file mode 100644 index 0000000..5e6ee2d --- /dev/null +++ b/docs/uttc-ms11-jerk-validation-decision-20260511.md @@ -0,0 +1,151 @@ +# UTTC_MS11 跳过 flyshot jerk 阻断的结论记录 + +## 背景 + +2026-05-11 对 `UTTC_MS11` 做了两轮独立 `ConfigRoot` 重新生成与对比: + +1. 保持 flyshot 离散校验中的 `vel/acc/jerk` 全部生效。 +2. 临时只对 flyshot 链路跳过 `jerk` 阻断,仍保留速度与加速度校验。 + +两轮都使用当前程序重新生成: + +- `POST /set_speedRatio/` +- `POST /save_traj_info/` + +并与以下旧抓包侧结果做对比: + +- `../Rvbust/uttc-20260428/2026042802-1_joint_segments/segment_02/JointDetialTraj.txt` +- `../Rvbust/uttc-20260428/2026042802-0.5_joint_segments/segment_02/JointDetialTraj.txt` +- `../Rvbust/uttc-20260428/1倍速度 角度坐标点/真实轨迹JointDetialTraj.txt` + +## 直接结论 + +对当前 `UTTC_MS11` 现场样本,flyshot 链路若继续用离散 `jerk` 作为阻断条件,会触发大量时间轴 stretch,使新结果远慢于旧抓包。 + +当 flyshot 链路只保留速度、加速度校验并跳过 `jerk` 阻断后: + +- 新 `1.0x` 实发时长从 `16.576s` 回落到 `7.552s` +- 新 `0.5x` 实发时长从 `33.152s` 回落到 `15.104s` +- 与旧抓包主运动段相比,只剩很小的时长差异,可接受并忽略 + +因此,当前项目对 `UTTC_MS11` 的实际结论定为: + +- flyshot 链路临时不再把离散 `jerk` 作为阻断条件 +- 仍保留速度、加速度校验 +- `jerk` 诊断文件继续保留,用于分析,不再作为该路径是否可运行的唯一否决条件 + +## 证据 + +### 1. 开启 jerk 阻断时的现象 + +重新生成结果目录: + +- `../temp/regen-20260511-110950/` + +关键结果: + +- `1.0x ActualSendTiming.txt`:`2073` 行,`send_duration=16.576s` +- `0.5x ActualSendTiming.txt`:`4145` 行,`send_duration=33.152s` + +对比旧抓包主段: + +- 旧 `1.0x`:`7.408109s` +- 旧 `0.5x`:`14.808342s` + +可见新结果明显被拉长。 + +### 2. 触发 stretch 的第一条失败证据 + +日志文件: + +- `../temp/regen-20260511-110950/host.log` + +第一条离散失败不是加速度,而是开头窗口的 `Joint1 Jerk`: + +- `轨迹 UTTC_MS11 第 3 行 Joint1 Jerk超限` +- `time=0.008000->0.016000s` +- `actual=-1732.421875` +- `limit=91.698044` +- `ratio=18.8927` + +后续 `CreateLimitCompliantDenseTrajectory(...)` 按 `1.01` 倍持续拉长时间轴,直到通过离散校验,最终把总时长推到约 `16.57s`。 + +### 3. 只跳过 flyshot jerk 阻断后的结果 + +重新生成结果目录: + +- `../temp/regen-nojerk-20260511-113258/` + +关键结果: + +- `1.0x ActualSendTiming.txt`:`945` 行,`send_duration=7.552s` +- `0.5x ActualSendTiming.txt`:`1889` 行,`send_duration=15.104s` + +与旧抓包主段对比: + +| 项目 | 旧抓包时长 | 新结果时长 | 差值 | +| --- | ---: | ---: | ---: | +| `1.0x` | `7.408109s` | `7.552s` | `0.143891s` | +| `0.5x` | `14.808342s` | `15.104s` | `0.295658s` | + +该差异已经很小,当前按“可忽略”处理。 + +## 轨迹误差变化 + +在同一套对比脚本口径下: + +### 开启 jerk 阻断时 + +- `JointDetialTraj` 新 vs 旧真实轨迹:全局 RMS 约 `46.664 deg` +- `ActualSendJointTraj 1.0x` 新 vs 旧抓包:全局 RMS 约 `46.665 deg` +- `ActualSendJointTraj 0.5x` 新 vs 旧抓包:全局 RMS 约 `46.667 deg` + +### 跳过 flyshot jerk 阻断后 + +- `JointDetialTraj` 新 vs 旧真实轨迹:全局 RMS 约 `8.411 deg` +- `ActualSendJointTraj 1.0x` 新 vs 旧抓包:全局 RMS 约 `8.412 deg` +- `ActualSendJointTraj 0.5x` 新 vs 旧抓包:全局 RMS 约 `8.412 deg` + +说明这次差异收敛主要来自去掉 flyshot jerk 阻断后的 stretch,而不是 `speed_ratio` 链路本身被重新设计。 + +## 对 jerk 的解释 + +这次调整不等于“现场 jerk 完全合规”,而是明确区分: + +1. `jerk` 仍然继续计算和导出 +2. `jerk` 仍然可作为诊断指标 +3. 但对当前 `UTTC_MS11` flyshot 现场样本,不再把 `jerk` 作为阻断执行的唯一判据 + +重新生成后的分析文件仍显示: + +- 加速度 ratio 明显在限值内 +- `jerk` ratio 依然可能大于 `1` + +这与旧抓包整理结果的观察一致:当前现场样本里,“加速度合规但跃度超限”是可接受现象,不能单凭 jerk 超限就否决 flyshot 轨迹。 + +## 当前代码决策范围 + +本次代码调整范围刻意收窄为: + +- 只对 flyshot 链路关闭 `jerk` 阻断 +- 保留 flyshot 的速度、加速度校验 +- 不修改 `move_joint` 的 jerk 约束逻辑 + +对应入口包括: + +- `ControllerClientTrajectoryOrchestrator.CreateLimitCompliantDenseTrajectory(...)` +- `FlyshotExecutionSendSequenceBuilder.Build(...)` +- `FlyshotTrajectoryArtifactWriter.WriteActualSendArtifacts(...)` + +`move_joint` 仍保持原先的 jerk 受限实现,不共享这次豁免策略。 + +## 当前验收判断 + +对 `UTTC_MS11`: + +- 只保留 flyshot 速度/加速度校验 +- 跳过 flyshot jerk 阻断 +- 新 `1.0x / 0.5x` 与旧抓包主段的剩余时长差异已经很小 +- 当前判断:这点差异可以忽略 + +后续除非现场再次提供新的反证,否则本结论作为当前项目实现与验收的记录保留。 diff --git a/global.json b/global.json new file mode 100644 index 0000000..6661367 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.420", + "rollForward": "disable" + } +} diff --git a/src/Flyshot.ControllerClientCompat/ControllerClientCompatOptions.cs b/src/Flyshot.ControllerClientCompat/ControllerClientCompatOptions.cs new file mode 100644 index 0000000..100aa18 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/ControllerClientCompatOptions.cs @@ -0,0 +1,46 @@ +namespace Flyshot.ControllerClientCompat; + +/// +/// 表示 HTTP-only ControllerClient 兼容层的基础配置。 +/// +public sealed class ControllerClientCompatOptions +{ + /// + /// 获取或设置对外暴露的服务端版本号。 + /// + public string ServerVersion { get; set; } = "flyshot-replacement-controller-client-compat/0.1.0"; + + /// + /// 获取或设置运行配置根目录;为空时默认使用程序基目录下的 Config。 + /// + public string? ConfigRoot { get; set; } + + /// + /// 获取或设置旧父工作区根目录;仅用于测试或旧样本显式兼容。 + /// + public string? WorkspaceRoot { get; set; } + + /// + /// 解析运行配置根目录,确保运行时默认不再依赖源码仓库位置。 + /// + /// 运行配置根目录的绝对路径。 + public string ResolveConfigRoot() + { + var root = string.IsNullOrWhiteSpace(ConfigRoot) + ? Path.Combine(AppContext.BaseDirectory, "Config") + : ConfigRoot; + + return Path.GetFullPath(root); + } + + /// + /// 解析显式配置的旧父工作区根目录;未配置时返回 null。 + /// + /// 旧父工作区根目录的绝对路径,或 null。 + public string? ResolveLegacyWorkspaceRoot() + { + return string.IsNullOrWhiteSpace(WorkspaceRoot) + ? null + : Path.GetFullPath(WorkspaceRoot); + } +} diff --git a/src/Flyshot.ControllerClientCompat/ControllerClientCompatRobotCatalog.cs b/src/Flyshot.ControllerClientCompat/ControllerClientCompatRobotCatalog.cs new file mode 100644 index 0000000..e5a4e73 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/ControllerClientCompatRobotCatalog.cs @@ -0,0 +1,74 @@ +using Flyshot.Core.Config; +using Flyshot.Core.Domain; + +namespace Flyshot.ControllerClientCompat; + +/// +/// 根据旧版 ControllerClient 的机器人名称,解析当前 replacement 仓库支持的真实模型文件。 +/// +public sealed class ControllerClientCompatRobotCatalog +{ + /// + /// 保存当前现场支持的机器人名称到运行目录 JSON 模型文件名映射。 + /// + private static readonly IReadOnlyDictionary SupportedRobotModelFileMap = new Dictionary(StringComparer.Ordinal) + { + ["FANUC_LR_Mate_200iD"] = "LR_Mate_200iD_7L.json", + ["FANUC_LR_Mate_200iD_7L"] = "LR_Mate_200iD_7L.json" + }; + + private readonly ControllerClientCompatOptions _options; + private readonly RobotModelLoader _robotModelLoader; + + /// + /// 初始化机器人兼容目录解析器。 + /// + /// 兼容层基础配置。 + /// 机器人 JSON 模型加载器。 + public ControllerClientCompatRobotCatalog( + ControllerClientCompatOptions options, + RobotModelLoader robotModelLoader) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _robotModelLoader = robotModelLoader ?? throw new ArgumentNullException(nameof(robotModelLoader)); + } + + /// + /// 根据旧客户端的机器人名称加载对应模型。 + /// + /// 旧客户端传入的机器人名称。 + /// RobotConfig.json 中的加速度倍率。 + /// RobotConfig.json 中的 jerk 倍率。 + /// 兼容层加载出的机器人模型。 + public RobotProfile LoadProfile(string robotName, double accLimitScale = 1.0, double jerkLimitScale = 1.0) + { + if (string.IsNullOrWhiteSpace(robotName)) + { + throw new ArgumentException("机器人名称不能为空。", nameof(robotName)); + } + + if (!SupportedRobotModelFileMap.TryGetValue(robotName, out var modelFileName)) + { + throw new InvalidOperationException($"Unsupported robot name: {robotName}"); + } + + var modelPath = ResolveModelPath(modelFileName); + return _robotModelLoader.LoadProfile(modelPath, accLimitScale, jerkLimitScale); + } + + /// + /// 解析机器人模型路径,只从运行目录 Config/Models 读取当前现场固化的 JSON 模型。 + /// + /// 运行目录 Config/Models 下的机器人 JSON 模型文件名。 + /// 可传给 JSON 模型加载器的模型文件绝对路径。 + private string ResolveModelPath(string modelFileName) + { + var configModelPath = Path.Combine(_options.ResolveConfigRoot(), "Models", modelFileName); + if (File.Exists(configModelPath)) + { + return configModelPath; + } + + return "Not found"; + } +} diff --git a/src/Flyshot.ControllerClientCompat/ControllerClientCompatService.cs b/src/Flyshot.ControllerClientCompat/ControllerClientCompatService.cs new file mode 100644 index 0000000..66f0f6b --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/ControllerClientCompatService.cs @@ -0,0 +1,1149 @@ +using Flyshot.Core.Config; +using Flyshot.Core.Domain; +using Flyshot.Core.Planning.Sampling; +using Flyshot.Runtime.Common; +using Microsoft.Extensions.Logging; + +namespace Flyshot.ControllerClientCompat; + +/// +/// 在宿主进程内实现 HTTP-only ControllerClient 兼容语义,并把控制器状态委托给运行时。 +/// +public sealed class ControllerClientCompatService : IControllerClientCompatService +{ + private readonly object _stateLock = new(); + private readonly object _motionLock = new(); + private readonly Dictionary _uploadedTrajectories = new(StringComparer.Ordinal); + private readonly ControllerClientCompatOptions _options; + private readonly ControllerClientCompatRobotCatalog _robotCatalog; + private readonly IControllerRuntime _runtime; + private readonly ControllerClientTrajectoryOrchestrator _trajectoryOrchestrator; + private readonly RobotConfigLoader _configLoader; + private readonly FlyshotTrajectoryArtifactWriter _artifactWriter; + private readonly JsonFlyshotTrajectoryStore _trajectoryStore; + private readonly ILogger? _logger; + private RobotProfile? _activeRobotProfile; + private string? _configuredRobotName; + private CompatibilityRobotSettings? _robotSettings; + private string? _connectedServerIp; + private int _connectedServerPort; + private bool _showTcp = true; + private double _showTcpAxisLength = 0.1; + private int _showTcpAxisSize = 2; + + /// + /// 初始化一份 HTTP-only 的 ControllerClient 兼容服务。 + /// + /// 兼容层基础配置。 + /// 机器人模型目录。 + /// 控制器运行时。 + /// 轨迹规划与触发编排器。 + /// 旧版 RobotConfig.json 加载器。 + /// saveTrajectory 规划结果点位导出器。 + /// 统一 RobotConfig.json 持久化存储;为空时按配置根目录创建默认实例。 + /// 日志记录器;允许测试直接构造时传入 null。 + public ControllerClientCompatService( + ControllerClientCompatOptions options, + ControllerClientCompatRobotCatalog robotCatalog, + IControllerRuntime runtime, + ControllerClientTrajectoryOrchestrator trajectoryOrchestrator, + RobotConfigLoader configLoader, + FlyshotTrajectoryArtifactWriter? artifactWriter = null, + JsonFlyshotTrajectoryStore? trajectoryStore = null, + ILogger? logger = null) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _robotCatalog = robotCatalog ?? throw new ArgumentNullException(nameof(robotCatalog)); + _runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); + _trajectoryOrchestrator = trajectoryOrchestrator ?? throw new ArgumentNullException(nameof(trajectoryOrchestrator)); + _configLoader = configLoader ?? throw new ArgumentNullException(nameof(configLoader)); + _artifactWriter = artifactWriter ?? new FlyshotTrajectoryArtifactWriter(_options, new RobotModelLoader()); + _trajectoryStore = trajectoryStore ?? new JsonFlyshotTrajectoryStore(_options, _configLoader); + _logger = logger; + } + + /// + public string ServerVersion => _options.ServerVersion; + + /// + public bool IsSetUp + { + get + { + lock (_stateLock) + { + return _activeRobotProfile is not null; + } + } + } + + /// + /// 获取当前运行时是否处于运动态。 + /// + public bool IsInMotion => _runtime.GetSnapshot().IsInMotion; + + /// + public void ConnectServer(string serverIp, int port) + { + if (string.IsNullOrWhiteSpace(serverIp)) + { + throw new ArgumentException("服务端 IP 不能为空。", nameof(serverIp)); + } + + if (port <= 0) + { + throw new ArgumentOutOfRangeException(nameof(port), "端口必须大于 0。"); + } + + lock (_stateLock) + { + // HTTP-only 阶段仍记录旧客户端期望的 50001 地址,便于后续 TCP 入口恢复时复用状态。 + _connectedServerIp = serverIp; + _connectedServerPort = port; + } + + _logger?.LogInformation("ConnectServer 完成: {ServerIp}:{Port}", serverIp, port); + } + + /// + public string GetServerVersion() + { + return ServerVersion; + } + + /// + public string GetClientVersion() + { + return "flyshot-replacement-controller-client-compat/0.1.0"; + } + + /// + public void SetUpRobot(string robotName) + { + _logger?.LogInformation("SetUpRobot 开始: robotName={RobotName}", robotName); + + var robotSettings = TryLoadRobotSettings() ?? CreateDefaultRobotSettings(); + var robotProfile = _robotCatalog.LoadProfile( + robotName, + robotSettings.AccLimitScale, + robotSettings.JerkLimitScale); + + lock (_stateLock) + { + // 机器人重新初始化时同步重置运行时和上传轨迹目录,保持旧服务初始化语义。 + _configuredRobotName = robotName; + _activeRobotProfile = robotProfile; + _uploadedTrajectories.Clear(); + _runtime.ResetRobot(robotProfile, robotName); + // RobotConfig.json 的 speed_ratio 是启动默认值;在线 set_speedRatio 仍可覆盖后续执行。 + _runtime.SetSpeedRatio(robotSettings.SpeedRatio); + _robotSettings = robotSettings; + + // 从持久化存储恢复该机器人名下之前已上传的轨迹。 + var savedTrajectories = _trajectoryStore.LoadAll(robotName, out _); + foreach (var saved in savedTrajectories) + { + _uploadedTrajectories[saved.Key] = saved.Value; + } + } + + _logger?.LogInformation( + "SetUpRobot 完成: robotName={RobotName}, dof={Dof}, accLimit={AccLimit}, jerkLimit={JerkLimit}, speedRatio={SpeedRatio}, 恢复轨迹数={TrajCount}", + robotName, + robotProfile.DegreesOfFreedom, + robotSettings.AccLimitScale, + robotSettings.JerkLimitScale, + robotSettings.SpeedRatio, + _uploadedTrajectories.Count); + } + + /// + public void SetUpRobotFromEnv(string envFile) + { + if (string.IsNullOrWhiteSpace(envFile)) + { + throw new ArgumentException("环境文件路径不能为空。", nameof(envFile)); + } + + throw new NotSupportedException("SetUpRobotFromEnv 尚未接入环境文件解析。"); + } + + /// + public void SetShowTcp(bool isShow, double axisLength, int axisSize) + { + if (axisLength <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(axisLength), "TCP 坐标轴长度必须大于 0。"); + } + + if (axisSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(axisSize), "TCP 坐标轴线宽必须大于 0。"); + } + + lock (_stateLock) + { + EnsureRobotSetup(); + + // 当前无 GUI 渲染层,先保存显示参数,保证旧 SDK 参数不会在 HTTP 边界丢失。 + _showTcp = isShow; + _showTcpAxisLength = axisLength; + _showTcpAxisSize = axisSize; + } + } + + /// + public void SetActiveController(bool sim) + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + _runtime.SetActiveController(sim); + } + + /// + public void Connect(string robotIp) + { + if (string.IsNullOrWhiteSpace(robotIp)) + { + throw new ArgumentException("控制器 IP 不能为空。", nameof(robotIp)); + } + + _logger?.LogInformation("Connect 开始: robotIp={RobotIp}", robotIp); + + lock (_stateLock) + { + EnsureRobotSetup(); + } + + _runtime.Connect(robotIp); + + _logger?.LogInformation("Connect 完成: robotIp={RobotIp}", robotIp); + } + + /// + public void Disconnect() + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + _runtime.Disconnect(); + } + + /// + public void EnableRobot(int bufferSize) + { + _logger?.LogInformation("EnableRobot 开始: bufferSize={BufferSize}", bufferSize); + lock (_stateLock) + { + EnsureRobotSetup(); + } + _runtime.EnableRobot(bufferSize); + _logger?.LogInformation("EnableRobot 完成"); + } + + /// + public void DisableRobot() + { + _logger?.LogInformation("DisableRobot 开始"); + lock (_stateLock) + { + EnsureRobotSetup(); + } + + // DisableRobot 是中断/控制命令,不能被长时间运动串行锁挡住。 + _runtime.DisableRobot(); + _logger?.LogInformation("DisableRobot 完成"); + } + + /// + public void StopMove() + { + _logger?.LogInformation("StopMove 开始"); + lock (_stateLock) + { + EnsureRobotSetup(); + } + + // StopMove 必须能在运动执行期间直接进入 runtime 取消发送任务。 + _runtime.StopMove(); + _logger?.LogInformation("StopMove 完成"); + } + + /// + public ControllerStateSnapshot GetControllerSnapshot() + { + return _runtime.GetSnapshot(); + } + + /// + public ControllerClientStatusSnapshotMetadata GetStatusSnapshotMetadata() + { + lock (_stateLock) + { + var isSetup = _activeRobotProfile is not null; + return new ControllerClientStatusSnapshotMetadata( + isSetup, + isSetup ? _configuredRobotName : null, + isSetup ? _activeRobotProfile!.DegreesOfFreedom : 0, + isSetup ? _uploadedTrajectories.Keys.ToArray() : Array.Empty(), + GetServerVersion(), + GetClientVersion()); + } + } + + /// + public double GetSpeedRatio() + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + return _runtime.GetSpeedRatio(); + } + + /// + public void SetSpeedRatio(double ratio) + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + _runtime.SetSpeedRatio(ratio); + } + + /// + public void SetIo(int port, bool value, string ioType) + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + _runtime.SetIo(port, value, ioType); + } + + /// + public bool GetIo(int port, string ioType) + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + return _runtime.GetIo(port, ioType); + } + + /// + public IReadOnlyList GetNearestIk(IReadOnlyList pose, IReadOnlyList seed) + { + ArgumentNullException.ThrowIfNull(pose); + ArgumentNullException.ThrowIfNull(seed); + + lock (_stateLock) + { + EnsureRobotSetup(); + if (pose.Count != 7) + { + throw new ArgumentException("位姿必须是 [x,y,z,qx,qy,qz,qw] 七元数组。", nameof(pose)); + } + + if (seed.Count != GetDegreesOfFreedom()) + { + throw new ArgumentException("seed 关节数量必须与机器人自由度一致。", nameof(seed)); + } + + throw new NotSupportedException("GetNearestIK 尚未接入逆解求解器。"); + } + } + + /// + public void SetTcp(double x, double y, double z) + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + _runtime.SetTcp(x, y, z); + } + + /// + public IReadOnlyList GetTcp() + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + return _runtime.GetTcp(); + } + + /// + public IReadOnlyList GetJointPositions() + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + return _runtime.GetJointPositions(); + } + + /// + public void MoveJoint(IReadOnlyList jointPositions) + { + ArgumentNullException.ThrowIfNull(jointPositions); + + _logger?.LogInformation("MoveJoint 开始: 目标关节数={JointCount}", jointPositions.Count); + _logger?.LogDebug("MoveJoint 目标关节: {Joints}", string.Join(", ", jointPositions.Select(j => j.ToString("F4")))); + + RobotProfile robot; + lock (_stateLock) + { + robot = RequireActiveRobot(); + EnsureRuntimeEnabled(); + } + + lock (_motionLock) + { + ExecuteMoveJointAndWait(robot, jointPositions, "MoveJoint"); + } + + _logger?.LogInformation("MoveJoint 完成"); + } + + /// + public void MovePose(IReadOnlyList pose) + { + ArgumentNullException.ThrowIfNull(pose); + + _logger?.LogInformation("MovePose 开始: 目标位姿维度={PoseCount}", pose.Count); + _logger?.LogDebug("MovePose 目标位姿: {Pose}", string.Join(", ", pose.Select(v => v.ToString("F4")))); + + RobotProfile robot; + lock (_stateLock) + { + robot = RequireActiveRobot(); + EnsureRuntimeEnabled(); + } + + lock (_motionLock) + { + ExecuteMovePoseAndWait(robot, pose, "MovePose"); + } + + _logger?.LogInformation("MovePose 完成"); + } + + /// + public void ExecuteTrajectory(IReadOnlyList> waypoints, TrajectoryExecutionOptions? options = null) + { + ArgumentNullException.ThrowIfNull(waypoints); + options ??= new TrajectoryExecutionOptions(); + if (waypoints.Count == 0) + { + throw new ArgumentException("轨迹路点不能为空。", nameof(waypoints)); + } + + _logger?.LogInformation("ExecuteTrajectory 开始: 路点数={WaypointCount}, method={Method}, saveTraj={SaveTraj}", + waypoints.Count, options.Method, options.SaveTrajectory); + _logger?.LogDebug("ExecuteTrajectory 路点详情: {Waypoints}", + string.Join(" | ", waypoints.Select(wp => $"[{string.Join(", ", wp.Select(j => j.ToString("F4")))}]"))); + + RobotProfile robot; + CompatibilityRobotSettings settings; + lock (_stateLock) + { + robot = RequireActiveRobot(); + EnsureRuntimeEnabled(); + settings = RequireRobotSettings(); + } + + lock (_motionLock) + { + // 普通轨迹必须按调用方指定 method 规划,再把规划结果交给运行时执行。 + var planningSpeedScale = settings.PlanningSpeedScale; + var speedRatio = _runtime.GetSnapshot().SpeedRatio; + var bundle = _trajectoryOrchestrator.PlanOrdinaryTrajectory(robot, waypoints, options, planningSpeedScale, speedRatio); + _logger?.LogInformation( + "ExecuteTrajectory 规划完成: method={Method}, 时长={Duration}s, 有效={IsValid}, 采样点数={SampleCount}, planningSpeedScale={PlanningSpeedScale}, speedRatio={SpeedRatio}", + bundle.Result.Method, + bundle.Result.Duration.TotalSeconds, + bundle.Result.IsValid, + bundle.Result.DenseJointTrajectory?.Count ?? 0, + planningSpeedScale, + speedRatio); + var finalJointPositions = bundle.PlannedTrajectory.PlannedWaypoints[^1].Positions; + _runtime.ExecuteTrajectory(bundle.Result, finalJointPositions); + } + + _logger?.LogInformation("ExecuteTrajectory 完成"); + } + + /// + public IReadOnlyList GetPose() + { + lock (_stateLock) + { + EnsureRobotSetup(); + } + + return _runtime.GetPose(); + } + + /// + public void UploadTrajectory(ControllerClientCompatUploadedTrajectory trajectory) + { + ArgumentNullException.ThrowIfNull(trajectory); + + _logger?.LogInformation( + "UploadTrajectory 开始: name={Name}, waypoints={WaypointCount}, shotFlags={ShotCount}", + trajectory.Name, + trajectory.Waypoints.Count, + trajectory.ShotFlags.Count(static f => f)); + + string robotName; + CompatibilityRobotSettings settings; + lock (_stateLock) + { + EnsureRuntimeEnabled(); + _uploadedTrajectories[trajectory.Name] = trajectory; + + robotName = _configuredRobotName ?? throw new InvalidOperationException("Robot has not been setup."); + settings = _robotSettings ?? CreateDefaultRobotSettings(); + } + + // RobotConfig.json 持久化是文件 I/O,放在状态锁外,避免状态页轮询被磁盘写入拖住。 + _trajectoryStore.Save(robotName, settings, trajectory); + + _logger?.LogInformation("UploadTrajectory 完成: name={Name}", trajectory.Name); + } + + /// + public IReadOnlyList ListTrajectoryNames() + { + lock (_stateLock) + { + return _uploadedTrajectories.Keys.ToArray(); + } + } + + /// + public void ExecuteTrajectoryByName(string name, FlyshotExecutionOptions? options = null) + { + options ??= new FlyshotExecutionOptions(); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("轨迹名称不能为空。", nameof(name)); + } + + _logger?.LogInformation( + "ExecuteTrajectoryByName 开始: name={Name}, method={Method}, moveToStart={MoveToStart}, useCache={UseCache}, wait={Wait}", + name, options.Method, options.MoveToStart, options.UseCache, options.Wait); + + RobotProfile robot; + CompatibilityRobotSettings settings; + ControllerClientCompatUploadedTrajectory trajectory; + lock (_stateLock) + { + robot = RequireActiveRobot(); + EnsureRuntimeEnabled(); + + if (!_uploadedTrajectories.TryGetValue(name, out var uploadedTrajectory)) + { + _logger?.LogWarning("ExecuteTrajectoryByName 失败: 轨迹不存在 name={Name}", name); + throw new InvalidOperationException("FlyShot trajectory does not exist."); + } + + // 飞拍执行只拿上传轨迹的瞬时副本,后续规划/导出都不再依赖字典锁。 + trajectory = CloneUploadedTrajectory(uploadedTrajectory); + if (trajectory.Waypoints.Count == 0) + { + _logger?.LogWarning("ExecuteTrajectoryByName 失败: 轨迹无路点 name={Name}", name); + throw new InvalidOperationException("FlyShot trajectory contains no waypoints."); + } + + settings = RequireRobotSettings(); + } + + lock (_motionLock) + { + // 已上传飞拍轨迹必须按调用方指定 method 生成 shot timeline 后再交给运行时。 + var speedRatio = _runtime.GetSnapshot().SpeedRatio; + var bundle = _trajectoryOrchestrator.PlanUploadedFlyshot(robot, trajectory, options, settings, settings.PlanningSpeedScale, speedRatio); + bundle = PrepareFlyshotExecutionBundle(robot, bundle, speedRatio); + ExportFlyshotArtifactsIfRequested( + name, + options.SaveTrajectory, + robot, + trajectory, + options, + settings, + bundle, + settings.PlanningSpeedScale, + speedRatio); + _logger?.LogInformation( + "ExecuteTrajectoryByName 规划完成: name={Name}, method={Method}, 时长={Duration}s, 触发事件数={TriggerCount}, 使用缓存={UsedCache}, planningSpeedScale={PlanningSpeedScale}, speedRatio={SpeedRatio}", + name, + bundle.Result.Method, + bundle.Result.Duration.TotalSeconds, + bundle.Result.TriggerTimeline.Count, + bundle.Result.UsedCache, + settings.PlanningSpeedScale, + speedRatio); + + if (options.MoveToStart) + { + _logger?.LogInformation("ExecuteTrajectoryByName 先移动到起点"); + ExecuteMoveJointAndWait(robot, bundle.PlannedTrajectory.PlannedWaypoints[0].Positions, "ExecuteTrajectoryByName.move_to_start"); + EnsureFeedbackNearFlyshotStart(bundle.PlannedTrajectory.PlannedWaypoints[0].Positions, name); + } + else + { + // 正式飞拍前必须确认机器人反馈已经在轨迹起点附近,避免 J519 目标突变。 + if (!IsFeedbackNearFlyshotStart(bundle.PlannedTrajectory.PlannedWaypoints[0].Positions, name)) + { + return; + } + } + + var finalJointPositions = bundle.PlannedTrajectory.PlannedWaypoints[^1].Positions; + _runtime.ExecuteTrajectory(bundle.Result, finalJointPositions); + if (options.Wait) + { + var executionDuration = bundle.PreparedExecution is null + ? bundle.Result.Duration + : TimeSpan.FromSeconds(bundle.PreparedExecution.FinalDurationSeconds); + WaitForRuntimeMotionComplete("ExecuteTrajectoryByName.flyshot", executionDuration); + } + } + + _logger?.LogInformation("ExecuteTrajectoryByName 完成: name={Name}", name); + } + + /// + /// 从当前关节位置生成临时 PTP 稠密轨迹并阻塞等待运行时完成,避免后续 J519 目标发生突变。 + /// + /// 当前机器人模型。 + /// 目标关节位置,单位为弧度。 + /// 用于日志和超时异常的操作名。 + private void ExecuteMoveJointAndWait(RobotProfile robot, IReadOnlyList targetJointPositions, string operationName) + { + var currentJointPositions = _runtime.GetJointPositions(); + EnsureJointVector(currentJointPositions, robot.DegreesOfFreedom, nameof(currentJointPositions)); + EnsureJointVector(targetJointPositions, robot.DegreesOfFreedom, nameof(targetJointPositions)); + + var speedRatio = _runtime.GetSnapshot().SpeedRatio; + var moveResult = MoveJointTrajectoryGenerator.CreateResult(robot, currentJointPositions, targetJointPositions, speedRatio, _logger); + _logger?.LogInformation( + "{OperationName} PTP规划完成: 当前速度倍率={SpeedRatio}, 规划时长={Duration}s, 采样点数={SampleCount}", + operationName, + speedRatio, + moveResult.Duration.TotalSeconds, + moveResult.DenseJointTrajectory?.Count ?? 0); + + _runtime.ExecuteTrajectory(moveResult, targetJointPositions); + WaitForRuntimeMotionComplete(operationName, moveResult.Duration); + } + + /// + /// 从当前 TCP 位姿生成临时直角 PTP 稠密轨迹并阻塞等待运行时完成。 + /// + /// 当前机器人模型,用于取得 J519 伺服周期。 + /// 目标位姿 [x,y,z,w,p,r],单位为 mm/deg。 + /// 用于日志和超时异常的操作名。 + private void ExecuteMovePoseAndWait(RobotProfile robot, IReadOnlyList targetPose, string operationName) + { + var currentPose = NormalizeRuntimePose(_runtime.GetPose()); + EnsurePoseVector(targetPose, nameof(targetPose)); + + var speedRatio = _runtime.GetSnapshot().SpeedRatio; + var moveResult = MovePoseTrajectoryGenerator.CreateResult(currentPose, targetPose, robot.ServoPeriod, speedRatio, _logger); + _logger?.LogInformation( + "{OperationName} 直角PTP规划完成: 当前速度倍率={SpeedRatio}, 规划时长={Duration}s, 采样点数={SampleCount}", + operationName, + speedRatio, + moveResult.Duration.TotalSeconds, + moveResult.DenseCartesianTrajectory?.Count ?? 0); + + _runtime.ExecuteCartesianTrajectory(moveResult, targetPose); + WaitForRuntimeMotionComplete(operationName, moveResult.Duration); + } + + /// + /// 校验当前反馈是否接近飞拍起点;不接近时直接抛出兼容错误。 + /// + private void EnsureFeedbackNearFlyshotStart(IReadOnlyList targetJointPositions, string name) + { + if (!IsFeedbackNearFlyshotStart(targetJointPositions, name)) + { + throw new InvalidOperationException("Robot feedback is not near flyshot start."); + } + } + + /// + /// 检查当前机械臂关节反馈与计划轨迹第一个点之间的差异。 + /// + private bool IsFeedbackNearFlyshotStart(IReadOnlyList targetJointPositions, string name) + { + var currentJointPositions = _runtime.GetJointPositions(); + var diff = currentJointPositions.Zip(targetJointPositions, (current, target) => Math.Abs(current - target)).Sum(); + if (diff <= 0.01) + { + return true; + } + + _logger?.LogWarning("ExecuteTrajectoryByName 当前关节坐标与计划轨迹的第一个点之间的差异过大 name={Name}, diff={Diff}", name, diff); + return false; + } + + /// + /// 等待运行时报告当前运动结束,用于把 move_to_start 与正式飞拍轨迹串行化。 + /// + /// 用于日志和超时异常的操作名。 + /// 规划运动时长。 + private void WaitForRuntimeMotionComplete(string operationName, TimeSpan plannedDuration) + { + var timeout = ResolveMotionCompletionTimeout(plannedDuration); + var deadline = DateTimeOffset.UtcNow.Add(timeout); + + while (true) + { + if (!_runtime.GetSnapshot().IsInMotion) + { + _logger?.LogInformation("{OperationName} 运动完成", operationName); + return; + } + + if (DateTimeOffset.UtcNow >= deadline) + { + throw new TimeoutException($"{operationName} 等待运动完成超时,planned={plannedDuration.TotalSeconds:F3}s, timeout={timeout.TotalSeconds:F3}s。"); + } + + Thread.Sleep(TimeSpan.FromMilliseconds(10)); + } + } + + /// + /// 根据规划时长推导等待超时,给真机通信和状态更新留出余量。 + /// + /// 规划运动时长。 + /// 等待运行时完成的最大时长。 + private static TimeSpan ResolveMotionCompletionTimeout(TimeSpan plannedDuration) + { + var timeoutSeconds = Math.Max(5.0, plannedDuration.TotalSeconds * 3.0 + 2.0); + return TimeSpan.FromSeconds(timeoutSeconds); + } + + /// + public void SaveTrajectoryInfo(string name, string method = "icsp") + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("轨迹名称不能为空。", nameof(name)); + } + + _logger?.LogInformation("SaveTrajectoryInfo 开始: name={Name}, method={Method}", name, method); + + RobotProfile robot; + CompatibilityRobotSettings planningSettings; + ControllerClientCompatUploadedTrajectory trajectory; + lock (_stateLock) + { + robot = RequireActiveRobot(); + if (!_uploadedTrajectories.TryGetValue(name, out var uploadedTrajectory)) + { + _logger?.LogWarning("SaveTrajectoryInfo 失败: 轨迹不存在 name={Name}", name); + throw new InvalidOperationException("FlyShot trajectory does not exist."); + } + + // 先通过规划校验避免静默接受非法参数,同时把轨迹信息强制刷写到本地 JSON。 + // 保存轨迹信息会执行规划和文件导出,先复制上传数据再释放状态锁。 + trajectory = CloneUploadedTrajectory(uploadedTrajectory); + planningSettings = RequireRobotSettings(); + } + + var speedRatio = _runtime.GetSnapshot().SpeedRatio; + var bundle = _trajectoryOrchestrator.PlanUploadedFlyshot( + robot, + trajectory, + new FlyshotExecutionOptions(useCache: false, saveTrajectory: true, method: method), + planningSettings, + planningSettings.PlanningSpeedScale, + speedRatio); + bundle = PrepareFlyshotExecutionBundle(robot, bundle, speedRatio); + _logger?.LogInformation("SaveTrajectoryInfo 规划完成记录到本地"); + ExportFlyshotArtifactsIfRequested( + name, + saveTrajectory: true, + robot, + trajectory, + new FlyshotExecutionOptions(useCache: false, saveTrajectory: true, method: method), + planningSettings, + bundle, + planningSettings.PlanningSpeedScale, + speedRatio); + + _logger?.LogInformation("SaveTrajectoryInfo 完成: name={Name}", name); + } + + /// + public bool IsFlyshotTrajectoryValid(out TimeSpan duration, string name, string method = "icsp", bool saveTrajectory = false) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("轨迹名称不能为空。", nameof(name)); + } + + _logger?.LogInformation("IsFlyshotTrajectoryValid 开始: name={Name}, method={Method}", name, method); + + RobotProfile robot; + CompatibilityRobotSettings planningSettings; + ControllerClientCompatUploadedTrajectory trajectory; + lock (_stateLock) + { + robot = RequireActiveRobot(); + if (!_uploadedTrajectories.TryGetValue(name, out var uploadedTrajectory)) + { + _logger?.LogWarning("IsFlyshotTrajectoryValid 失败: 轨迹不存在 name={Name}", name); + throw new InvalidOperationException("FlyShot trajectory does not exist."); + } + + // 有效性检查只消费当前快照,不要求和后续上传/删除形成长事务。 + trajectory = CloneUploadedTrajectory(uploadedTrajectory); + planningSettings = RequireRobotSettings(); + } + + var speedRatio = _runtime.GetSnapshot().SpeedRatio; + var bundle = _trajectoryOrchestrator.PlanUploadedFlyshot( + robot, + trajectory, + new FlyshotExecutionOptions(method: method, saveTrajectory: saveTrajectory), + planningSettings, + planningSettings.PlanningSpeedScale, + speedRatio); + bundle = PrepareFlyshotExecutionBundle(robot, bundle, speedRatio); + ExportFlyshotArtifactsIfRequested( + name, + saveTrajectory, + robot, + trajectory, + new FlyshotExecutionOptions(method: method, saveTrajectory: saveTrajectory), + planningSettings, + bundle, + planningSettings.PlanningSpeedScale, + speedRatio); + + duration = bundle.Result.Duration; + _logger?.LogInformation( + "IsFlyshotTrajectoryValid 结果: name={Name}, valid={Valid}, duration={Duration}s", + name, bundle.Result.IsValid, duration.TotalSeconds); + return bundle.Result.IsValid; + } + + /// + public void DeleteTrajectory(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("轨迹名称不能为空。", nameof(name)); + } + + _logger?.LogInformation("DeleteTrajectory 开始: name={Name}", name); + + string robotName; + lock (_stateLock) + { + if (!_uploadedTrajectories.Remove(name)) + { + _logger?.LogWarning("DeleteTrajectory 失败: 轨迹不存在 name={Name}", name); + throw new InvalidOperationException("DeleteFlyShotTraj failed"); + } + + robotName = _configuredRobotName ?? throw new InvalidOperationException("Robot has not been setup."); + } + + // 删除持久化文件不占用状态锁,状态页只需要看到内存字典的即时快照。 + _trajectoryStore.Delete(robotName, name); + + _logger?.LogInformation("DeleteTrajectory 完成: name={Name}", name); + } + + /// + public string GetRobotName() + { + lock (_stateLock) + { + return _configuredRobotName ?? throw new InvalidOperationException("Robot has not been setup."); + } + } + + /// + public int GetDegreesOfFreedom() + { + lock (_stateLock) + { + return _activeRobotProfile?.DegreesOfFreedom ?? throw new InvalidOperationException("Robot has not been setup."); + } + } + + /// + /// 获取当前机器人配置,未初始化时抛出兼容错误。 + /// + /// 当前机器人配置。 + private RobotProfile RequireActiveRobot() + { + return _activeRobotProfile ?? throw new InvalidOperationException("Robot has not been setup."); + } + + /// + /// 获取当前机器人兼容配置;未加载旧配置时回退到现场默认值。 + /// + /// 当前机器人配置。 + private CompatibilityRobotSettings RequireRobotSettings() + { + return _robotSettings ?? CreateDefaultRobotSettings(); + } + + /// + /// 复制一份上传轨迹快照,避免锁外规划期间观察到可变集合引用。 + /// + /// 待复制的上传轨迹。 + /// 上传轨迹副本。 + private static ControllerClientCompatUploadedTrajectory CloneUploadedTrajectory(ControllerClientCompatUploadedTrajectory trajectory) + { + return new ControllerClientCompatUploadedTrajectory( + trajectory.Name, + trajectory.Waypoints, + trajectory.ShotFlags, + trajectory.OffsetValues, + trajectory.AddressGroups); + } + + /// + /// 校验机器人已经完成初始化。 + /// + private void EnsureRobotSetup() + { + _ = RequireActiveRobot(); + } + + /// + /// 校验运行时已经处于可执行状态。 + /// + private void EnsureRuntimeEnabled() + { + EnsureRobotSetup(); + if (!_runtime.GetSnapshot().IsEnabled) + { + throw new InvalidOperationException("Robot has not been enabled."); + } + } + + /// + /// 校验关节向量与当前机器人自由度一致,且所有值都是有限数值。 + /// + /// 待校验关节向量,单位为弧度。 + /// 期望自由度。 + /// 调用方参数名。 + private static void EnsureJointVector(IReadOnlyList joints, int expectedCount, string paramName) + { + if (joints.Count != expectedCount) + { + throw new ArgumentException($"关节数量必须为 {expectedCount}。", paramName); + } + + for (var index = 0; index < joints.Count; index++) + { + var value = joints[index]; + if (double.IsNaN(value) || double.IsInfinity(value)) + { + throw new ArgumentOutOfRangeException(paramName, $"第 {index} 个关节值必须是有限数值。"); + } + } + } + + /// + /// 校验直角位姿为 [x,y,z,w,p,r] 六维有限数。 + /// + /// 待校验位姿,单位为 mm/deg。 + /// 调用方参数名。 + private static void EnsurePoseVector(IReadOnlyList pose, string paramName) + { + if (pose.Count != 6) + { + throw new ArgumentException("位姿必须为 [x,y,z,w,p,r] 六维数组。", paramName); + } + + for (var index = 0; index < pose.Count; index++) + { + var value = pose[index]; + if (double.IsNaN(value) || double.IsInfinity(value)) + { + throw new ArgumentOutOfRangeException(paramName, $"第 {index} 个位姿值必须是有限数值。"); + } + } + } + + /// + /// 将运行时位姿快照归一化为 J519 直角命令需要的 [x,y,z,w,p,r] 六维。 + /// + /// 运行时返回的位姿数组。 + /// 六维直角位姿。 + private static IReadOnlyList NormalizeRuntimePose(IReadOnlyList pose) + { + if (pose.Count < 6) + { + throw new InvalidOperationException("Runtime pose must contain at least [x,y,z,w,p,r]."); + } + + var normalized = pose.Take(6).ToArray(); + EnsurePoseVector(normalized, nameof(pose)); + return normalized; + } + + /// + /// 根据 saveTrajectory 参数把规划结果点位写入运行目录 Config/Data/name。 + /// + /// 飞拍轨迹名称。 + /// 是否导出规划结果点位。 + /// 当前机器人模型。 + /// 已上传的飞拍轨迹。 + /// 本次规划选项。 + /// 当前机器人兼容设置。 + /// 运行时和 ActualSend 诊断使用的规划结果包。 + /// 规划速度倍率。 + /// 本次运行时速度倍率。 + private void ExportFlyshotArtifactsIfRequested( + string name, + bool saveTrajectory, + RobotProfile robot, + ControllerClientCompatUploadedTrajectory uploaded, + FlyshotExecutionOptions options, + CompatibilityRobotSettings settings, + PlannedExecutionBundle executionBundle, + double planningSpeedScale, + double speedRatio) + { + if (!saveTrajectory) + { + return; + } + + // 旧格式 Joint/Cart 导出必须只反映 planning_speed_scale;ActualSend* 才反映当前运行时 speedRatio。 + var exportPlanningBundle = _trajectoryOrchestrator.PlanUploadedFlyshot( + robot, + uploaded, + new FlyshotExecutionOptions( + moveToStart: options.MoveToStart, + useCache: false, + saveTrajectory: options.SaveTrajectory, + method: options.Method, + wait: options.Wait), + settings, + planningSpeedScale, + speedRatio: 1.0); + _artifactWriter.WriteUploadedFlyshot(name, robot, exportPlanningBundle, executionBundle, speedRatio); + } + + /// + /// 为飞拍链路预先构建最终发送队列,确保运行时只消费已经离散校验通过的 8ms 点列。 + /// + private static PlannedExecutionBundle PrepareFlyshotExecutionBundle( + RobotProfile robot, + PlannedExecutionBundle bundle, + double speedRatio) + { + var preparedExecution = FlyshotExecutionSendSequenceBuilder.Build( + robot, + bundle.ExecutionTrajectory, + bundle.Result, + robot.ServoPeriod.TotalSeconds, + requestedSpeedRatio: speedRatio, + samplingSpeedRatio: 1.0); + var preparedResult = new TrajectoryResult( + programName: bundle.Result.ProgramName, + method: bundle.Result.Method, + isValid: bundle.Result.IsValid, + duration: bundle.Result.Duration, + shotEvents: bundle.Result.ShotEvents, + triggerTimeline: bundle.Result.TriggerTimeline, + artifacts: bundle.Result.Artifacts, + failureReason: bundle.Result.FailureReason, + usedCache: bundle.Result.UsedCache, + originalWaypointCount: bundle.Result.OriginalWaypointCount, + plannedWaypointCount: bundle.Result.PlannedWaypointCount, + triggerSampleIndexOffsetCycles: bundle.Result.TriggerSampleIndexOffsetCycles, + denseJointTrajectory: bundle.Result.DenseJointTrajectory, + preparedFlyshotExecution: preparedExecution); + return new PlannedExecutionBundle( + bundle.PlannedTrajectory, + bundle.ShotTimeline, + preparedResult, + preparedExecution, + bundle.ExecutionTrajectory); + } + + /// + /// 尝试从配置根目录加载 RobotConfig.json 获取机器人配置;失败时返回 null。 + /// + /// 加载到的机器人配置,或 null。 + private CompatibilityRobotSettings? TryLoadRobotSettings() + { + foreach (var root in EnumerateRobotConfigRoots()) + { + try + { + // 运行配置根本身已经是 Config 目录,这里用绝对路径避免再次追加 Config。 + var configPath = Path.Combine(root, "RobotConfig.json"); + var loaded = _configLoader.Load(configPath, root); + return loaded.Robot; + } + catch + { + // 单个候选根目录加载失败时继续尝试下一个兼容入口。 + } + } + + return null; + } + + /// + /// 枚举 RobotConfig.json 的配置根目录,运行目录 Config 优先,旧父工作区仅在显式配置时参与。 + /// + /// 待尝试的配置根目录列表。 + private IEnumerable EnumerateRobotConfigRoots() + { + yield return _options.ResolveConfigRoot(); + + var legacyWorkspaceRoot = _options.ResolveLegacyWorkspaceRoot(); + if (legacyWorkspaceRoot is not null) + { + yield return legacyWorkspaceRoot; + } + } + + /// + /// 构造与旧现场默认行为一致的机器人兼容配置。 + /// + /// 默认机器人配置。 + private static CompatibilityRobotSettings CreateDefaultRobotSettings() + { + return new CompatibilityRobotSettings( + useDo: false, + ioAddresses: Array.Empty(), + ioKeepCycles: 2, + triggerSampleIndexOffsetCycles: 7, + accLimitScale: 1.0, + jerkLimitScale: 1.0, + adaptIcspTryNum: 5); + } + +} diff --git a/src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs b/src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs new file mode 100644 index 0000000..fc09530 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs @@ -0,0 +1,42 @@ +using Flyshot.Core.Config; +using Flyshot.Runtime.Common; +using Flyshot.Runtime.Fanuc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Flyshot.ControllerClientCompat; + +/// +/// 提供 ControllerClient HTTP 兼容层的依赖注入注册入口。 +/// +public static class ControllerClientCompatServiceCollectionExtensions +{ + /// + /// 将 HTTP-only 的 ControllerClient 兼容服务注册到当前宿主。 + /// + /// 当前宿主服务集合。 + /// 宿主配置根。 + /// 同一服务集合,便于链式调用。 + public static IServiceCollection AddControllerClientCompat(this IServiceCollection services, IConfiguration configuration) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(configuration); + + services + .AddOptions() + .Bind(configuration.GetSection("ControllerClientCompat")); + + services.AddSingleton(static serviceProvider => serviceProvider.GetRequiredService>().Value); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } +} diff --git a/src/Flyshot.ControllerClientCompat/ControllerClientCompatUploadedTrajectory.cs b/src/Flyshot.ControllerClientCompat/ControllerClientCompatUploadedTrajectory.cs new file mode 100644 index 0000000..96783b9 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/ControllerClientCompatUploadedTrajectory.cs @@ -0,0 +1,64 @@ +namespace Flyshot.ControllerClientCompat; + +/// +/// 保存一条已上传到兼容层内存目录中的飞拍轨迹,供 HTTP API 层列出、执行和删除。 +/// +public sealed class ControllerClientCompatUploadedTrajectory +{ + /// + /// 初始化一条飞拍轨迹快照,并立即复制所有数组,避免调用方后续原地修改。 + /// + /// 轨迹名称。 + /// 关节路点集合。 + /// 拍摄标志集合。 + /// 偏移周期集合。 + /// 地址组集合。 + public ControllerClientCompatUploadedTrajectory( + string name, + IEnumerable> waypoints, + IEnumerable shotFlags, + IEnumerable offsetValues, + IEnumerable> addressGroups) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("轨迹名称不能为空。", nameof(name)); + } + + ArgumentNullException.ThrowIfNull(waypoints); + ArgumentNullException.ThrowIfNull(shotFlags); + ArgumentNullException.ThrowIfNull(offsetValues); + ArgumentNullException.ThrowIfNull(addressGroups); + + Name = name; + Waypoints = waypoints.Select(static waypoint => waypoint.ToArray()).ToArray(); + ShotFlags = shotFlags.ToArray(); + OffsetValues = offsetValues.ToArray(); + AddressGroups = addressGroups.Select(static group => group.ToArray()).ToArray(); + } + + /// + /// 获取轨迹名称。 + /// + public string Name { get; } + + /// + /// 获取已复制的关节路点集合。 + /// + public IReadOnlyList Waypoints { get; } + + /// + /// 获取拍摄标志集合。 + /// + public IReadOnlyList ShotFlags { get; } + + /// + /// 获取偏移周期集合。 + /// + public IReadOnlyList OffsetValues { get; } + + /// + /// 获取地址组集合。 + /// + public IReadOnlyList AddressGroups { get; } +} diff --git a/src/Flyshot.ControllerClientCompat/ControllerClientStatusSnapshotMetadata.cs b/src/Flyshot.ControllerClientCompat/ControllerClientStatusSnapshotMetadata.cs new file mode 100644 index 0000000..09d86d1 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/ControllerClientStatusSnapshotMetadata.cs @@ -0,0 +1,62 @@ +namespace Flyshot.ControllerClientCompat; + +/// +/// 保存状态页需要的 ControllerClient 兼容层元数据快照,避免状态页连续读取多个受保护字段。 +/// +public sealed record ControllerClientStatusSnapshotMetadata +{ + /// + /// 初始化一份兼容层状态元数据快照。 + /// + /// 当前是否已经完成机器人初始化。 + /// 当前机器人名称;未初始化时为空。 + /// 当前机器人自由度;未初始化时为 0。 + /// 当前已上传轨迹名称快照。 + /// 兼容服务端版本。 + /// 兼容客户端版本。 + public ControllerClientStatusSnapshotMetadata( + bool isSetup, + string? robotName, + int degreesOfFreedom, + IReadOnlyList uploadedTrajectories, + string serverVersion, + string clientVersion) + { + IsSetup = isSetup; + RobotName = robotName; + DegreesOfFreedom = degreesOfFreedom; + UploadedTrajectories = uploadedTrajectories.ToArray(); + ServerVersion = serverVersion; + ClientVersion = clientVersion; + } + + /// + /// 获取当前是否已经完成机器人初始化。 + /// + public bool IsSetup { get; } + + /// + /// 获取当前机器人名称;未初始化时为空。 + /// + public string? RobotName { get; } + + /// + /// 获取当前机器人自由度;未初始化时为 0。 + /// + public int DegreesOfFreedom { get; } + + /// + /// 获取已上传轨迹名称数组快照。 + /// + public IReadOnlyList UploadedTrajectories { get; } + + /// + /// 获取兼容服务端版本。 + /// + public string ServerVersion { get; } + + /// + /// 获取兼容客户端版本。 + /// + public string ClientVersion { get; } +} diff --git a/src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs b/src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs new file mode 100644 index 0000000..a4d9b56 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs @@ -0,0 +1,639 @@ +using Flyshot.Core.Config; +using Flyshot.Core.Domain; +using Flyshot.Core.Planning; +using Flyshot.Core.Planning.Sampling; +using Flyshot.Core.Triggering; +using Microsoft.Extensions.Logging; + +namespace Flyshot.ControllerClientCompat; + +/// +/// 负责把 ControllerClient 兼容层的轨迹输入转换为规划结果和触发时间轴。 +/// +public sealed class ControllerClientTrajectoryOrchestrator +{ + /// + /// 稠密轨迹离散限幅失败后允许统一拉长时间轴的最大次数。 + /// + private const int MaxDenseLimitStretchIterations = 100; + + /// + /// 每次离散限幅失败后统一放大的时间倍率。 + /// + private const double DenseLimitStretchFactor = 1.01; + + /// + /// 平滑起停重定时混合系数:0 表示不平滑,1 表示完全使用平滑时间律。 + /// 当前取值用于“弱平滑”,仅轻微拉伸首尾时间段,避免起停过慢。 + /// + private const double SmoothStartStopBlend = 0.60; + + private readonly ICspPlanner _icspPlanner; + private readonly SelfAdaptIcspPlanner _selfAdaptIcspPlanner; + private readonly ShotTimelineBuilder _shotTimelineBuilder = new(new WaypointTimestampResolver()); + private readonly Dictionary _flyshotCache = new(StringComparer.Ordinal); + private readonly ILogger? _logger; + + /// + /// 初始化轨迹编排器。 + /// + /// 日志记录器;允许 null。 + /// 日志工厂;允许 null。 + public ControllerClientTrajectoryOrchestrator( + ILogger? logger = null, + ILoggerFactory? loggerFactory = null) + { + _logger = logger; + _icspPlanner = new(logger: loggerFactory?.CreateLogger()); + _selfAdaptIcspPlanner = new(logger: loggerFactory?.CreateLogger()); + } + + /// + /// 对普通轨迹执行 ICSP 规划。 + /// + /// 当前机器人配置。 + /// 普通轨迹关节路点。 + /// 执行参数。 + /// 规划速度倍率。 + /// 本次执行速度倍率;普通轨迹在规划阶段折算进有效限速倍率。 + /// 包含规划轨迹、空触发时间轴和执行结果的结果包。 + public PlannedExecutionBundle PlanOrdinaryTrajectory( + RobotProfile robot, + IReadOnlyList> waypoints, + TrajectoryExecutionOptions? options = null, + double planningSpeedScale = 1.0, + double speedRatio = 1.0) + { + ArgumentNullException.ThrowIfNull(robot); + ArgumentNullException.ThrowIfNull(waypoints); + options ??= new TrajectoryExecutionOptions(); + var effectivePlanningSpeedScale = ResolveEffectivePlanningSpeedScale(planningSpeedScale, speedRatio, "普通轨迹"); + var planningRobot = ApplyPlanningSpeedScale(robot, effectivePlanningSpeedScale); + + _logger?.LogInformation( + "PlanOrdinaryTrajectory 开始: 路点数={WaypointCount}, method={Method}, planningSpeedScale={PlanningSpeedScale}, speedRatio={SpeedRatio}, effectivePlanningSpeedScale={EffectivePlanningSpeedScale}", + waypoints.Count, options.Method, planningSpeedScale, speedRatio, effectivePlanningSpeedScale); + + var program = CreateProgram( + name: "ordinary-trajectory", + waypoints: waypoints, + shotFlags: Enumerable.Repeat(false, waypoints.Count), + offsetValues: Enumerable.Repeat(0, waypoints.Count), + addressGroups: Enumerable.Range(0, waypoints.Count).Select(static _ => Array.Empty())); + + var method = ParseOrdinaryMethod(options.Method); + var request = new TrajectoryRequest( + robot: planningRobot, + program: program, + method: method, + saveTrajectoryArtifacts: options.SaveTrajectory); + + var plannedTrajectory = PlanByMethod(request, method); + var executionTrajectory = plannedTrajectory; + var denseJointTrajectory = CreateLimitCompliantDenseTrajectory(ref executionTrajectory, shapeTrajectoryEdges: false); + var shotTimeline = new ShotTimeline(Array.Empty(), Array.Empty()); + var result = CreateResult( + executionTrajectory, + shotTimeline, + denseJointTrajectory, + usedCache: false, + triggerSampleIndexOffsetCycles: 0); + + _logger?.LogInformation( + "PlanOrdinaryTrajectory 完成: 时长={Duration}s, 采样点数={SampleCount}", + result.Duration.TotalSeconds, + result.DenseJointTrajectory?.Count ?? 0); + + return new PlannedExecutionBundle(plannedTrajectory, shotTimeline, result, executionTrajectory: executionTrajectory); + } + + /// + /// 对已经上传的飞拍轨迹执行自适应 ICSP 规划并生成触发时间轴。 + /// + /// 当前机器人配置。 + /// 兼容层保存的上传轨迹。 + /// 执行参数。 + /// 兼容层机器人设置。 + /// 规划速度倍率。 + /// 本次飞拍使用的速度倍率;为空时使用配置默认值。 + /// 包含规划轨迹、触发时间轴和执行结果的结果包。 + public PlannedExecutionBundle PlanUploadedFlyshot( + RobotProfile robot, + ControllerClientCompatUploadedTrajectory uploaded, + FlyshotExecutionOptions? options = null, + CompatibilityRobotSettings? settings = null, + double? planningSpeedScale = null, + double? speedRatio = null) + { + ArgumentNullException.ThrowIfNull(robot); + ArgumentNullException.ThrowIfNull(uploaded); + options ??= new FlyshotExecutionOptions(); + settings ??= CreateDefaultRobotSettings(); + var requestedSpeedRatio = speedRatio ?? settings.SpeedRatio; + var configuredPlanningSpeedScale = planningSpeedScale ?? settings.PlanningSpeedScale; + var effectivePlanningSpeedScale = ResolveEffectivePlanningSpeedScale(configuredPlanningSpeedScale, requestedSpeedRatio, "飞拍"); + var planningRobot = ApplyPlanningSpeedScale(robot, effectivePlanningSpeedScale); + + _logger?.LogInformation( + "PlanUploadedFlyshot 开始: name={Name}, waypoints={WaypointCount}, method={Method}, useCache={UseCache}, planningSpeedScale={PlanningSpeedScale}, speedRatio={SpeedRatio}, effectivePlanningSpeedScale={EffectivePlanningSpeedScale}, smoothStartStopTiming={SmoothStartStopTiming}", + uploaded.Name, + uploaded.Waypoints.Count, + options.Method, + options.UseCache, + configuredPlanningSpeedScale, + requestedSpeedRatio, + effectivePlanningSpeedScale, + settings.SmoothStartStopTiming); + + var program = CreateProgram( + name: uploaded.Name, + waypoints: uploaded.Waypoints, + shotFlags: uploaded.ShotFlags, + offsetValues: uploaded.OffsetValues, + addressGroups: uploaded.AddressGroups); + + var method = ParseFlyshotMethod(options.Method); + var cacheKey = CreateFlyshotCacheKey(planningRobot, uploaded, options, settings, effectivePlanningSpeedScale); + if (options.UseCache && _flyshotCache.TryGetValue(cacheKey, out var cachedBundle)) + { + _logger?.LogInformation("PlanUploadedFlyshot 命中缓存: name={Name}, cacheKey={CacheKey}", uploaded.Name, cacheKey); + + // 命中缓存时只替换 TrajectoryResult 的 usedCache 标志,规划轨迹和触发时间轴保持不可变复用。 + return cachedBundle; + } + + var request = new TrajectoryRequest( + robot: planningRobot, + program: program, + method: method, + moveToStart: options.MoveToStart, + saveTrajectoryArtifacts: options.SaveTrajectory, + useCache: options.UseCache); + + var plannedTrajectory = PlanByMethod(request, method, settings); + var smoothedExecutionTrajectory = ApplyExecutionTiming(plannedTrajectory, settings); + var denseJointTrajectory = CreateLimitCompliantDenseTrajectory(ref smoothedExecutionTrajectory, shapeTrajectoryEdges: false); + var shotTimeline = _shotTimelineBuilder.Build( + smoothedExecutionTrajectory, + holdCycles: settings.IoKeepCycles, + samplePeriod: planningRobot.ServoPeriod, + useDo: settings.UseDo); + var result = CreateResult( + smoothedExecutionTrajectory, + shotTimeline, + denseJointTrajectory, + usedCache: false, + triggerSampleIndexOffsetCycles: settings.TriggerSampleIndexOffsetCycles); + var bundle = new PlannedExecutionBundle(plannedTrajectory, shotTimeline, result, executionTrajectory: smoothedExecutionTrajectory); + + _logger?.LogInformation( + "PlanUploadedFlyshot 完成: name={Name}, 时长={Duration}s, 触发事件数={TriggerCount}, 采样点数={SampleCount}", + uploaded.Name, result.Duration.TotalSeconds, result.TriggerTimeline.Count, result.DenseJointTrajectory?.Count ?? 0); + + if (options.UseCache) + { + _flyshotCache[cacheKey] = bundle; + } + + return bundle; + } + + /// + /// 按普通轨迹执行接口约束解析 method 参数。 + /// + /// 旧 SDK 传入的方法名。 + /// 领域层规划方法。 + private static PlanningMethod ParseOrdinaryMethod(string method) + { + var normalized = NormalizeMethod(method); + return normalized switch + { + "icsp" => PlanningMethod.Icsp, + "doubles" => PlanningMethod.Doubles, + _ => throw new ArgumentException($"Unsupported ExecuteTrajectory method: {method}", nameof(method)) + }; + } + + /// + /// 按飞拍轨迹执行接口约束解析 method 参数。 + /// + /// 旧 SDK 传入的方法名。 + /// 领域层规划方法。 + private static PlanningMethod ParseFlyshotMethod(string method) + { + var normalized = NormalizeMethod(method); + return normalized switch + { + "icsp" => PlanningMethod.Icsp, + "self-adapt-icsp" => PlanningMethod.SelfAdaptIcsp, + "doubles" => PlanningMethod.Doubles, + _ => throw new ArgumentException($"Unsupported ExecuteFlyShotTraj method: {method}", nameof(method)) + }; + } + + /// + /// 按领域枚举分派到当前已经落地的规划器。 + /// + /// 规划请求。 + /// 规划方法。 + /// 规划轨迹。 + private PlannedTrajectory PlanByMethod(TrajectoryRequest request, PlanningMethod method, CompatibilityRobotSettings? settings = null) + { + return method switch + { + PlanningMethod.Icsp => _icspPlanner.Plan(request), + PlanningMethod.SelfAdaptIcsp => _selfAdaptIcspPlanner.Plan(request, settings?.AdaptIcspTryNum ?? 5), + PlanningMethod.Doubles => throw new NotSupportedException("doubles 轨迹规划尚未落地。"), + _ => throw new ArgumentOutOfRangeException(nameof(method), method, "未知轨迹规划方法。") + }; + } + + /// + /// 归一化旧 SDK 的 method 字符串。 + /// + /// 原始方法名。 + /// 小写短横线方法名。 + private static string NormalizeMethod(string method) + { + if (string.IsNullOrWhiteSpace(method)) + { + return "icsp"; + } + + return method.Trim().ToLowerInvariant(); + } + + /// + /// 为已上传飞拍轨迹构造包含参数和轨迹内容的缓存键,避免同名覆盖后误用旧规划结果。 + /// + /// 机器人配置。 + /// 上传轨迹。 + /// 执行参数。 + /// 缓存键。 + private static string CreateFlyshotCacheKey( + RobotProfile robot, + ControllerClientCompatUploadedTrajectory uploaded, + FlyshotExecutionOptions options, + CompatibilityRobotSettings settings, + double planningSpeedScale) + { + var hash = new HashCode(); + hash.Add(robot.Name, StringComparer.Ordinal); + hash.Add(planningSpeedScale); + hash.Add(uploaded.Name, StringComparer.Ordinal); + hash.Add(NormalizeMethod(options.Method), StringComparer.Ordinal); + hash.Add(options.MoveToStart); + hash.Add(options.SaveTrajectory); + hash.Add(settings.UseDo); + hash.Add(settings.IoKeepCycles); + hash.Add(settings.TriggerSampleIndexOffsetCycles); + hash.Add(settings.AdaptIcspTryNum); + hash.Add(settings.SmoothStartStopTiming); + + foreach (var limit in robot.JointLimits) + { + hash.Add(limit.JointName, StringComparer.Ordinal); + hash.Add(limit.VelocityLimit); + hash.Add(limit.AccelerationLimit); + hash.Add(limit.JerkLimit); + } + + foreach (var waypoint in uploaded.Waypoints) + { + foreach (var value in waypoint) + { + hash.Add(value); + } + } + + foreach (var flag in uploaded.ShotFlags) + { + hash.Add(flag); + } + + foreach (var offset in uploaded.OffsetValues) + { + hash.Add(offset); + } + + foreach (var group in uploaded.AddressGroups) + { + foreach (var address in group) + { + hash.Add(address); + } + } + + return hash.ToHashCode().ToString("X8"); + } + + /// + /// 构造编排器直接调用时的默认兼容配置,保持既有单元测试中的 DO 生成行为。 + /// + /// 默认机器人兼容配置。 + private static CompatibilityRobotSettings CreateDefaultRobotSettings() + { + return new CompatibilityRobotSettings( + useDo: true, + ioAddresses: Array.Empty(), + ioKeepCycles: 0, + triggerSampleIndexOffsetCycles: 7, + accLimitScale: 1.0, + jerkLimitScale: 1.0, + adaptIcspTryNum: 5); + } + + /// + /// 解析飞拍生成轨迹时真正使用的规划速度倍率。 + /// + /// 配置或调用方传入的规划倍率。 + /// 本次执行速度倍率。 + /// 用于缩放规划关节限制的有效倍率。 + private static double ResolveEffectivePlanningSpeedScale(double planningSpeedScale, double speedRatio, string trajectoryKind) + { + if (speedRatio <= 0.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(speedRatio), $"{trajectoryKind}速度倍率必须是有限正数。"); + } + + // 生成轨迹时把执行倍率折算进规划限制,保证 planning_speed_scale * speed_ratio 相同的配置产出同一条轨迹。 + return planningSpeedScale * speedRatio; + } + + /// + /// 按运行配置决定是否对规划结果做执行前时间轴重映射。 + /// + /// 规划阶段得到的轨迹。 + /// 当前 RobotConfig.json 解析出的兼容设置。 + /// 运行时真正用于采样和触发的轨迹。 + private static PlannedTrajectory ApplyExecutionTiming(PlannedTrajectory plannedTrajectory, CompatibilityRobotSettings settings) + { + // legacy-fit 模式需要严格保留 waypoint.txt 反推出的节点时间,不能再二次改写时间轴。 + return settings.SmoothStartStopTiming + ? ApplySmoothStartStopTiming(plannedTrajectory) + : plannedTrajectory; + } + + /// + /// 按规划全局速度倍率生成规划专用机器人约束。 + /// + /// 原始机器人约束。 + /// 规划阶段的全局速度倍率,1.0 表示不额外缩放。 + /// 已按速度倍率缩放后的规划机器人约束。 + private static RobotProfile ApplyPlanningSpeedScale(RobotProfile robot, double planningSpeedScale) + { + if (double.IsNaN(planningSpeedScale) || double.IsInfinity(planningSpeedScale) || planningSpeedScale <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(planningSpeedScale), "规划速度倍率必须是有限正数。"); + } + + if (Math.Abs(planningSpeedScale - 1.0) < 1e-12) + { + return robot; + } + + // speed_ratio 已在调用方折算到规划倍率中;运行时只消费已经生成好的 8ms 稠密轨迹。 + var scaledLimits = robot.JointLimits + .Select(limit => new JointLimit( + limit.JointName, + limit.VelocityLimit * planningSpeedScale, + limit.AccelerationLimit * planningSpeedScale * planningSpeedScale, + limit.JerkLimit * planningSpeedScale * planningSpeedScale * planningSpeedScale)) + .ToArray(); + + return new RobotProfile( + name: robot.Name, + modelPath: robot.ModelPath, + degreesOfFreedom: robot.DegreesOfFreedom, + jointLimits: scaledLimits, + jointCouplings: robot.JointCouplings, + servoPeriod: robot.ServoPeriod, + triggerPeriod: robot.TriggerPeriod); + } + + /// + /// 把兼容层输入数组转换成领域层 FlyshotProgram。 + /// + /// 轨迹名称。 + /// 关节路点。 + /// 拍照标志。 + /// 偏移周期。 + /// IO 地址组。 + /// 领域层飞拍程序。 + private static FlyshotProgram CreateProgram( + string name, + IEnumerable> waypoints, + IEnumerable shotFlags, + IEnumerable offsetValues, + IEnumerable> addressGroups) + { + return new FlyshotProgram( + name: name, + waypoints: waypoints.Select(static waypoint => new JointWaypoint(waypoint)).ToArray(), + shotFlags: shotFlags.ToArray(), + offsetValues: offsetValues.ToArray(), + addressGroups: addressGroups.Select(static group => new IoAddressGroup(group)).ToArray()); + } + + /// + /// 从规划轨迹和触发时间轴构造运行时可消费的稳定结果对象。 + /// + /// 规划后的轨迹。 + /// 触发时间轴。 + /// 运行时执行结果描述。 + private static TrajectoryResult CreateResult( + PlannedTrajectory plannedTrajectory, + ShotTimeline shotTimeline, + IReadOnlyList> denseJointTrajectory, + bool usedCache, + int triggerSampleIndexOffsetCycles) + { + return new TrajectoryResult( + programName: plannedTrajectory.OriginalProgram.Name, + method: plannedTrajectory.Method, + isValid: true, + duration: TimeSpan.FromSeconds(plannedTrajectory.WaypointTimes[^1]), + shotEvents: shotTimeline.ShotEvents, + triggerTimeline: shotTimeline.TriggerTimeline, + artifacts: Array.Empty(), + failureReason: null, + usedCache: usedCache, + originalWaypointCount: plannedTrajectory.OriginalWaypointCount, + plannedWaypointCount: plannedTrajectory.PlannedWaypointCount, + triggerSampleIndexOffsetCycles: triggerSampleIndexOffsetCycles, + denseJointTrajectory: denseJointTrajectory); + } + + /// + /// 生成满足离散速度、加速度和 Jerk 限制的稠密执行轨迹。 + /// + private IReadOnlyList> CreateLimitCompliantDenseTrajectory( + ref PlannedTrajectory executionTrajectory, + bool shapeTrajectoryEdges) + { + for (var iteration = 0; iteration <= MaxDenseLimitStretchIterations; iteration++) + { + var denseJointTrajectory = TrajectorySampler.SampleJointTrajectory( + executionTrajectory, + samplePeriod: executionTrajectory.Robot.ServoPeriod.TotalSeconds, + smoothStartStop: shapeTrajectoryEdges); + + try + { + TrajectoryLimitValidator.ValidateDenseJointTrajectory( + executionTrajectory.Robot, + denseJointTrajectory, + trajectoryName: executionTrajectory.OriginalProgram.Name, + validateJerk: false); + return denseJointTrajectory; + } + catch (InvalidOperationException ex) when (iteration < MaxDenseLimitStretchIterations) + { + _logger?.LogWarning(ex, "稠密轨迹离散限幅校验失败,准备拉长时间轴重试"); + // 离散差分超限时统一拉长时间轴,保持路点几何不变并降低速度、加速度和 Jerk。 + executionTrajectory = StretchTrajectoryTiming(executionTrajectory, DenseLimitStretchFactor); + _logger?.LogInformation( + "离散差分超限,拉长时间轴,iteration={Iteration}, factor={StretchFactor}", + iteration, + DenseLimitStretchFactor); + _logger?.LogInformation("拉长之后的总时间={TotalTime}", executionTrajectory.WaypointTimes[^1]); + } + } + + throw new InvalidOperationException("稠密轨迹离散限幅校验未能产生有效结果。"); + } + + /// + /// 按统一倍率拉长轨迹时间轴,保留原始路点和触发元数据。 + /// + private PlannedTrajectory StretchTrajectoryTiming(PlannedTrajectory trajectory, double stretchFactor) + { + var waypointTimes = trajectory.WaypointTimes.Select(time => time * stretchFactor).ToArray(); + var segmentDurations = trajectory.SegmentDurations.Select(duration => duration * stretchFactor).ToArray(); + var segmentScales = trajectory.SegmentScales.Select(scale => scale / stretchFactor).ToArray(); + + return new PlannedTrajectory( + robot: trajectory.Robot, + originalProgram: trajectory.OriginalProgram, + plannedWaypoints: trajectory.PlannedWaypoints, + waypointTimes: waypointTimes, + segmentDurations: segmentDurations, + segmentScales: segmentScales, + method: trajectory.Method, + iterations: trajectory.Iterations, + threshold: trajectory.Threshold); + } + + /// + /// 为飞拍执行生成平滑起停时间轴(仅重定时,不改几何路点)。 + /// 该方法保持 不变,只重映射 , + /// 让轨迹在起点和终点附近具有更柔和的速度变化,从而降低“突然起步/突然收尾”带来的离散差分尖峰。 + /// + /// 规划器输出的原始轨迹(通常是线性时间轴)。 + /// + /// 若满足平滑条件,则返回新的重定时轨迹;若路点数量不足或总时长无效,则直接返回输入轨迹。 + /// + private static PlannedTrajectory ApplySmoothStartStopTiming(PlannedTrajectory plannedTrajectory) + { + var originalTimes = plannedTrajectory.WaypointTimes; + // 至少需要“起点-中间点-终点”三类点,才有可平滑的中间区间。 + if (originalTimes.Count < 3) + { + return plannedTrajectory; + } + + var totalDuration = originalTimes[^1]; + // 轨迹总时长必须为正,避免后续归一化进度出现除零或无意义映射。 + if (totalDuration <= 0.0) + { + return plannedTrajectory; + } + + var smoothedTimes = new double[originalTimes.Count]; + // 强制固定边界:起点仍在 0,终点仍在总时长,保证任务总耗时不变。 + smoothedTimes[0] = 0.0; + smoothedTimes[^1] = totalDuration; + + for (var index = 1; index < originalTimes.Count - 1; index++) + { + // 把原始时刻归一化到 [0, 1],用于统一时间律变换。 + var normalizedProgress = originalTimes[index] / totalDuration; + // 先求完整平滑时间律对应的时刻,再与原线性时刻按比例混合。 + // 这里采用弱平滑(blend=0.35):保留大部分原节奏,仅轻微降低首尾突变。 + var smoothedProgress = InvertSmoothStartStopProgress(normalizedProgress); + var blendedProgress = ((1.0 - SmoothStartStopBlend) * normalizedProgress) + + (SmoothStartStopBlend * smoothedProgress); + smoothedTimes[index] = totalDuration * blendedProgress; + } + + var segmentDurations = new double[smoothedTimes.Length - 1]; + for (var index = 0; index < segmentDurations.Length; index++) + { + // 重建每段持续时间,供后续稠密采样和限幅检查使用。 + segmentDurations[index] = smoothedTimes[index + 1] - smoothedTimes[index]; + } + + return new PlannedTrajectory( + robot: plannedTrajectory.Robot, + originalProgram: plannedTrajectory.OriginalProgram, + plannedWaypoints: plannedTrajectory.PlannedWaypoints, + waypointTimes: smoothedTimes, + segmentDurations: segmentDurations, + segmentScales: plannedTrajectory.SegmentScales, + method: plannedTrajectory.Method, + iterations: plannedTrajectory.Iterations, + threshold: plannedTrajectory.Threshold); + } + + /// + /// 反解平滑时间律进度:给定目标进度 p,求满足 f(t)=p 的 t。 + /// 其中 f(t) 由 给出,是单调递增的 7 次 smootherstep 曲线, + /// 通过二分法可稳定得到对应的归一化时间,避免显式求高次方程根带来的复杂性和数值不稳定。 + /// + /// 目标归一化进度,理论范围 [0, 1]。 + /// 与目标进度对应的归一化时间,范围 [0, 1]。 + private static double InvertSmoothStartStopProgress(double normalizedProgress) + { + // 防御式裁剪,避免上游浮点误差将进度推到区间外。 + var target = Math.Clamp(normalizedProgress, 0.0, 1.0); + var low = 0.0; + var high = 1.0; + // 固定迭代次数确保耗时可预测;40 次足以把区间收敛到工程可用精度。 + for (var iteration = 0; iteration < 40; iteration++) + { + var middle = (low + high) / 2.0; + var progress = EvaluateSmoothStartStopProgress(middle); + // f(middle) 小于目标,说明根在右半区间;否则在左半区间。 + if (progress < target) + { + low = middle; + } + else + { + high = middle; + } + } + + return (low + high) / 2.0; + } + + /// + /// 计算 7 次 smootherstep 的进度函数值 f(u)。 + /// 该函数在 u=0 和 u=1 处具有更高阶导数连续性,可让起停段速度变化更平滑, + /// 适合用于飞拍轨迹的时间重参数化,减少离散导数在边界处的突变。 + /// + /// 归一化时间 u,理论范围 [0, 1]。 + /// 归一化进度 f(u),范围 [0, 1]。 + private static double EvaluateSmoothStartStopProgress(double normalizedTime) + { + // 先裁剪再计算多项式,确保数值稳定且不受外部越界输入影响。 + var u = Math.Clamp(normalizedTime, 0.0, 1.0); + var u2 = u * u; + var u3 = u2 * u; + var u4 = u3 * u; + var u5 = u4 * u; + var u6 = u5 * u; + var u7 = u6 * u; + return (35.0 * u4) - (84.0 * u5) + (70.0 * u6) - (20.0 * u7); + } +} diff --git a/src/Flyshot.ControllerClientCompat/Flyshot.ControllerClientCompat.csproj b/src/Flyshot.ControllerClientCompat/Flyshot.ControllerClientCompat.csproj new file mode 100644 index 0000000..ba5ff6c --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/Flyshot.ControllerClientCompat.csproj @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + <_Parameter1>Flyshot.Core.Tests + + + + diff --git a/src/Flyshot.ControllerClientCompat/FlyshotExecutionOptions.cs b/src/Flyshot.ControllerClientCompat/FlyshotExecutionOptions.cs new file mode 100644 index 0000000..67aa6b7 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/FlyshotExecutionOptions.cs @@ -0,0 +1,54 @@ +namespace Flyshot.ControllerClientCompat; + +/// +/// 表示飞拍轨迹执行接口的可选参数,字段名对齐旧 `ControllerClient::ExecuteFlyShotTraj`。 +/// +public sealed class FlyshotExecutionOptions +{ + /// + /// 初始化飞拍轨迹执行参数。 + /// + /// 执行前是否自动移动到轨迹起点。 + /// 轨迹生成方法,支持 `icsp`、`doubles` 或 `self-adapt-icsp`。 + /// 是否保存轨迹信息。 + /// 是否优先复用已规划轨迹缓存。 + /// 是否等待机器人执行完整条飞拍轨迹后再返回。 + public FlyshotExecutionOptions( + bool moveToStart = true, + string method = "icsp", + bool saveTrajectory = true, + bool useCache = true, + bool wait = true) + { + MoveToStart = moveToStart; + Method = string.IsNullOrWhiteSpace(method) ? "icsp" : method; + SaveTrajectory = saveTrajectory; + UseCache = useCache; + Wait = wait; + } + + /// + /// 获取执行前是否自动移动到轨迹起点。 + /// + public bool MoveToStart { get; } + + /// + /// 获取轨迹生成方法。 + /// + public string Method { get; } + + /// + /// 获取是否保存轨迹信息。 + /// + public bool SaveTrajectory { get; } + + /// + /// 获取是否优先复用已规划轨迹缓存。 + /// + public bool UseCache { get; } + + /// + /// 获取是否等待机器人执行完整条飞拍轨迹后再返回。 + /// + public bool Wait { get; } +} diff --git a/src/Flyshot.ControllerClientCompat/FlyshotTrajectoryArtifactWriter.cs b/src/Flyshot.ControllerClientCompat/FlyshotTrajectoryArtifactWriter.cs new file mode 100644 index 0000000..735b4cc --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/FlyshotTrajectoryArtifactWriter.cs @@ -0,0 +1,341 @@ +using Flyshot.Core.Config; +using Flyshot.Core.Domain; +using Flyshot.Core.Planning; +using Flyshot.Core.Planning.Export; +using Flyshot.Core.Planning.Kinematics; +using Flyshot.Core.Planning.Sampling; +using Microsoft.Extensions.Logging; +using System.Text; + +namespace Flyshot.ControllerClientCompat; + +/// +/// 负责把 saveTrajectory 生成的规划结果点位写入运行目录 Config/Data。 +/// +public sealed class FlyshotTrajectoryArtifactWriter +{ + /// + /// 旧 Data 明细点位文件使用的默认采样周期,单位为秒。 + /// + private const double LegacyDetailSamplePeriodSeconds = 0.016; + + /// + /// FANUC J519 实际下发的固定伺服周期,单位为秒。 + /// + private const double ActualSendServoPeriodSeconds = 0.008; + + private readonly ControllerClientCompatOptions _options; + private readonly RobotModelLoader _robotModelLoader; + private readonly ILogger? _logger; + + /// + /// 初始化规划结果点位导出器。 + /// + /// 兼容层基础配置,用于定位运行配置根目录。 + /// 机器人模型加载器,用于生成笛卡尔点位。 + /// 日志记录器;允许 null。 + public FlyshotTrajectoryArtifactWriter( + ControllerClientCompatOptions options, + RobotModelLoader robotModelLoader, + ILogger? logger = null) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _robotModelLoader = robotModelLoader ?? throw new ArgumentNullException(nameof(robotModelLoader)); + _logger = logger; + } + + /// + /// 将飞拍规划结果导出到 Config/Data/name。 + /// + /// 飞拍轨迹名称。 + /// 当前机器人配置。 + /// 规划结果包;旧规划导出和实发诊断使用同一份视图。 + /// 请求速度倍率,仅作为诊断列保留;最终采样倍率已在规划阶段处理。 + public void WriteUploadedFlyshot(string trajectoryName, RobotProfile robot, PlannedExecutionBundle bundle, double speedRatio = 1.0) + { + WriteUploadedFlyshot(trajectoryName, robot, bundle, bundle, speedRatio); + } + + /// + /// 将飞拍规划结果导出到 Config/Data/name,并显式区分旧规划导出视图和实发诊断视图。 + /// + /// 飞拍轨迹名称。 + /// 当前机器人配置。 + /// 旧格式规划导出使用的结果包,不应包含运行时 speedRatio 折算。 + /// 实发诊断使用的结果包,应与运行时最终发送队列保持一致。 + /// 请求速度倍率,仅作为 ActualSend 诊断列保留。 + public void WriteUploadedFlyshot( + string trajectoryName, + RobotProfile robot, + PlannedExecutionBundle exportPlanningBundle, + PlannedExecutionBundle executionBundle, + double speedRatio = 1.0) + { + if (string.IsNullOrWhiteSpace(trajectoryName)) + { + throw new ArgumentException("轨迹名称不能为空。", nameof(trajectoryName)); + } + + ArgumentNullException.ThrowIfNull(robot); + ArgumentNullException.ThrowIfNull(exportPlanningBundle); + ArgumentNullException.ThrowIfNull(executionBundle); + + var outputDir = Path.Combine(_options.ResolveConfigRoot(), "Data", SanitizeDirectoryName(trajectoryName)); + Directory.CreateDirectory(outputDir); + if (exportPlanningBundle.Result.DenseJointTrajectory is null) + { + throw new InvalidOperationException("导出飞拍旧格式规划工件前必须先生成规划明细轨迹。"); + } + + // 旧格式规划文件固定使用不含运行时 speedRatio 的规划视图,避免和 ActualSend 实发诊断混在一起。 + var kinematicsModel = _robotModelLoader.LoadKinematicsModel(robot.ModelPath); + var jointTrajectory = BuildJointRows(exportPlanningBundle.PlannedTrajectory); + _logger?.LogInformation("规划之后的轨迹点位数量为:{}", jointTrajectory.Count); + var executionDenseTrajectory = exportPlanningBundle.Result.DenseJointTrajectory; + var jointDetailTrajectory = DownsampleDenseRows( + executionDenseTrajectory, + samplePeriodSeconds: LegacyDetailSamplePeriodSeconds); + var cartTrajectory = BuildCartesianRows(exportPlanningBundle.PlannedTrajectory, kinematicsModel); + var cartDetailTrajectory = BuildCartesianRowsFromJointDense(jointDetailTrajectory, kinematicsModel); + + TrajectoryExporter.WriteJointTrajectory(Path.Combine(outputDir, "JointTraj.txt"), jointTrajectory); + TrajectoryExporter.WriteJointDenseTrajectory(Path.Combine(outputDir, "JointDetialTraj.txt"), jointDetailTrajectory); + TrajectoryExporter.WriteCartesianTrajectory(Path.Combine(outputDir, "CartTraj.txt"), cartTrajectory); + TrajectoryExporter.WriteCartesianDenseTrajectory(Path.Combine(outputDir, "CartDetialTraj.txt"), cartDetailTrajectory); + WriteActualSendArtifacts(outputDir, robot, executionBundle.Result, speedRatio); + + _logger?.LogInformation( + "saveTrajectory 已导出规划点位: name={TrajectoryName}, outputDir={OutputDir}, jointRows={JointRows}, detailRows={DetailRows}, speedRatio={SpeedRatio}", + trajectoryName, + outputDir, + jointTrajectory.Count, + jointDetailTrajectory.Count, + speedRatio); + } + + /// + /// 生成按 J519 8ms 实际发送周期重采样的轨迹点,供 saveTrajectory 离线对比真实下发序列。 + /// + private void WriteActualSendArtifacts(string outputDir, RobotProfile robot, TrajectoryResult result, double speedRatio) + { + ArgumentNullException.ThrowIfNull(robot); + + var preparedExecution = result.PreparedFlyshotExecution; + if (result.DenseJointTrajectory is null && preparedExecution is null) + { + return; + } + + var samples = preparedExecution is null + ? J519SendTrajectorySampler.SampleDenseJointTrajectory( + result.DenseJointTrajectory!, + result.Duration.TotalSeconds, + ActualSendServoPeriodSeconds, + speedRatio: 1.0) + : preparedExecution.Samples.Select(static sample => new J519SendSample( + sample.SampleIndex, + sample.SendTime, + sample.TrajectoryTime, + sample.SpeedRatio, + sample.JointsDegrees)).ToArray(); + var triggerBindings = preparedExecution is null + ? TriggerSampleBinder.Bind( + result.TriggerTimeline, + samples, + result.TriggerSampleIndexOffsetCycles) + : preparedExecution.TriggerBindings.Select(static binding => + new TriggerSampleBinding( + binding.Trigger, + new J519SendSample( + binding.Sample.SampleIndex, + binding.Sample.SendTime, + binding.Sample.TrajectoryTime, + binding.Sample.SpeedRatio, + binding.Sample.JointsDegrees), + binding.SampleIndex, + binding.FoundInWindow)).ToArray(); + TrajectoryLimitValidator.ValidateJ519SendSamples( + robot, + samples, + trajectoryName: result.ProgramName, + validateJerk: false); + var jointRows = new List>(samples.Count); + List> timingRows = preparedExecution is null + ? new List>(samples.Count) + : preparedExecution.TimingRows.Select(static row => (IReadOnlyList)row.ToArray()).ToList(); + List> jerkRows = preparedExecution is null + ? [] + : preparedExecution.JerkRows.Select(static row => (IReadOnlyList)row.ToArray()).ToList(); + double? previousSendTime = null; + double[]? previousJoints = null; + double[]? previousVelocity = null; + double[]? previousAcceleration = null; + + foreach (var sample in samples) + { + jointRows.Add(BuildActualSendJointRow(sample.SendTime, sample.JointsDegrees)); + if (preparedExecution is null) + { + timingRows.Add(J519SendTrajectorySampler.BuildTimingRow(sample, speedRatio, stretchIterationCount: 0)); + + if (previousSendTime is not null && previousJoints is not null) + { + jerkRows.Add(J519SendTrajectorySampler.BuildJerkRow( + previousSendTime.Value, + sample.SendTime, + previousJoints, + sample.JointsDegrees, + ref previousVelocity, + ref previousAcceleration)); + } + } + + previousSendTime = sample.SendTime; + previousJoints = sample.JointsDegrees.ToArray(); + } + + WriteDenseRows(Path.Combine(outputDir, "ActualSendJointTraj.txt"), jointRows); + WriteDenseRows(Path.Combine(outputDir, "ActualSendTiming.txt"), timingRows); + WriteDenseRows(Path.Combine(outputDir, "ActualSendJerkStats.txt"), jerkRows); + TrajectoryExporter.WriteShotEvents(Path.Combine(outputDir, "ShotEvents.json"), result.ShotEvents, triggerBindings); + } + + /// + /// 构造实际发送点位文本行,格式为 send_time + 关节角度 + io_mask + io_value。 + /// + private static IReadOnlyList BuildActualSendJointRow(double sendTime, IReadOnlyList joints) + { + var row = new double[joints.Count + 3]; + row[0] = Math.Round(sendTime, 6); + for (var index = 0; index < joints.Count; index++) + { + row[index + 1] = Math.Round(joints[index], 6); + } + + row[^2] = 0.0; + row[^1] = 0.0; + return row; + } + + /// + /// 以空格分隔的旧轨迹文本格式写出数值行。 + /// + private static void WriteDenseRows(string path, IReadOnlyList> rows) + { + var sb = new StringBuilder(); + foreach (var row in rows) + { + sb.AppendLine(string.Join(" ", row.Select(static value => $"{value:F6}"))); + } + + File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false)); + } + + /// + /// 构造 JointTraj.txt 行数据,格式为 time + 关节弧度。 + /// + private static IReadOnlyList> BuildJointRows(PlannedTrajectory trajectory) + { + var rows = new List>(trajectory.PlannedWaypoints.Count); + for (var index = 0; index < trajectory.PlannedWaypoints.Count; index++) + { + var row = new List(trajectory.PlannedWaypoints[index].Positions.Count + 1) + { + Math.Round(trajectory.WaypointTimes[index], 6) + }; + row.AddRange(trajectory.PlannedWaypoints[index].Positions.Select(static value => Math.Round(value, 6))); + rows.Add(row); + } + + return rows; + } + + /// + /// 构造 CartTraj.txt 行数据,格式为 time + x/y/z/qx/qy/qz/qw。 + /// + private static IReadOnlyList> BuildCartesianRows( + PlannedTrajectory trajectory, + RobotKinematicsModel kinematicsModel) + { + var rows = new List>(trajectory.PlannedWaypoints.Count); + for (var index = 0; index < trajectory.PlannedWaypoints.Count; index++) + { + var pose = RobotKinematics.ForwardKinematics(kinematicsModel, trajectory.PlannedWaypoints[index].Positions.ToArray()); + var row = new List(pose.Length + 1) + { + Math.Round(trajectory.WaypointTimes[index], 6) + }; + row.AddRange(pose.Select(static value => Math.Round(value, 6))); + rows.Add(row); + } + + return rows; + } + + /// + /// 基于执行侧稠密关节轨迹生成笛卡尔导出行,保持与 JointDetialTraj.txt 同一来源。 + /// + private static IReadOnlyList> BuildCartesianRowsFromJointDense( + IReadOnlyList> jointDenseRows, + RobotKinematicsModel kinematicsModel) + { + var rows = new List>(jointDenseRows.Count); + foreach (var jointRow in jointDenseRows) + { + var jointPositions = jointRow.Skip(1).ToArray(); + var pose = RobotKinematics.ForwardKinematics(kinematicsModel, jointPositions); + var row = new List(pose.Length + 1) + { + Math.Round(jointRow[0], 6) + }; + row.AddRange(pose.Select(static value => Math.Round(value, 6))); + rows.Add(row); + } + + return rows; + } + + /// + /// 将 8ms 执行稠密轨迹按指定周期抽稀为低频兼容视图,并始终保留终点。 + /// + private static IReadOnlyList> DownsampleDenseRows( + IReadOnlyList> denseRows, + double samplePeriodSeconds) + { + var result = new List>(); + var epsilon = 1e-6; + var nextSampleTime = 0.0; + + foreach (var row in denseRows) + { + var sampleTime = row[0]; + if (sampleTime + epsilon < nextSampleTime) + { + continue; + } + + if (Math.Abs(sampleTime - nextSampleTime) <= epsilon || sampleTime.Equals(0.0)) + { + result.Add(row); + nextSampleTime += samplePeriodSeconds; + } + } + + if (result.Count == 0 || !ReferenceEquals(result[^1], denseRows[^1])) + { + result.Add(denseRows[^1]); + } + + return result; + } + + /// + /// 将轨迹名转换为可用目录名,避免 HTTP 输入中的路径字符污染输出目录。 + /// + private static string SanitizeDirectoryName(string name) + { + var invalidChars = Path.GetInvalidFileNameChars(); + var chars = name.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray(); + return new string(chars); + } +} diff --git a/src/Flyshot.ControllerClientCompat/FlyshotTrajectoryEdgeShaper.cs b/src/Flyshot.ControllerClientCompat/FlyshotTrajectoryEdgeShaper.cs new file mode 100644 index 0000000..9515930 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/FlyshotTrajectoryEdgeShaper.cs @@ -0,0 +1,217 @@ +namespace Flyshot.ControllerClientCompat; + +/// +/// 对飞拍稠密关节轨迹的首尾采样点做速度整形,降低启动和结束时的单步角度变化。 +/// +internal static class FlyshotTrajectoryEdgeShaper +{ + /// + /// 首尾整形默认覆盖的采样点数(含锚点)。 + /// + internal const int DefaultEdgePointCount = 10; + + /// + /// 对稠密关节轨迹做首尾整形,时间列保持不变,首段采用 ease-in,尾段采用 ease-out。 + /// + /// 输入稠密关节轨迹,每行格式为 [time, j1..jN],关节单位为弧度。 + /// 保留旧签名兼容调用方;当前实现不再按角度阈值扩窗。 + /// 单侧整形覆盖的采样点数(含锚点),默认首尾各 10 点。 + /// 经过首尾整形后的新轨迹;若不满足整形条件则返回原轨迹副本。 + internal static IReadOnlyList> ShapeDenseJointTrajectory( + IReadOnlyList> denseJointTrajectory, + double maxEdgeStepDegrees = 0.0, + int maxWindowPoints = DefaultEdgePointCount) + { + ArgumentNullException.ThrowIfNull(denseJointTrajectory); + if (denseJointTrajectory.Count == 0) + { + return Array.Empty>(); + } + + var copiedRows = denseJointTrajectory + .Select(static row => row.ToArray()) + .ToArray(); + if (copiedRows.Length < 5 || maxWindowPoints < 2) + { + return copiedRows; + } + + var lastIndex = copiedRows.Length - 1; + var window = Math.Min(maxWindowPoints, lastIndex / 2); + if (window < 2) + { + return copiedRows; + } + + // 以原始轨迹为参考估计窗口边界的速度,并在位移累计量上做单段单调整形, + // 目标是让首尾 10 点表现为更平滑的加减速,而不是硬匹配高阶导数导致振荡。 + var originalRows = copiedRows + .Select(static row => row.ToArray()) + .ToArray(); + + ApplyLeadingHermiteBlend(copiedRows, originalRows, window); + ApplyTrailingHermiteBlend(copiedRows, originalRows, window); + + return copiedRows; + } + + /// + /// 对首段做单段 Hermite 累计位移整形:起点速度为 0,窗口末端按原轨迹边界速度接回中段。 + /// + private static void ApplyLeadingHermiteBlend(double[][] rows, double[][] originalRows, int window) + { + var startRow = originalRows[0]; + var endRow = originalRows[window]; + var totalDuration = endRow[0] - startRow[0]; + if (totalDuration <= 0.0) + { + return; + } + + for (var jointIndex = 1; jointIndex < startRow.Length; jointIndex++) + { + var delta = endRow[jointIndex] - startRow[jointIndex]; + if (Math.Abs(delta) <= 1e-12) + { + continue; + } + + var endVelocity = EstimateVelocity(originalRows, window, jointIndex); + var normalizedEndSlope = ClampNormalizedSlope((endVelocity * totalDuration) / delta); + + for (var index = 1; index < window; index++) + { + var normalizedTime = (rows[index][0] - startRow[0]) / totalDuration; + var shapedValue = startRow[jointIndex] + + (delta * EvaluateHermiteProgress(normalizedTime, startSlope: 0.0, endSlope: normalizedEndSlope)); + var blendWeight = Math.Pow(1.0 - normalizedTime, 2.0); + rows[index][jointIndex] = Lerp(originalRows[index][jointIndex], shapedValue, blendWeight); + } + } + } + + /// + /// 对尾段做单段 Hermite 累计位移整形:窗口起点按原轨迹边界速度接入,终点速度减到 0。 + /// + private static void ApplyTrailingHermiteBlend(double[][] rows, double[][] originalRows, int window) + { + var startIndex = rows.Length - 1 - window; + var startRow = originalRows[startIndex]; + var endRow = originalRows[^1]; + var totalDuration = endRow[0] - startRow[0]; + if (totalDuration <= 0.0) + { + return; + } + + for (var jointIndex = 1; jointIndex < startRow.Length; jointIndex++) + { + var delta = endRow[jointIndex] - startRow[jointIndex]; + if (Math.Abs(delta) <= 1e-12) + { + continue; + } + + var startVelocity = EstimateVelocity(originalRows, startIndex, jointIndex); + var normalizedStartSlope = ClampNormalizedSlope((startVelocity * totalDuration) / delta); + + for (var index = 1; index < window; index++) + { + var normalizedTime = (rows[startIndex + index][0] - startRow[0]) / totalDuration; + var shapedValue = startRow[jointIndex] + + (delta * EvaluateHermiteProgress(normalizedTime, startSlope: normalizedStartSlope, endSlope: 0.0)); + var blendWeight = Math.Pow(normalizedTime, 2.0); + rows[startIndex + index][jointIndex] = Lerp(originalRows[startIndex + index][jointIndex], shapedValue, blendWeight); + } + } + } + + /// + /// 估算给定行在原始轨迹上的一阶导,首尾退化为单边差分。 + /// + private static double EstimateVelocity(double[][] rows, int index, int jointIndex) + { + if (index <= 0) + { + var dt = rows[1][0] - rows[0][0]; + return dt <= 0.0 ? 0.0 : (rows[1][jointIndex] - rows[0][jointIndex]) / dt; + } + + if (index >= rows.Length - 1) + { + var dt = rows[^1][0] - rows[^2][0]; + return dt <= 0.0 ? 0.0 : (rows[^1][jointIndex] - rows[^2][jointIndex]) / dt; + } + + var previousDt = rows[index][0] - rows[index - 1][0]; + var nextDt = rows[index + 1][0] - rows[index][0]; + if (previousDt <= 0.0 || nextDt <= 0.0) + { + return 0.0; + } + + var backward = (rows[index][jointIndex] - rows[index - 1][jointIndex]) / previousDt; + var forward = (rows[index + 1][jointIndex] - rows[index][jointIndex]) / nextDt; + return (backward + forward) / 2.0; + } + + /// + /// 估算给定行在原始轨迹上的二阶导,端点退化为 0 以避免放大边界噪声。 + /// + private static double EstimateAcceleration(double[][] rows, int index, int jointIndex) + { + if (index <= 0 || index >= rows.Length - 1) + { + return 0.0; + } + + var previousDt = rows[index][0] - rows[index - 1][0]; + var nextDt = rows[index + 1][0] - rows[index][0]; + if (previousDt <= 0.0 || nextDt <= 0.0) + { + return 0.0; + } + + var backward = (rows[index][jointIndex] - rows[index - 1][jointIndex]) / previousDt; + var forward = (rows[index + 1][jointIndex] - rows[index][jointIndex]) / nextDt; + var averageDt = (previousDt + nextDt) / 2.0; + return averageDt <= 0.0 ? 0.0 : (forward - backward) / averageDt; + } + + /// + /// 计算 Hermite 累计位移曲线在 0..1 归一化时间上的进度值。 + /// + private static double EvaluateHermiteProgress(double normalizedTime, double startSlope, double endSlope) + { + var u = Math.Clamp(normalizedTime, 0.0, 1.0); + var u2 = u * u; + var u3 = u2 * u; + var h00 = (2.0 * u3) - (3.0 * u2) + 1.0; + var h10 = u3 - (2.0 * u2) + u; + var h01 = (-2.0 * u3) + (3.0 * u2); + var h11 = u3 - u2; + return (h00 * 0.0) + (h10 * startSlope) + (h01 * 1.0) + (h11 * endSlope); + } + + /// + /// 把归一化边界斜率限制在单调 Hermite 常见的稳定区间内,避免过冲和窗口内振荡。 + /// + private static double ClampNormalizedSlope(double normalizedSlope) + { + if (double.IsNaN(normalizedSlope) || double.IsInfinity(normalizedSlope)) + { + return 0.0; + } + + return Math.Clamp(normalizedSlope, 0.0, 3.0); + } + + /// + /// 在线性插值基础上做温和混合,避免首尾窗口为了追赶锚点而产生过大的局部跃度。 + /// + private static double Lerp(double originalValue, double shapedValue, double weight) + { + var clampedWeight = Math.Clamp(weight, 0.0, 1.0); + return originalValue + ((shapedValue - originalValue) * clampedWeight); + } +} diff --git a/src/Flyshot.ControllerClientCompat/FlyshotTrajectoryStore.cs b/src/Flyshot.ControllerClientCompat/FlyshotTrajectoryStore.cs new file mode 100644 index 0000000..6cadd92 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/FlyshotTrajectoryStore.cs @@ -0,0 +1,227 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Flyshot.Core.Config; +using Flyshot.Core.Domain; +using Microsoft.Extensions.Logging; + +namespace Flyshot.ControllerClientCompat; + +/// +/// 使用运行目录 Config/RobotConfig.json 持久化单机器人飞拍轨迹和机器人配置。 +/// +public sealed class JsonFlyshotTrajectoryStore +{ + private readonly ControllerClientCompatOptions _options; + private readonly RobotConfigLoader _configLoader; + private readonly ILogger? _logger; + + /// + /// 初始化基于 JSON 文件的轨迹存储。 + /// + /// 兼容层基础配置,用于定位运行配置根目录。 + /// 旧版 RobotConfig.json 加载器,用于反序列化已保存的轨迹。 + /// 日志记录器;允许 null。 + public JsonFlyshotTrajectoryStore(ControllerClientCompatOptions options, RobotConfigLoader configLoader, ILogger? logger = null) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _configLoader = configLoader ?? throw new ArgumentNullException(nameof(configLoader)); + _logger = logger; + } + + /// + /// 将单条轨迹持久化到统一 RobotConfig.json,同时更新机器人配置段。 + /// + /// 当前已初始化的机器人名称,仅用于日志诊断。 + /// 当前机器人级兼容配置。 + /// 要保存的已上传轨迹。 + public void Save(string robotName, CompatibilityRobotSettings settings, ControllerClientCompatUploadedTrajectory trajectory) + { + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(trajectory); + + _logger?.LogInformation( + "RobotConfig 保存轨迹: robot={RobotName}, name={TrajectoryName}, waypoints={WaypointCount}", + robotName, + trajectory.Name, + trajectory.Waypoints.Count); + + var path = ResolveStorePath(); + var directory = Path.GetDirectoryName(path)!; + Directory.CreateDirectory(directory); + + JsonObject root; + if (File.Exists(path)) + { + var existingJson = File.ReadAllText(path); + root = JsonNode.Parse(existingJson)?.AsObject() ?? new JsonObject(); + } + else + { + root = new JsonObject(); + } + + // 更新 robot 配置段,保持与旧版 RobotConfig.json 字段名一致。 + root["robot"] = SerializeRobotSettings(settings); + + // 确保 flying_shots 节点存在。 + if (!root.TryGetPropertyValue("flying_shots", out var flyingShotsNode) || flyingShotsNode is not JsonObject flyingShotsObj) + { + flyingShotsObj = new JsonObject(); + root["flying_shots"] = flyingShotsObj; + } + + flyingShotsObj[trajectory.Name] = SerializeTrajectory(trajectory); + + var writeOptions = new JsonSerializerOptions + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower + }; + File.WriteAllText(path, root.ToJsonString(writeOptions)); + + _logger?.LogInformation("RobotConfig 轨迹已保存到 {Path}", path); + } + + /// + /// 从统一 RobotConfig.json 删除指定名称的轨迹。 + /// + /// 当前已初始化的机器人名称,仅用于日志诊断。 + /// 要删除的轨迹名称。 + public void Delete(string robotName, string trajectoryName) + { + if (string.IsNullOrWhiteSpace(trajectoryName)) + { + throw new ArgumentException("轨迹名称不能为空。", nameof(trajectoryName)); + } + + _logger?.LogInformation("RobotConfig 删除轨迹: robot={RobotName}, name={TrajectoryName}", robotName, trajectoryName); + + var path = ResolveStorePath(); + if (!File.Exists(path)) + { + _logger?.LogWarning("RobotConfig 删除失败: 文件不存在 {Path}", path); + return; + } + + var existingJson = File.ReadAllText(path); + var root = JsonNode.Parse(existingJson)?.AsObject(); + if (root is null) + { + _logger?.LogWarning("RobotConfig 删除失败: 无法解析 JSON {Path}", path); + return; + } + + if (root.TryGetPropertyValue("flying_shots", out var flyingShotsNode) && flyingShotsNode is JsonObject flyingShotsObj) + { + var removed = flyingShotsObj.Remove(trajectoryName); + if (removed) + { + var writeOptions = new JsonSerializerOptions + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower + }; + File.WriteAllText(path, root.ToJsonString(writeOptions)); + _logger?.LogInformation("RobotConfig 轨迹已删除: {TrajectoryName}", trajectoryName); + } + else + { + _logger?.LogWarning("RobotConfig 删除失败: 轨迹不存在 {TrajectoryName}", trajectoryName); + } + } + } + + /// + /// 加载统一 RobotConfig.json 中的所有轨迹,并回传机器人配置。 + /// + /// 当前已初始化的机器人名称,仅用于日志诊断。 + /// 输出 RobotConfig.json 中的机器人配置;若文件不存在或解析失败则为 null。 + /// 按轨迹名称索引的已上传轨迹集合。 + public IReadOnlyDictionary LoadAll(string robotName, out CompatibilityRobotSettings? settings) + { + var path = ResolveStorePath(); + if (!File.Exists(path)) + { + _logger?.LogInformation("RobotConfig 无持久化数据: {Path}", path); + settings = null; + return new Dictionary(StringComparer.Ordinal); + } + + try + { + _logger?.LogInformation("RobotConfig 正在加载: {Path}", path); + + var loaded = _configLoader.Load(path, _options.ResolveConfigRoot()); + settings = loaded.Robot; + + var dict = new Dictionary(StringComparer.Ordinal); + foreach (var program in loaded.Programs) + { + var traj = new ControllerClientCompatUploadedTrajectory( + name: program.Value.Name, + waypoints: program.Value.Waypoints.Select(static wp => wp.Positions), + shotFlags: program.Value.ShotFlags, + offsetValues: program.Value.OffsetValues, + addressGroups: program.Value.AddressGroups.Select(static g => g.Addresses)); + dict[program.Key] = traj; + } + + _logger?.LogInformation( + "RobotConfig 加载完成: robot={RobotName}, 轨迹数={Count}, useDo={UseDo}, ioKeepCycles={IoKeepCycles}", + robotName, + dict.Count, + settings?.UseDo, + settings?.IoKeepCycles); + + return dict; + } + catch (Exception ex) + { + _logger?.LogError(ex, "RobotConfig 加载失败: {Path}", path); + settings = null; + return new Dictionary(StringComparer.Ordinal); + } + } + + /// + /// 把机器人兼容配置序列化为 JSON 对象,字段名与旧版 RobotConfig.json 一致。 + /// + private static JsonObject SerializeRobotSettings(CompatibilityRobotSettings settings) + { + return new JsonObject + { + ["use_do"] = JsonValue.Create(settings.UseDo), + ["io_addr"] = JsonSerializer.SerializeToNode(settings.IoAddresses), + ["io_keep_cycles"] = JsonValue.Create(settings.IoKeepCycles), + ["trigger_sample_index_offset_cycles"] = JsonValue.Create(settings.TriggerSampleIndexOffsetCycles), + ["acc_limit"] = JsonValue.Create(settings.AccLimitScale), + ["jerk_limit"] = JsonValue.Create(settings.JerkLimitScale), + ["adapt_icsp_try_num"] = JsonValue.Create(settings.AdaptIcspTryNum), + ["planning_speed_scale"] = JsonValue.Create(settings.PlanningSpeedScale), + ["speed_ratio"] = JsonValue.Create(settings.SpeedRatio), + ["smooth_start_stop_timing"] = JsonValue.Create(settings.SmoothStartStopTiming) + }; + } + + /// + /// 把已上传轨迹序列化为 JSON 对象,字段名与旧版 RobotConfig.json 的 flying_shots 节点一致。 + /// + private static JsonObject SerializeTrajectory(ControllerClientCompatUploadedTrajectory trajectory) + { + return new JsonObject + { + ["traj_waypoints"] = JsonSerializer.SerializeToNode(trajectory.Waypoints), + ["shot_flags"] = JsonSerializer.SerializeToNode(trajectory.ShotFlags), + ["offset_values"] = JsonSerializer.SerializeToNode(trajectory.OffsetValues), + ["addr"] = JsonSerializer.SerializeToNode(trajectory.AddressGroups) + }; + } + + /// + /// 解析单程序单机器人的统一配置文件路径。 + /// + private string ResolveStorePath() + { + return Path.Combine(_options.ResolveConfigRoot(), "RobotConfig.json"); + } +} diff --git a/src/Flyshot.ControllerClientCompat/IControllerClientCompatService.cs b/src/Flyshot.ControllerClientCompat/IControllerClientCompatService.cs new file mode 100644 index 0000000..3236873 --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/IControllerClientCompatService.cs @@ -0,0 +1,238 @@ +using Flyshot.Core.Domain; + +namespace Flyshot.ControllerClientCompat; + +/// +/// 定义 HTTP-only 兼容层对外暴露的 ControllerClient 语义服务接口。 +/// +public interface IControllerClientCompatService +{ + /// + /// 获取当前兼容层对外报告的服务端版本号。 + /// + string ServerVersion { get; } + + /// + /// 获取当前是否已经完成机器人初始化。 + /// + bool IsSetUp { get; } + + /// + /// 保存当前调用方期望连接的 replacement 服务端地址。 + /// + /// 客户端传入的服务端 IP。 + /// 客户端传入的服务端端口。 + void ConnectServer(string serverIp, int port); + + /// + /// 获取兼容服务端版本号。 + /// + /// 服务端版本号。 + string GetServerVersion(); + + /// + /// 获取兼容客户端版本号。 + /// + /// 客户端版本号。 + string GetClientVersion(); + + /// + /// 根据旧客户端使用的机器人名称完成机器人初始化。 + /// + /// 机器人名称。 + void SetUpRobot(string robotName); + + /// + /// 根据旧客户端传入的环境文件完成机器人初始化。 + /// + /// 环境文件路径。 + void SetUpRobotFromEnv(string envFile); + + /// + /// 设置是否显示 TCP 坐标轴。 + /// + /// 是否显示 TCP。 + /// 坐标轴长度。 + /// 坐标轴线宽。 + void SetShowTcp(bool isShow, double axisLength, int axisSize); + + /// + /// 记录当前激活的控制器类型。 + /// + /// 是否为仿真控制器。 + void SetActiveController(bool sim); + + /// + /// 记录当前控制器已经建立连接。 + /// + /// 控制器 IP。 + void Connect(string robotIp); + + /// + /// 记录当前控制器已经断开。 + /// + void Disconnect(); + + /// + /// 记录当前机器人进入使能态。 + /// + /// 缓冲区大小。 + void EnableRobot(int bufferSize); + + /// + /// 记录当前机器人退出使能态。 + /// + void DisableRobot(); + + /// + /// 停止当前运动状态。 + /// + void StopMove(); + + /// + /// 读取当前控制器运行时状态快照。 + /// + /// 控制器运行时状态快照。 + ControllerStateSnapshot GetControllerSnapshot(); + + /// + /// 读取状态页所需的兼容层元数据快照。 + /// + /// 兼容层元数据快照。 + ControllerClientStatusSnapshotMetadata GetStatusSnapshotMetadata(); + + /// + /// 获取当前速度倍率。 + /// + /// 当前速度倍率。 + double GetSpeedRatio(); + + /// + /// 更新当前速度倍率。 + /// + /// 目标速度倍率。 + void SetSpeedRatio(double ratio); + + /// + /// 写入兼容层缓存的 IO 数值。 + /// + /// IO 端口号。 + /// IO 值。 + /// IO 类型。 + void SetIo(int port, bool value, string ioType); + + /// + /// 读取兼容层缓存的 IO 数值。 + /// + /// IO 端口号。 + /// IO 类型。 + /// 缓存中的 IO 值。 + bool GetIo(int port, string ioType); + + /// + /// 按给定位姿和 seed 计算最近 IK。 + /// + /// 目标位姿数组。 + /// IK seed 关节数组。 + /// IK 结果关节数组。 + IReadOnlyList GetNearestIk(IReadOnlyList pose, IReadOnlyList seed); + + /// + /// 设置当前 TCP 三维坐标。 + /// + /// TCP X。 + /// TCP Y。 + /// TCP Z。 + void SetTcp(double x, double y, double z); + + /// + /// 读取当前 TCP 三维坐标。 + /// + /// TCP 数组。 + IReadOnlyList GetTcp(); + + /// + /// 读取当前关节位置。 + /// + /// 关节位置数组。 + IReadOnlyList GetJointPositions(); + + /// + /// 更新当前关节位置。 + /// + /// 目标关节位置。 + void MoveJoint(IReadOnlyList jointPositions); + + /// + /// 按直角坐标移动到目标 TCP 位姿。 + /// + /// 目标位姿 [x,y,z,w,p,r],单位为 mm/deg。 + void MovePose(IReadOnlyList pose); + + /// + /// 执行普通轨迹。 + /// + /// 轨迹路点集合。 + /// 执行参数。 + void ExecuteTrajectory(IReadOnlyList> waypoints, TrajectoryExecutionOptions? options = null); + + /// + /// 读取当前末端位姿快照。 + /// + /// 位姿数组。 + IReadOnlyList GetPose(); + + /// + /// 上传一条飞拍轨迹。 + /// + /// 飞拍轨迹。 + void UploadTrajectory(ControllerClientCompatUploadedTrajectory trajectory); + + /// + /// 列出当前已上传的飞拍轨迹名称。 + /// + /// 轨迹名称列表。 + IReadOnlyList ListTrajectoryNames(); + + /// + /// 执行指定名称的飞拍轨迹。 + /// + /// 轨迹名称。 + /// 飞拍执行参数。 + void ExecuteTrajectoryByName(string name, FlyshotExecutionOptions? options = null); + + /// + /// 保存指定飞拍轨迹的轨迹信息。 + /// + /// 轨迹名称。 + /// 轨迹生成方法。 + void SaveTrajectoryInfo(string name, string method = "icsp"); + + /// + /// 检查指定飞拍轨迹是否可执行。 + /// + /// 输出规划轨迹总时长。 + /// 轨迹名称。 + /// 轨迹生成方法。 + /// 是否保存轨迹信息。 + /// 轨迹是否有效。 + bool IsFlyshotTrajectoryValid(out TimeSpan duration, string name, string method = "icsp", bool saveTrajectory = false); + + /// + /// 删除指定名称的飞拍轨迹。 + /// + /// 轨迹名称。 + void DeleteTrajectory(string name); + + /// + /// 读取当前配置过的机器人名称。 + /// + /// 机器人名称。 + string GetRobotName(); + + /// + /// 读取当前机器人自由度。 + /// + /// 机器人自由度。 + int GetDegreesOfFreedom(); +} diff --git a/src/Flyshot.ControllerClientCompat/MoveJointTrajectoryGenerator.cs b/src/Flyshot.ControllerClientCompat/MoveJointTrajectoryGenerator.cs new file mode 100644 index 0000000..399d8da --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/MoveJointTrajectoryGenerator.cs @@ -0,0 +1,356 @@ +using Flyshot.Core.Domain; +using Microsoft.Extensions.Logging; + +namespace Flyshot.ControllerClientCompat; + +/// +/// MoveJoint 轨迹生成器。 +/// 将起始关节角到目标关节角的单段运动,按速度、加速度、jerk 约束生成稠密点到点轨迹, +/// 供 FANUC J519 伺服流逐周期下发。 +/// +/// 核心思路:路径只取关节空间直线 q=q0+(q1-q0)*s(t),时间律使用 7 阶平滑函数; +/// 生成后再按离散采样点反算速度、加速度和 jerk,确保真实下发点也满足限制。 +/// +internal static class MoveJointTrajectoryGenerator +{ + /// + /// 最小段基础时长(秒)。零位移时仍由采样对齐逻辑生成起点和终点两帧。 + /// + private const double MinimumMoveJointDurationSeconds = 0.0; + + /// + /// 7 阶平滑点到点时间律的一阶导数最大值。 + /// + private const double SmoothPtpVelocityShapeCoefficient = 2.1875; + + /// + /// 7 阶平滑点到点时间律的二阶导数最大值。 + /// + private const double SmoothPtpAccelerationShapeCoefficient = 7.513188404399293; + + /// + /// 7 阶平滑点到点时间律的三阶导数最大值。 + /// + private const double SmoothPtpJerkShapeCoefficient = 52.5; + + /// + /// 单次 MoveJoint 最大采样点数上限,防止极端配置下生成过大的轨迹数组。 + /// + private const int MaxMoveJointSampleCount = 1_000_000; + + /// + /// 离散限位校验允许的浮点容差。 + /// + private const double DiscreteLimitTolerance = 1.000001; + + /// + /// 离散限位校验失败时最多拉长的采样周期次数。 + /// + private const int MaxDiscreteLimitStretchIterations = 10_000; + + /// + /// 计算 MoveJoint 轨迹的完整结果。 + /// + /// 处理流程: + /// 1. 根据关节限位计算连续时间律理论最短时长 + /// 2. 按 speedRatio 缩放规划限位,并将时长对齐到固定伺服采样间隔 + /// 3. 用 7 阶平滑点到点时间律生成稠密轨迹点 + /// 4. 按离散点反查速度、加速度和 jerk,必要时拉长时长重算 + /// 5. 封装为 TrajectoryResult 返回 + /// + /// 机器人配置,含自由度数和关节限位。 + /// 起始关节角(弧度)。 + /// 目标关节角(弧度)。 + /// 速度倍率,必须大于 0;在生成阶段折算进有效关节限位。 + /// 可选的诊断日志。 + public static TrajectoryResult CreateResult( + RobotProfile robot, + IReadOnlyList startJoints, + IReadOnlyList targetJoints, + double speedRatio, + ILogger? logger = null) + { + ArgumentNullException.ThrowIfNull(robot); + ArgumentNullException.ThrowIfNull(startJoints); + ArgumentNullException.ThrowIfNull(targetJoints); + + if (speedRatio <= 0.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio)) + { + throw new InvalidOperationException("Speed ratio must be greater than zero for MoveJoint execution."); + } + + if (startJoints.Count != robot.DegreesOfFreedom || targetJoints.Count != robot.DegreesOfFreedom) + { + throw new InvalidOperationException($"MoveJoint expects {robot.DegreesOfFreedom} joints."); + } + + var planningRobot = ApplySpeedRatioToJointLimits(robot, speedRatio); + var requestedDurationSeconds = ResolveDurationSeconds(planningRobot, startJoints, targetJoints); + var samplePeriodSeconds = robot.ServoPeriod.TotalSeconds; + var durationSeconds = AlignDurationToServoStep(requestedDurationSeconds, samplePeriodSeconds); + var denseJointTrajectory = GenerateDenseTrajectory(startJoints, targetJoints, durationSeconds, samplePeriodSeconds); + + var stretchCount = 0; + while (!SatisfiesDiscreteJointLimits(planningRobot, denseJointTrajectory)) + { + stretchCount++; + if (stretchCount > MaxDiscreteLimitStretchIterations) + { + throw new InvalidOperationException("MoveJoint duration cannot be stretched enough to satisfy joint limits."); + } + + // 连续时间律满足限位后,仍以实际离散点为准;不满足时逐周期拉长后重采样。 + durationSeconds = AlignDurationToServoStep(durationSeconds + samplePeriodSeconds, samplePeriodSeconds); + denseJointTrajectory = GenerateDenseTrajectory(startJoints, targetJoints, durationSeconds, samplePeriodSeconds); + } + + logger?.LogDebug( + "MoveJointTrajectoryGenerator: requestedDuration={RequestedDuration:F4}s, duration={Duration:F4}s, speedRatio={SpeedRatio}, samplePeriod={SamplePeriod:F6}s, sampleCount={SampleCount}, stretchCount={StretchCount}", + requestedDurationSeconds, + durationSeconds, + speedRatio, + samplePeriodSeconds, + denseJointTrajectory.Count, + stretchCount); + + return new TrajectoryResult( + programName: "move-joint", + method: PlanningMethod.Doubles, + isValid: true, + duration: TimeSpan.FromSeconds(durationSeconds), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 2, + plannedWaypointCount: denseJointTrajectory.Count, + denseJointTrajectory: denseJointTrajectory); + } + + /// + /// 将执行速度倍率折算为 MoveJoint 规划限位,保持最终稠密轨迹仍按物理 8ms 周期生成。 + /// + private static RobotProfile ApplySpeedRatioToJointLimits(RobotProfile robot, double speedRatio) + { + if (Math.Abs(speedRatio - 1.0) < 1e-12) + { + return robot; + } + + var scaledLimits = robot.JointLimits + .Select(limit => new JointLimit( + limit.JointName, + limit.VelocityLimit * speedRatio, + limit.AccelerationLimit * speedRatio * speedRatio, + limit.JerkLimit * speedRatio * speedRatio * speedRatio)) + .ToArray(); + + return new RobotProfile( + name: robot.Name, + modelPath: robot.ModelPath, + degreesOfFreedom: robot.DegreesOfFreedom, + jointLimits: scaledLimits, + jointCouplings: robot.JointCouplings, + servoPeriod: robot.ServoPeriod, + triggerPeriod: robot.TriggerPeriod); + } + + /// + /// 根据 7 阶平滑点到点时间律和每轴限位,计算 MoveJoint 理论最短时长。 + /// + /// 时间律为 s(u)=35u^4-84u^5+70u^6-20u^7,其中 u=t/T。 + /// 各轴位移 d_i 共用同一个 s(t),所以每轴分别按 d_i 放大速度、加速度和 jerk,再取全局最大时长。 + /// + internal static double ResolveDurationSeconds( + RobotProfile robot, + IReadOnlyList startJoints, + IReadOnlyList targetJoints) + { + var duration = MinimumMoveJointDurationSeconds; + + for (var index = 0; index < robot.DegreesOfFreedom; index++) + { + var distance = Math.Abs(targetJoints[index] - startJoints[index]); + if (distance <= 0.0) + { + continue; + } + + var limit = robot.JointLimits[index]; + var velocityDuration = distance * SmoothPtpVelocityShapeCoefficient / limit.VelocityLimit; + var accelerationDuration = Math.Sqrt(distance * SmoothPtpAccelerationShapeCoefficient / limit.AccelerationLimit); + var jerkDuration = Math.Cbrt(distance * SmoothPtpJerkShapeCoefficient / limit.JerkLimit); + + duration = Math.Max(duration, Math.Max(velocityDuration, Math.Max(accelerationDuration, jerkDuration))); + } + + return duration; + } + + /// + /// 将请求时长向上对齐到整数个采样周期,确保轨迹末帧正好落在 duration 处。 + /// + /// 请求的理论最短时长(秒)。 + /// 采样周期(秒)。 + /// 对齐后的时长,为 samplePeriodSeconds 的整数倍。 + internal static double AlignDurationToServoStep(double durationSeconds, double samplePeriodSeconds) + { + if (samplePeriodSeconds <= 0.0 || double.IsNaN(samplePeriodSeconds) || double.IsInfinity(samplePeriodSeconds)) + { + throw new InvalidOperationException("Speed ratio must be greater than zero for MoveJoint execution."); + } + + var intervals = ResolveSampleIntervalCount(durationSeconds, samplePeriodSeconds); + + return intervals * samplePeriodSeconds; + } + + /// + /// 生成从起始关节角到目标关节角的稠密等时间隔轨迹点序列。 + /// + /// 每行格式:[time_seconds, joint_0, joint_1, ..., joint_n-1]。 + /// + internal static IReadOnlyList> GenerateDenseTrajectory( + IReadOnlyList startJoints, + IReadOnlyList targetJoints, + double durationSeconds, + double samplePeriodSeconds) + { + var sampleCount = ResolveSampleIntervalCount(durationSeconds, samplePeriodSeconds) + 1; + var rows = new List>(checked((int)sampleCount)); + + for (var index = 0L; index < sampleCount; index++) + { + var time = Math.Min(index * samplePeriodSeconds, durationSeconds); + rows.Add(CreateRow(time, durationSeconds, startJoints, targetJoints)); + } + + return rows; + } + + /// + /// 计算 7 阶平滑点到点时间律的位置归一化值。 + /// + /// 归一化时间 u,取值会被限制在 [0, 1]。 + /// 归一化位置 s(u),范围 [0, 1]。 + internal static double EvaluateSmoothPtpPositionScale(double normalizedTime) + { + var clamped = Math.Clamp(normalizedTime, 0.0, 1.0); + var u2 = clamped * clamped; + var u4 = u2 * u2; + + return u4 * (35.0 + (clamped * (-84.0 + (clamped * (70.0 - (20.0 * clamped)))))); + } + + /// + /// 计算时长对应的采样间隔数(向上取整)。 + /// 采样间隔数 + 1 = 采样点数,因为轨迹包含起点和终点。 + /// + private static long ResolveSampleIntervalCount(double durationSeconds, double samplePeriodSeconds) + { + var rawIntervals = durationSeconds / samplePeriodSeconds; + if (double.IsNaN(rawIntervals) || double.IsInfinity(rawIntervals)) + { + throw new InvalidOperationException("MoveJoint sample count is not representable."); + } + + var intervals = (long)Math.Ceiling(Math.Max(0.0, rawIntervals) - 1e-9); + intervals = Math.Max(1, intervals); + if (intervals + 1 > MaxMoveJointSampleCount) + { + throw new InvalidOperationException($"MoveJoint sample count must be between 2 and {MaxMoveJointSampleCount}."); + } + + return intervals; + } + + /// + /// 构造单个轨迹行:[time_seconds, joint_0, ..., joint_N-1]。 + /// + private static IReadOnlyList CreateRow( + double timeSeconds, + double durationSeconds, + IReadOnlyList startJoints, + IReadOnlyList targetJoints) + { + var u = durationSeconds <= 0.0 ? 1.0 : Math.Clamp(timeSeconds / durationSeconds, 0.0, 1.0); + var scale = EvaluateSmoothPtpPositionScale(u); + var row = new double[startJoints.Count + 1]; + row[0] = Math.Round(timeSeconds, 9); + + for (var index = 0; index < startJoints.Count; index++) + { + row[index + 1] = startJoints[index] + ((targetJoints[index] - startJoints[index]) * scale); + } + + return row; + } + + /// + /// 用生成后的离散采样点复核每轴速度、加速度和 jerk,避免连续时间律在采样后仍出现差分越限。 + /// + private static bool SatisfiesDiscreteJointLimits(RobotProfile robot, IReadOnlyList> rows) + { + double? previousTime = null; + double[]? previousPositions = null; + double[]? previousVelocities = null; + double[]? previousAccelerations = null; + + foreach (var row in rows) + { + var currentTime = row[0]; + var currentPositions = new double[robot.DegreesOfFreedom]; + for (var index = 0; index < robot.DegreesOfFreedom; index++) + { + currentPositions[index] = row[index + 1]; + } + + if (previousTime is not null && previousPositions is not null) + { + var dt = currentTime - previousTime.Value; + if (dt <= 0.0) + { + throw new InvalidOperationException("MoveJoint dense trajectory timestamps must be strictly increasing."); + } + + var velocities = new double[robot.DegreesOfFreedom]; + var accelerations = new double[robot.DegreesOfFreedom]; + for (var index = 0; index < robot.DegreesOfFreedom; index++) + { + var limit = robot.JointLimits[index]; + velocities[index] = (currentPositions[index] - previousPositions[index]) / dt; + if (Math.Abs(velocities[index]) > limit.VelocityLimit * DiscreteLimitTolerance) + { + return false; + } + + accelerations[index] = previousVelocities is null + ? 0.0 + : (velocities[index] - previousVelocities[index]) / dt; + if (Math.Abs(accelerations[index]) > limit.AccelerationLimit * DiscreteLimitTolerance) + { + return false; + } + + if (previousAccelerations is not null) + { + var jerk = (accelerations[index] - previousAccelerations[index]) / dt; + if (Math.Abs(jerk) > limit.JerkLimit * DiscreteLimitTolerance) + { + return false; + } + } + } + + previousVelocities = velocities; + previousAccelerations = accelerations; + } + + previousTime = currentTime; + previousPositions = currentPositions; + } + + return true; + } +} diff --git a/src/Flyshot.ControllerClientCompat/MovePoseTrajectoryGenerator.cs b/src/Flyshot.ControllerClientCompat/MovePoseTrajectoryGenerator.cs new file mode 100644 index 0000000..4a148cc --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/MovePoseTrajectoryGenerator.cs @@ -0,0 +1,353 @@ +using Flyshot.Core.Domain; +using Microsoft.Extensions.Logging; + +namespace Flyshot.ControllerClientCompat; + +/// +/// MovePose 直角坐标轨迹生成器。 +/// 将起始 TCP 位姿到目标 TCP 位姿的单段运动,按保守直角速度、加速度和 jerk 限制生成 8ms 稠密点。 +/// +internal static class MovePoseTrajectoryGenerator +{ + /// + /// 直角位置轴最大速度,单位为 mm/s。 + /// + private const double LinearVelocityLimit = 50.0; + + /// + /// 直角位置轴最大加速度,单位为 mm/s^2。 + /// + private const double LinearAccelerationLimit = 250.0; + + /// + /// 直角位置轴最大 jerk,单位为 mm/s^3。 + /// + private const double LinearJerkLimit = 1250.0; + + /// + /// 姿态轴最大速度,单位为 deg/s。 + /// + private const double AngularVelocityLimit = 10.0; + + /// + /// 姿态轴最大加速度,单位为 deg/s^2。 + /// + private const double AngularAccelerationLimit = 50.0; + + /// + /// 姿态轴最大 jerk,单位为 deg/s^3。 + /// + private const double AngularJerkLimit = 250.0; + + /// + /// 7 阶平滑点到点时间律的一阶导数最大值。 + /// + private const double SmoothPtpVelocityShapeCoefficient = 2.1875; + + /// + /// 7 阶平滑点到点时间律的二阶导数最大值。 + /// + private const double SmoothPtpAccelerationShapeCoefficient = 7.513188404399293; + + /// + /// 7 阶平滑点到点时间律的三阶导数最大值。 + /// + private const double SmoothPtpJerkShapeCoefficient = 52.5; + + /// + /// 单次 MovePose 最大采样点数上限,避免极低速度倍率生成过大的队列。 + /// + private const int MaxMovePoseSampleCount = 1_000_000; + + /// + /// 离散限位校验允许的浮点容差。 + /// + private const double DiscreteLimitTolerance = 1.000001; + + /// + /// 离散限位校验失败时最多拉长的采样周期次数。 + /// + private const int MaxDiscreteLimitStretchIterations = 10_000; + + /// + /// 计算 MovePose 轨迹的完整结果。 + /// + /// 起始位姿 [x,y,z,w,p,r],单位为 mm/deg。 + /// 目标位姿 [x,y,z,w,p,r],单位为 mm/deg。 + /// J519 伺服发送周期。 + /// 速度倍率,必须大于 0。 + /// 可选诊断日志。 + /// 包含稠密直角轨迹的规划结果。 + public static TrajectoryResult CreateResult( + IReadOnlyList startPose, + IReadOnlyList targetPose, + TimeSpan servoPeriod, + double speedRatio, + ILogger? logger = null) + { + ArgumentNullException.ThrowIfNull(startPose); + ArgumentNullException.ThrowIfNull(targetPose); + EnsurePoseVector(startPose, nameof(startPose)); + EnsurePoseVector(targetPose, nameof(targetPose)); + + if (speedRatio <= 0.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio)) + { + throw new InvalidOperationException("Speed ratio must be greater than zero for MovePose execution."); + } + + var samplePeriodSeconds = servoPeriod.TotalSeconds; + if (samplePeriodSeconds <= 0.0 || double.IsNaN(samplePeriodSeconds) || double.IsInfinity(samplePeriodSeconds)) + { + throw new InvalidOperationException("MovePose servo period must be a finite positive duration."); + } + + var requestedDurationSeconds = ResolveDurationSeconds(startPose, targetPose, speedRatio); + var durationSeconds = AlignDurationToServoStep(requestedDurationSeconds, samplePeriodSeconds); + var denseCartesianTrajectory = GenerateDenseTrajectory(startPose, targetPose, durationSeconds, samplePeriodSeconds); + + var stretchCount = 0; + while (!SatisfiesDefaultCartesianLimits(denseCartesianTrajectory, speedRatio)) + { + stretchCount++; + if (stretchCount > MaxDiscreteLimitStretchIterations) + { + throw new InvalidOperationException("MovePose duration cannot be stretched enough to satisfy Cartesian limits."); + } + + // 离散差分以真实下发点为准;不满足时逐周期拉长后重采样。 + durationSeconds = AlignDurationToServoStep(durationSeconds + samplePeriodSeconds, samplePeriodSeconds); + denseCartesianTrajectory = GenerateDenseTrajectory(startPose, targetPose, durationSeconds, samplePeriodSeconds); + } + + logger?.LogDebug( + "MovePoseTrajectoryGenerator: requestedDuration={RequestedDuration:F4}s, duration={Duration:F4}s, speedRatio={SpeedRatio}, samplePeriod={SamplePeriod:F6}s, sampleCount={SampleCount}, stretchCount={StretchCount}", + requestedDurationSeconds, + durationSeconds, + speedRatio, + samplePeriodSeconds, + denseCartesianTrajectory.Count, + stretchCount); + + return new TrajectoryResult( + programName: "move-pose", + method: PlanningMethod.Doubles, + isValid: true, + duration: TimeSpan.FromSeconds(durationSeconds), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 2, + plannedWaypointCount: denseCartesianTrajectory.Count, + denseCartesianTrajectory: denseCartesianTrajectory); + } + + /// + /// 检查稠密直角轨迹是否满足默认六维速度、加速度和 jerk 限制。 + /// + /// 稠密轨迹行,每行为 [time,x,y,z,w,p,r]。 + /// 生成该轨迹时使用的速度倍率。 + /// 满足限制时返回 true,否则返回 false。 + public static bool SatisfiesDefaultCartesianLimits(IReadOnlyList> rows, double speedRatio) + { + if (speedRatio <= 0.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio)) + { + throw new InvalidOperationException("Speed ratio must be greater than zero for MovePose limit validation."); + } + + double? previousTime = null; + double[]? previousPositions = null; + double[]? previousVelocities = null; + double[]? previousAccelerations = null; + + foreach (var row in rows) + { + if (row.Count != 7) + { + throw new InvalidOperationException("MovePose dense trajectory rows must contain time plus six pose values."); + } + + var currentTime = row[0]; + var currentPositions = row.Skip(1).Take(6).ToArray(); + + if (previousTime is not null && previousPositions is not null) + { + var dt = currentTime - previousTime.Value; + if (dt <= 0.0) + { + throw new InvalidOperationException("MovePose dense trajectory timestamps must be strictly increasing."); + } + + var velocities = new double[6]; + var accelerations = new double[6]; + for (var index = 0; index < 6; index++) + { + var limits = GetAxisLimits(index, speedRatio); + velocities[index] = (currentPositions[index] - previousPositions[index]) / dt; + if (Math.Abs(velocities[index]) > limits.Velocity * DiscreteLimitTolerance) + { + return false; + } + + accelerations[index] = previousVelocities is null + ? 0.0 + : (velocities[index] - previousVelocities[index]) / dt; + if (Math.Abs(accelerations[index]) > limits.Acceleration * DiscreteLimitTolerance) + { + return false; + } + + if (previousAccelerations is not null) + { + var jerk = (accelerations[index] - previousAccelerations[index]) / dt; + if (Math.Abs(jerk) > limits.Jerk * DiscreteLimitTolerance) + { + return false; + } + } + } + + previousVelocities = velocities; + previousAccelerations = accelerations; + } + + previousTime = currentTime; + previousPositions = currentPositions; + } + + return true; + } + + /// + /// 根据 7 阶平滑点到点时间律和直角六维限制计算理论最短时长。 + /// + private static double ResolveDurationSeconds(IReadOnlyList startPose, IReadOnlyList targetPose, double speedRatio) + { + var duration = 0.0; + for (var index = 0; index < 6; index++) + { + var distance = Math.Abs(targetPose[index] - startPose[index]); + if (distance <= 0.0) + { + continue; + } + + var limits = GetAxisLimits(index, speedRatio); + var velocityDuration = distance * SmoothPtpVelocityShapeCoefficient / limits.Velocity; + var accelerationDuration = Math.Sqrt(distance * SmoothPtpAccelerationShapeCoefficient / limits.Acceleration); + var jerkDuration = Math.Cbrt(distance * SmoothPtpJerkShapeCoefficient / limits.Jerk); + duration = Math.Max(duration, Math.Max(velocityDuration, Math.Max(accelerationDuration, jerkDuration))); + } + + return duration; + } + + /// + /// 生成从起始位姿到目标位姿的稠密等时间隔直角轨迹点序列。 + /// + private static IReadOnlyList> GenerateDenseTrajectory( + IReadOnlyList startPose, + IReadOnlyList targetPose, + double durationSeconds, + double samplePeriodSeconds) + { + var sampleCount = ResolveSampleIntervalCount(durationSeconds, samplePeriodSeconds) + 1; + var rows = new List>(checked((int)sampleCount)); + + for (var index = 0L; index < sampleCount; index++) + { + var time = Math.Min(index * samplePeriodSeconds, durationSeconds); + rows.Add(CreateRow(time, durationSeconds, startPose, targetPose)); + } + + return rows; + } + + /// + /// 将请求时长向上对齐到整数个伺服周期。 + /// + private static double AlignDurationToServoStep(double durationSeconds, double samplePeriodSeconds) + { + return ResolveSampleIntervalCount(durationSeconds, samplePeriodSeconds) * samplePeriodSeconds; + } + + /// + /// 计算时长对应的采样间隔数,轨迹至少包含起点和终点两帧。 + /// + private static long ResolveSampleIntervalCount(double durationSeconds, double samplePeriodSeconds) + { + var rawIntervals = durationSeconds / samplePeriodSeconds; + if (double.IsNaN(rawIntervals) || double.IsInfinity(rawIntervals)) + { + throw new InvalidOperationException("MovePose sample count is not representable."); + } + + var intervals = (long)Math.Ceiling(Math.Max(0.0, rawIntervals) - 1e-9); + intervals = Math.Max(1, intervals); + if (intervals + 1 > MaxMovePoseSampleCount) + { + throw new InvalidOperationException($"MovePose sample count must be between 2 and {MaxMovePoseSampleCount}."); + } + + return intervals; + } + + /// + /// 构造单个轨迹行:[time_seconds,x,y,z,w,p,r]。 + /// + private static IReadOnlyList CreateRow( + double timeSeconds, + double durationSeconds, + IReadOnlyList startPose, + IReadOnlyList targetPose) + { + var u = durationSeconds <= 0.0 ? 1.0 : Math.Clamp(timeSeconds / durationSeconds, 0.0, 1.0); + var scale = MoveJointTrajectoryGenerator.EvaluateSmoothPtpPositionScale(u); + var row = new double[7]; + row[0] = Math.Round(timeSeconds, 9); + + for (var index = 0; index < 6; index++) + { + row[index + 1] = startPose[index] + ((targetPose[index] - startPose[index]) * scale); + } + + return row; + } + + /// + /// 获取指定直角轴在当前速度倍率下的有效限制。 + /// + private static CartesianAxisLimit GetAxisLimits(int index, double speedRatio) + { + var linearAxis = index < 3; + var velocity = linearAxis ? LinearVelocityLimit : AngularVelocityLimit; + var acceleration = linearAxis ? LinearAccelerationLimit : AngularAccelerationLimit; + var jerk = linearAxis ? LinearJerkLimit : AngularJerkLimit; + return new CartesianAxisLimit( + velocity * speedRatio, + acceleration * speedRatio * speedRatio, + jerk * speedRatio * speedRatio * speedRatio); + } + + /// + /// 校验位姿向量必须为六维有限数。 + /// + private static void EnsurePoseVector(IReadOnlyList pose, string parameterName) + { + if (pose.Count != 6) + { + throw new ArgumentException("MovePose expects pose [x,y,z,w,p,r].", parameterName); + } + + if (pose.Any(static value => double.IsNaN(value) || double.IsInfinity(value))) + { + throw new ArgumentException("MovePose pose values must be finite.", parameterName); + } + } + + /// + /// 表示单个直角轴的有效速度、加速度和 jerk 限制。 + /// + private readonly record struct CartesianAxisLimit(double Velocity, double Acceleration, double Jerk); +} diff --git a/src/Flyshot.ControllerClientCompat/PlannedExecutionBundle.cs b/src/Flyshot.ControllerClientCompat/PlannedExecutionBundle.cs new file mode 100644 index 0000000..e43772e --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/PlannedExecutionBundle.cs @@ -0,0 +1,58 @@ +using Flyshot.Core.Domain; +using Flyshot.Core.Planning; +using Flyshot.Core.Triggering; + +namespace Flyshot.ControllerClientCompat; + +/// +/// 表示兼容层执行轨迹前生成的完整规划结果包。 +/// +public sealed class PlannedExecutionBundle +{ + /// + /// 初始化一份执行规划结果包。 + /// + /// 规划后的原始连续轨迹。 + /// 飞拍触发时间轴。 + /// 对运行时和监控层暴露的规划结果。 + /// 飞拍链路预先准备好的最终发送队列。 + /// 与运行时结果同口径的连续执行轨迹;为空时复用 plannedTrajectory。 + public PlannedExecutionBundle( + PlannedTrajectory plannedTrajectory, + ShotTimeline shotTimeline, + TrajectoryResult result, + FlyshotPreparedExecution? preparedExecution = null, + PlannedTrajectory? executionTrajectory = null) + { + PlannedTrajectory = plannedTrajectory ?? throw new ArgumentNullException(nameof(plannedTrajectory)); + ShotTimeline = shotTimeline ?? throw new ArgumentNullException(nameof(shotTimeline)); + Result = result ?? throw new ArgumentNullException(nameof(result)); + PreparedExecution = preparedExecution; + ExecutionTrajectory = executionTrajectory ?? plannedTrajectory; + } + + /// + /// 获取规划后的原始连续轨迹。 + /// + public PlannedTrajectory PlannedTrajectory { get; } + + /// + /// 获取与运行时结果同口径的连续执行轨迹,用于构建最终 8ms 发送队列。 + /// + public PlannedTrajectory ExecutionTrajectory { get; } + + /// + /// 获取飞拍触发时间轴。 + /// + public ShotTimeline ShotTimeline { get; } + + /// + /// 获取运行时可消费的规划结果。 + /// + public TrajectoryResult Result { get; } + + /// + /// 获取飞拍链路预先准备好的最终发送队列;普通轨迹与 move_joint 为 null。 + /// + public FlyshotPreparedExecution? PreparedExecution { get; } +} diff --git a/src/Flyshot.ControllerClientCompat/TrajectoryExecutionOptions.cs b/src/Flyshot.ControllerClientCompat/TrajectoryExecutionOptions.cs new file mode 100644 index 0000000..eb02bcd --- /dev/null +++ b/src/Flyshot.ControllerClientCompat/TrajectoryExecutionOptions.cs @@ -0,0 +1,28 @@ +namespace Flyshot.ControllerClientCompat; + +/// +/// 表示普通轨迹执行接口的可选参数,字段名对齐旧 `ControllerClient::ExecuteTrajectory`。 +/// +public sealed class TrajectoryExecutionOptions +{ + /// + /// 初始化普通轨迹执行参数。 + /// + /// 轨迹生成方法,支持 `icsp` 或 `doubles`。 + /// 是否保存轨迹信息。 + public TrajectoryExecutionOptions(string method = "icsp", bool saveTrajectory = false) + { + Method = string.IsNullOrWhiteSpace(method) ? "icsp" : method; + SaveTrajectory = saveTrajectory; + } + + /// + /// 获取轨迹生成方法。 + /// + public string Method { get; } + + /// + /// 获取是否保存轨迹信息。 + /// + public bool SaveTrajectory { get; } +} diff --git a/src/Flyshot.Core.Config/Flyshot.Core.Config.csproj b/src/Flyshot.Core.Config/Flyshot.Core.Config.csproj index 1ccc45d..41216c6 100644 --- a/src/Flyshot.Core.Config/Flyshot.Core.Config.csproj +++ b/src/Flyshot.Core.Config/Flyshot.Core.Config.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Flyshot.Core.Config/LoadedRobotModel.cs b/src/Flyshot.Core.Config/LoadedRobotModel.cs new file mode 100644 index 0000000..9a78086 --- /dev/null +++ b/src/Flyshot.Core.Config/LoadedRobotModel.cs @@ -0,0 +1,30 @@ +using Flyshot.Core.Domain; + +namespace Flyshot.Core.Config; + +/// +/// 表示一次 JSON 模型解析后生成的完整机器人模型视图集合。 +/// +public sealed class LoadedRobotModel +{ + /// + /// 初始化完整机器人模型视图集合。 + /// + /// 规划和运行时使用的关节约束视图。 + /// 正运动学导出使用的几何链视图。 + public LoadedRobotModel(RobotProfile profile, RobotKinematicsModel kinematicsModel) + { + Profile = profile ?? throw new ArgumentNullException(nameof(profile)); + KinematicsModel = kinematicsModel ?? throw new ArgumentNullException(nameof(kinematicsModel)); + } + + /// + /// 获取规划和运行时使用的关节约束视图。 + /// + public RobotProfile Profile { get; } + + /// + /// 获取正运动学导出使用的几何链视图。 + /// + public RobotKinematicsModel KinematicsModel { get; } +} diff --git a/src/Flyshot.Core.Config/PathCompatibility.cs b/src/Flyshot.Core.Config/PathCompatibility.cs index 18a95ef..035d478 100644 --- a/src/Flyshot.Core.Config/PathCompatibility.cs +++ b/src/Flyshot.Core.Config/PathCompatibility.cs @@ -22,7 +22,7 @@ public enum CompatibilityPathStyle public static class PathCompatibility { /// - /// 按旧系统常见目录约定解析配置文件路径。 + /// 按当前服务配置目录约定解析配置文件路径。 /// /// 调用方传入的原始配置路径。 /// 当前兼容搜索的仓库根目录。 @@ -48,11 +48,10 @@ public static class PathCompatibility } var normalizedRepoRoot = Path.GetFullPath(repoRoot); - var fileName = Path.GetFileName(rawPath); var checkedPaths = new List(); - // 先按最常见的候选路径顺序尝试,保持与旧工具链相近的定位逻辑。 - foreach (var candidate in BuildConfigCandidates(normalizedRepoRoot, rawPath, fileName)) + // 相对路径只允许落在当前服务根目录的 Config 下,避免隐式回退到父工作区旧文件。 + foreach (var candidate in BuildConfigCandidates(normalizedRepoRoot, rawPath)) { var fullCandidate = Path.GetFullPath(candidate); if (checkedPaths.Contains(fullCandidate, StringComparer.OrdinalIgnoreCase)) @@ -67,18 +66,6 @@ public static class PathCompatibility } } - // 最后一层兜底按文件名全仓库搜索,但只接受唯一命中,避免同名配置误判。 - var matches = Directory - .EnumerateFiles(normalizedRepoRoot, fileName, SearchOption.AllDirectories) - .Select(Path.GetFullPath) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray(); - - if (matches.Length == 1) - { - return matches[0]; - } - throw new FileNotFoundException( $"未找到配置文件 '{configPath}'。已检查: {string.Join(", ", checkedPaths)}", configPath); @@ -106,15 +93,11 @@ public static class PathCompatibility } /// - /// 枚举旧系统中最常见的配置候选路径。 + /// 枚举当前服务配置目录下允许的配置候选路径。 /// - private static IEnumerable BuildConfigCandidates(string repoRoot, string rawPath, string fileName) + private static IEnumerable BuildConfigCandidates(string repoRoot, string rawPath) { - yield return Path.Combine(repoRoot, rawPath); - yield return Path.Combine(repoRoot, "Rvbust", "Data", fileName); - yield return Path.Combine(repoRoot, "Rvbust", "Install", "FlyingShot", "Config", fileName); - yield return Path.Combine(repoRoot, "Rvbust", fileName); - yield return Path.Combine(repoRoot, fileName); + yield return Path.Combine(repoRoot, "Config", rawPath); } /// diff --git a/src/Flyshot.Core.Config/RobotConfigLoader.cs b/src/Flyshot.Core.Config/RobotConfigLoader.cs index a21906f..5b4bffd 100644 --- a/src/Flyshot.Core.Config/RobotConfigLoader.cs +++ b/src/Flyshot.Core.Config/RobotConfigLoader.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Flyshot.Core.Domain; +using Microsoft.Extensions.Logging; namespace Flyshot.Core.Config; @@ -15,9 +16,13 @@ public sealed class CompatibilityRobotSettings bool useDo, IEnumerable ioAddresses, int ioKeepCycles, + int triggerSampleIndexOffsetCycles, double accLimitScale, double jerkLimitScale, - int adaptIcspTryNum) + int adaptIcspTryNum, + double planningSpeedScale = 1.0, + double speedRatio = 1.0, + bool smoothStartStopTiming = true) { ArgumentNullException.ThrowIfNull(ioAddresses); @@ -26,6 +31,11 @@ public sealed class CompatibilityRobotSettings throw new ArgumentOutOfRangeException(nameof(ioKeepCycles), "IO 保持周期不能为负数。"); } + if (triggerSampleIndexOffsetCycles < 0) + { + throw new ArgumentOutOfRangeException(nameof(triggerSampleIndexOffsetCycles), "触发 sample 偏移周期不能为负数。"); + } + if (accLimitScale <= 0.0) { throw new ArgumentOutOfRangeException(nameof(accLimitScale), "加速度倍率必须大于 0。"); @@ -36,6 +46,16 @@ public sealed class CompatibilityRobotSettings throw new ArgumentOutOfRangeException(nameof(jerkLimitScale), "Jerk 倍率必须大于 0。"); } + if (planningSpeedScale <= 0.0 || double.IsNaN(planningSpeedScale) || double.IsInfinity(planningSpeedScale)) + { + throw new ArgumentOutOfRangeException(nameof(planningSpeedScale), "规划速度倍率必须是有限正数。"); + } + + if (speedRatio <= 0.0 || speedRatio > 1.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(speedRatio), "运行时速度倍率必须在 (0, 1] 范围内。"); + } + if (adaptIcspTryNum < 0) { throw new ArgumentOutOfRangeException(nameof(adaptIcspTryNum), "补点尝试次数不能为负数。"); @@ -51,9 +71,13 @@ public sealed class CompatibilityRobotSettings UseDo = useDo; IoAddresses = copiedIoAddresses; IoKeepCycles = ioKeepCycles; + TriggerSampleIndexOffsetCycles = triggerSampleIndexOffsetCycles; AccLimitScale = accLimitScale; JerkLimitScale = jerkLimitScale; AdaptIcspTryNum = adaptIcspTryNum; + PlanningSpeedScale = planningSpeedScale; + SpeedRatio = speedRatio; + SmoothStartStopTiming = smoothStartStopTiming; } /// @@ -71,6 +95,12 @@ public sealed class CompatibilityRobotSettings /// public int IoKeepCycles { get; } + /// + /// 获取触发绑定到最佳 sample 后,还要再向后偏移的命令周期数。 + /// 该值作用在 J519 命令 sample 时间轴,不是 60015 状态反馈周期。 + /// + public int TriggerSampleIndexOffsetCycles { get; } + /// /// 获取加速度全局倍率。 /// @@ -81,6 +111,21 @@ public sealed class CompatibilityRobotSettings /// public double JerkLimitScale { get; } + /// + /// 获取规划阶段的全局速度倍率,只影响 JointTraj 基准时间,不等同于运行时 J519 下发速度倍率。 + /// + public double PlanningSpeedScale { get; } + + /// + /// 获取运行时默认速度倍率,会在 SetUpRobot 时同步到控制器运行时,供 MoveJoint 和后续执行链路使用。 + /// + public double SpeedRatio { get; } + + /// + /// 获取是否在飞拍执行前对整段时间轴做二次平滑起停重映射。 + /// + public bool SmoothStartStopTiming { get; } + /// /// 获取自适应补点最大尝试次数。 /// @@ -131,6 +176,17 @@ public sealed class LoadedRobotConfig /// public sealed class RobotConfigLoader { + private readonly ILogger? _logger; + + /// + /// 初始化 RobotConfigLoader。 + /// + /// 日志记录器;允许 null。 + public RobotConfigLoader(ILogger? logger = null) + { + _logger = logger; + } + /// /// 加载一份旧版 RobotConfig.json。 /// @@ -139,6 +195,8 @@ public sealed class RobotConfigLoader /// 规范化后的配置文档。 public LoadedRobotConfig Load(string configPath, string? repoRoot = null) { + _logger?.LogInformation("RobotConfig 开始加载: configPath={ConfigPath}, repoRoot={RepoRoot}", configPath, repoRoot); + var resolvedRepoRoot = ResolveRepoRoot(repoRoot); var resolvedConfigPath = PathCompatibility.ResolveConfigPath(configPath, resolvedRepoRoot); @@ -151,9 +209,13 @@ public sealed class RobotConfigLoader useDo: ReadBoolean(robotElement, "use_do", defaultValue: false), ioAddresses: ReadIntArray(robotElement, "io_addr"), ioKeepCycles: ReadInt(robotElement, "io_keep_cycles", defaultValue: 0), + triggerSampleIndexOffsetCycles: ReadInt(robotElement, "trigger_sample_index_offset_cycles", defaultValue: 7), accLimitScale: ReadDouble(robotElement, "acc_limit", defaultValue: 1.0), jerkLimitScale: ReadDouble(robotElement, "jerk_limit", defaultValue: 1.0), - adaptIcspTryNum: ReadInt(robotElement, "adapt_icsp_try_num", defaultValue: 0)); + adaptIcspTryNum: ReadInt(robotElement, "adapt_icsp_try_num", defaultValue: 0), + planningSpeedScale: ReadDouble(robotElement, "planning_speed_scale", defaultValue: 1.0), + speedRatio: ReadDouble(robotElement, "speed_ratio", defaultValue: 1.0), + smoothStartStopTiming: ReadBoolean(robotElement, "smooth_start_stop_timing", defaultValue: true)); var programs = new Dictionary(StringComparer.Ordinal); foreach (var programElement in flyingShotsElement.EnumerateObject()) @@ -163,6 +225,10 @@ public sealed class RobotConfigLoader programs.Add(programName, program); } + _logger?.LogInformation( + "RobotConfig 加载完成: resolvedPath={ResolvedPath}, useDo={UseDo}, ioKeepCycles={IoKeepCycles}, accLimit={AccLimit}, jerkLimit={JerkLimit}, planningSpeedScale={PlanningSpeedScale}, speedRatio={SpeedRatio}, smoothStartStopTiming={SmoothStartStopTiming}, adaptIcspTryNum={AdaptIcspTryNum}, 程序数={ProgramCount}", + resolvedConfigPath, robot.UseDo, robot.IoKeepCycles, robot.AccLimitScale, robot.JerkLimitScale, robot.PlanningSpeedScale, robot.SpeedRatio, robot.SmoothStartStopTiming, robot.AdaptIcspTryNum, programs.Count); + return new LoadedRobotConfig( sourcePath: resolvedConfigPath, robot: robot, @@ -253,7 +319,7 @@ public sealed class RobotConfigLoader } /// - /// 推断仓库根目录,优先使用调用方显式传入的值。 + /// 推断当前 replacement 仓库根目录,优先使用调用方显式传入的值。 /// private static string ResolveRepoRoot(string? repoRoot) { @@ -267,7 +333,7 @@ public sealed class RobotConfigLoader { if (File.Exists(Path.Combine(current.FullName, "FlyshotReplacement.sln"))) { - return Path.GetFullPath(Path.Combine(current.FullName, "..")); + return current.FullName; } current = current.Parent; diff --git a/src/Flyshot.Core.Config/RobotModelLoader.cs b/src/Flyshot.Core.Config/RobotModelLoader.cs index 06771ad..638a81d 100644 --- a/src/Flyshot.Core.Config/RobotModelLoader.cs +++ b/src/Flyshot.Core.Config/RobotModelLoader.cs @@ -1,28 +1,49 @@ -using System.Text; using System.Text.Json; using Flyshot.Core.Domain; +using Microsoft.Extensions.Logging; namespace Flyshot.Core.Config; /// -/// 从旧版 .robot(GLB) 文件中提取关节限制、模型名和 couple 元数据。 +/// 从现场固化的机器人 JSON 模型中提取关节限制、几何链和 couple 元数据。 /// public sealed class RobotModelLoader { - private const uint JsonChunkType = 0x4E4F534A; + private readonly ILogger? _logger; /// - /// 加载 .robot 文件并生成规划侧可直接消费的 RobotProfile。 + /// 初始化 RobotModelLoader。 /// - /// .robot 文件路径。 + /// 日志记录器;允许 null。 + public RobotModelLoader(ILogger? logger = null) + { + _logger = logger; + } + + /// + /// 加载机器人 JSON 文件并生成规划侧可直接消费的 RobotProfile。 + /// + /// 机器人 JSON 文件路径。 /// 加速度全局倍率。 /// Jerk 全局倍率。 /// 包含关节限制和 couple 信息的 RobotProfile。 public RobotProfile LoadProfile(string modelPath, double accLimitScale = 1.0, double jerkLimitScale = 1.0) + { + return LoadProfileAndKinematics(modelPath, accLimitScale, jerkLimitScale).Profile; + } + + /// + /// 加载机器人 JSON 文件并一次性生成规划约束视图与运动学几何视图。 + /// + /// 机器人 JSON 文件路径。 + /// 加速度全局倍率。 + /// Jerk 全局倍率。 + /// 包含规划约束视图和运动学几何视图的加载结果。 + public LoadedRobotModel LoadProfileAndKinematics(string modelPath, double accLimitScale = 1.0, double jerkLimitScale = 1.0) { if (string.IsNullOrWhiteSpace(modelPath)) { - throw new ArgumentException(".robot 路径不能为空。", nameof(modelPath)); + throw new ArgumentException("机器人 JSON 路径不能为空。", nameof(modelPath)); } if (accLimitScale <= 0.0) @@ -35,15 +56,72 @@ public sealed class RobotModelLoader throw new ArgumentOutOfRangeException(nameof(jerkLimitScale), "Jerk 倍率必须大于 0。"); } + _logger?.LogInformation("RobotModel JSON 开始加载: modelPath={ModelPath}, accLimitScale={AccLimitScale}, jerkLimitScale={JerkLimitScale}", modelPath, accLimitScale, jerkLimitScale); + var resolvedModelPath = Path.GetFullPath(modelPath); - var jsonText = ReadJsonChunk(resolvedModelPath); - using var document = JsonDocument.Parse(jsonText); + using var document = JsonDocument.Parse(File.ReadAllText(resolvedModelPath)); var robotBody = FindRobotBody(document.RootElement); var profileName = robotBody.TryGetProperty("name", out var nameElement) ? nameElement.GetString() ?? Path.GetFileNameWithoutExtension(resolvedModelPath) : Path.GetFileNameWithoutExtension(resolvedModelPath); + var profile = BuildProfile(robotBody, profileName, resolvedModelPath, accLimitScale, jerkLimitScale); + var kinematicsModel = BuildKinematicsModel(robotBody, profileName); + + _logger?.LogInformation( + "RobotModel JSON 加载完成: profileName={ProfileName}, dof={Dof}, 几何关节数={JointCount}, resolvedPath={ResolvedPath}", + profile.Name, profile.DegreesOfFreedom, kinematicsModel.Joints.Count, resolvedModelPath); + + return new LoadedRobotModel(profile, kinematicsModel); + } + + /// + /// 加载机器人 JSON 文件并生成运动学侧需要的完整几何模型。 + /// + /// 机器人 JSON 文件路径。 + /// 包含完整关节几何链的运动学模型。 + public RobotKinematicsModel LoadKinematicsModel(string modelPath) + { + return LoadProfileAndKinematics(modelPath).KinematicsModel; + } + + /// + /// 在 robotics.bodies 中找到当前现场机器人主体。 + /// + private static JsonElement FindRobotBody(JsonElement root) + { + var bodies = root + .GetProperty("scenes")[0] + .GetProperty("extras") + .GetProperty("rvbust") + .GetProperty("robotics") + .GetProperty("bodies"); + + foreach (var body in bodies.EnumerateArray()) + { + if (body.TryGetProperty("type", out var typeElement) && typeElement.GetInt32() == 2) + { + return body; + } + } + + foreach (var body in bodies.EnumerateArray()) + { + if (body.TryGetProperty("joints", out _) && body.TryGetProperty("name", out _)) + { + return body; + } + } + + throw new InvalidDataException("未在机器人 JSON 中找到包含 joints 的机器人主体。"); + } + + /// + /// 从机器人主体构造规划约束视图。 + /// + private RobotProfile BuildProfile(JsonElement robotBody, string profileName, string resolvedModelPath, double accLimitScale, double jerkLimitScale) + { var jointLimits = new List(); var jointCouplings = new List(); @@ -67,15 +145,22 @@ public sealed class RobotModelLoader { var masterJointName = coupleElement.GetProperty("master_joint").GetString() ?? throw new InvalidDataException($"关节 {jointName} 的 couple 缺少 master_joint。"); - + var multiplier = coupleElement.TryGetProperty("multiplier", out var multiplierElement) ? multiplierElement.GetDouble() : 0.0; + var offset = coupleElement.TryGetProperty("offset", out var offsetElement) ? offsetElement.GetDouble() : 0.0; jointCouplings.Add(new JointCoupling( slaveJointName: jointName, masterJointName: masterJointName, - multiplier: coupleElement.TryGetProperty("multiplier", out var multiplierElement) ? multiplierElement.GetDouble() : 0.0, - offset: coupleElement.TryGetProperty("offset", out var offsetElement) ? offsetElement.GetDouble() : 0.0)); + multiplier: multiplier, + offset: offset)); + _logger?.LogInformation("关节 {JointName} 的耦合关系: 主关节={MasterJointName}, 比例={Multiplier}, 偏移={Offset}", jointName, masterJointName, multiplier, offset); } } + foreach (var jointLimit in jointLimits) + { + _logger?.LogInformation("关节 {JointName} 的限制值: 速度={VelocityLimit}, 加速度={AccelerationLimit}, Jerk={JerkLimit}", jointLimit.JointName, jointLimit.VelocityLimit, jointLimit.AccelerationLimit, jointLimit.JerkLimit); + } + return new RobotProfile( name: profileName, modelPath: resolvedModelPath, @@ -87,99 +172,32 @@ public sealed class RobotModelLoader } /// - /// 从 GLB 文件中提取 JSON chunk 文本。 + /// 从机器人主体构造正运动学几何视图。 /// - private static string ReadJsonChunk(string modelPath) + private RobotKinematicsModel BuildKinematicsModel(JsonElement robotBody, string profileName) { - using var stream = File.OpenRead(modelPath); - using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: false); - - var magic = Encoding.ASCII.GetString(reader.ReadBytes(4)); - if (!string.Equals(magic, "glTF", StringComparison.Ordinal)) - { - throw new InvalidDataException($"{modelPath} 不是合法的 GLB 文件。"); - } - - var version = reader.ReadUInt32(); - if (version != 2) - { - throw new NotSupportedException($"当前仅支持 GLB 2.0,实际版本为 {version}。"); - } - - var totalLength = reader.ReadUInt32(); - while (stream.Position < totalLength) - { - var chunkLength = reader.ReadUInt32(); - var chunkType = reader.ReadUInt32(); - var chunkBytes = reader.ReadBytes((int)chunkLength); - if (chunkType == JsonChunkType) - { - return Encoding.UTF8.GetString(chunkBytes); - } - } - - throw new InvalidDataException($"{modelPath} 不包含 JSON chunk。"); - } - - /// - /// 在 robotics.bodies 中找到 type=2 的机器人主体。 - /// - private static JsonElement FindRobotBody(JsonElement root) - { - var bodies = root - .GetProperty("scenes")[0] - .GetProperty("extras") - .GetProperty("rvbust") - .GetProperty("robotics") - .GetProperty("bodies"); - - foreach (var body in bodies.EnumerateArray()) - { - if (body.TryGetProperty("type", out var typeElement) && typeElement.GetInt32() == 2) - { - return body; - } - } - - throw new InvalidDataException("未在 .robot 文件中找到 type=2 的机器人主体。"); - } - - /// - /// 加载 .robot 文件并生成运动学侧需要的完整几何模型。 - /// - /// .robot 文件路径。 - /// 包含完整关节几何链的运动学模型。 - public RobotKinematicsModel LoadKinematicsModel(string modelPath) - { - if (string.IsNullOrWhiteSpace(modelPath)) - { - throw new ArgumentException(".robot 路径不能为空。", nameof(modelPath)); - } - - var resolvedModelPath = Path.GetFullPath(modelPath); - var jsonText = ReadJsonChunk(resolvedModelPath); - using var document = JsonDocument.Parse(jsonText); - - var robotBody = FindRobotBody(document.RootElement); - var profileName = robotBody.TryGetProperty("name", out var nameElement) - ? nameElement.GetString() ?? Path.GetFileNameWithoutExtension(resolvedModelPath) - : Path.GetFileNameWithoutExtension(resolvedModelPath); - var joints = new List(); foreach (var jointElement in robotBody.GetProperty("joints").EnumerateArray()) { var jointName = jointElement.GetProperty("name").GetString() ?? throw new InvalidDataException("关节缺少 name。"); + // jointType: 关节类型编码;用于区分旋转关节/其他结构关节,后续几何链路可据此决定求解策略。 var jointType = jointElement.TryGetProperty("type", out var typeElement) ? typeElement.GetInt32() : 0; + // origin: 关节局部原点配置,格式通常为 [x, y, z, qx, qy, qz, qw],定义父坐标到关节坐标的位姿。 var origin = jointElement.GetProperty("origin").EnumerateArray().Select(static e => e.GetDouble()).ToArray(); + // axis: 关节运动轴;部分模型为 4 元组 [x, y, z, scale],其中方向向量用于正运动学雅可比计算。 var axis = jointElement.GetProperty("axis").EnumerateArray().Select(static e => e.GetDouble()).ToArray(); // axis 字段有时存的是 4 元组 [x, y, z, scale],取最后 3 个作为方向向量。 var axisVector = axis.Length >= 3 ? axis[^3..] : axis; + // originXyz: 平移分量 (x,y,z),用于构建关节在父链路下的位置偏移。 var originXyz = origin.Length >= 3 ? origin[..3] : origin; - var originQuat = origin.Length >= 7 ? origin[3..7] : new double[] { 0.0, 0.0, 0.0, 1.0 }; + // originQuat: 旋转分量 (qx,qy,qz,qw),用于构建关节在父链路下的姿态;缺省时回退单位四元数。 + var originQuat = origin.Length >= 7 ? origin[3..7] : [0.0, 0.0, 0.0, 1.0]; + // coupleMaster/coupleMultiplier/coupleOffset: 关节耦合参数,描述 slave 关节如何由 master 关节线性映射得到。 + // 典型关系: slave = master * multiplier + offset。 string? coupleMaster = null; double coupleMultiplier = 0.0; double coupleOffset = 0.0; @@ -190,19 +208,45 @@ public sealed class RobotModelLoader coupleOffset = coupleElement.TryGetProperty("offset", out var o) ? o.GetDouble() : 0.0; } + var parentLink = jointElement.GetProperty("parent").GetString() ?? string.Empty; + var childLink = jointElement.GetProperty("child").GetString() ?? string.Empty; + _logger?.LogInformation( + "几何关节解析: name={JointName}, parent={Parent}, child={Child}, type={JointType}, axis={Axis}, originXyz={OriginXyz}, originQuat={OriginQuat}, coupleMaster={CoupleMaster}, coupleMultiplier={CoupleMultiplier}, coupleOffset={CoupleOffset}", + jointName, + parentLink, + childLink, + jointType, + string.Join(", ", axisVector.Select(static v => v.ToString("G17"))), + string.Join(", ", originXyz.Select(static v => v.ToString("G17"))), + string.Join(", ", originQuat.Select(static v => v.ToString("G17"))), + coupleMaster ?? "", + coupleMultiplier, + coupleOffset); + joints.Add(new RobotJointGeometry( + // name: 当前关节名,作为几何链和耦合关系的主键。 name: jointName, - parent: jointElement.GetProperty("parent").GetString() ?? string.Empty, - child: jointElement.GetProperty("child").GetString() ?? string.Empty, + // parent: 父 link 名称,用于串起机器人树结构。 + parent: parentLink, + // child: 子 link 名称,标识该关节输出到哪个连杆。 + child: childLink, + // jointType: 关节类型编码,供运动学模型区分计算路径。 jointType: jointType, + // axis: 关节轴方向向量,决定旋转/平移沿哪个局部方向发生。 axis: axisVector, + // originXyz: 关节原点平移分量。 originXyz: originXyz, + // originQuatXyzw: 关节原点旋转四元数分量。 originQuatXyzw: originQuat, + // coupleMaster: 耦合主关节名(无耦合时为 null)。 coupleMaster: coupleMaster, + // coupleMultiplier: 耦合线性比例系数。 coupleMultiplier: coupleMultiplier, + // coupleOffset: 耦合常量偏移量。 coupleOffset: coupleOffset)); } + _logger?.LogInformation("几何模型构建完成: profileName={ProfileName}, jointCount={JointCount}", profileName, joints.Count); return new RobotKinematicsModel(name: profileName, joints: joints); } diff --git a/src/Flyshot.Core.Domain/ControllerStateSnapshot.cs b/src/Flyshot.Core.Domain/ControllerStateSnapshot.cs index d25f8c1..ffabae8 100644 --- a/src/Flyshot.Core.Domain/ControllerStateSnapshot.cs +++ b/src/Flyshot.Core.Domain/ControllerStateSnapshot.cs @@ -18,7 +18,14 @@ public sealed class ControllerStateSnapshot double speedRatio, IEnumerable? jointPositions = null, IEnumerable? cartesianPose = null, - IEnumerable? activeAlarms = null) + IEnumerable? activeAlarms = null, + IEnumerable? stateTailWords = null, + byte? j519Status = null, + uint? j519Sequence = null, + bool? j519AcceptsCommand = null, + bool? j519ReceivedCommand = null, + bool? j519SystemReady = null, + bool? j519RobotInMotion = null) { if (string.IsNullOrWhiteSpace(connectionState)) { @@ -34,6 +41,7 @@ public sealed class ControllerStateSnapshot var copiedJointPositions = jointPositions?.ToArray() ?? Array.Empty(); var copiedCartesianPose = cartesianPose?.ToArray() ?? Array.Empty(); var copiedActiveAlarms = activeAlarms?.ToArray() ?? Array.Empty(); + var copiedStateTailWords = stateTailWords?.ToArray() ?? Array.Empty(); CapturedAt = capturedAt; ConnectionState = connectionState; @@ -43,6 +51,13 @@ public sealed class ControllerStateSnapshot JointPositions = copiedJointPositions; CartesianPose = copiedCartesianPose; ActiveAlarms = copiedActiveAlarms; + StateTailWords = copiedStateTailWords; + J519Status = j519Status; + J519Sequence = j519Sequence; + J519AcceptsCommand = j519AcceptsCommand; + J519ReceivedCommand = j519ReceivedCommand; + J519SystemReady = j519SystemReady; + J519RobotInMotion = j519RobotInMotion; } /// @@ -92,4 +107,46 @@ public sealed class ControllerStateSnapshot /// [JsonPropertyName("activeAlarms")] public IReadOnlyList ActiveAlarms { get; } + + /// + /// 获取 TCP 10010 状态帧尾部原始状态字,仅用于诊断,不直接推断运行语义。 + /// + [JsonPropertyName("stateTailWords")] + public IReadOnlyList StateTailWords { get; } + + /// + /// 获取最近一次 UDP 60015 J519 响应的原始状态字节;没有响应时为 null。 + /// + [JsonPropertyName("j519Status")] + public byte? J519Status { get; } + + /// + /// 获取最近一次 UDP 60015 J519 响应序号;没有响应时为 null。 + /// + [JsonPropertyName("j519Sequence")] + public uint? J519Sequence { get; } + + /// + /// 获取 J519 accept_cmd 状态位;没有响应时为 null。 + /// + [JsonPropertyName("j519AcceptsCommand")] + public bool? J519AcceptsCommand { get; } + + /// + /// 获取 J519 received_cmd 状态位;没有响应时为 null。 + /// + [JsonPropertyName("j519ReceivedCommand")] + public bool? J519ReceivedCommand { get; } + + /// + /// 获取 J519 sysrdy 状态位;没有响应时为 null。 + /// + [JsonPropertyName("j519SystemReady")] + public bool? J519SystemReady { get; } + + /// + /// 获取 J519 rbt_inmotion 状态位;没有响应时为 null。 + /// + [JsonPropertyName("j519RobotInMotion")] + public bool? J519RobotInMotion { get; } } diff --git a/src/Flyshot.Core.Domain/FlyshotPreparedExecution.cs b/src/Flyshot.Core.Domain/FlyshotPreparedExecution.cs new file mode 100644 index 0000000..6ec9a93 --- /dev/null +++ b/src/Flyshot.Core.Domain/FlyshotPreparedExecution.cs @@ -0,0 +1,202 @@ +namespace Flyshot.Core.Domain; + +/// +/// 表示飞拍链路在进入运行时之前就已经准备完成的最终发送结果。 +/// +public sealed class FlyshotPreparedExecution +{ + /// + /// 初始化一份飞拍最终发送结果。 + /// + /// 最终 8ms 发送点列。 + /// 与最终发送点列对齐的触发绑定结果。 + /// 与最终发送点列一致的时间映射诊断行。 + /// 与最终发送点列一致的跃度诊断行。 + /// 请求的执行倍率。 + /// 最终发送队列采用的采样倍率;请求倍率已折算进规划时为 1。 + /// 最终发送总时长,单位为秒。 + /// 历史诊断字段;当前执行侧不自动改写 speed_ratio,因此正常为 0。 + public FlyshotPreparedExecution( + IEnumerable samples, + IEnumerable triggerBindings, + IEnumerable> timingRows, + IEnumerable> jerkRows, + double requestSpeedRatio, + double finalSpeedRatio, + double finalDurationSeconds, + int stretchIterationCount) + { + ArgumentNullException.ThrowIfNull(samples); + ArgumentNullException.ThrowIfNull(triggerBindings); + ArgumentNullException.ThrowIfNull(timingRows); + ArgumentNullException.ThrowIfNull(jerkRows); + + if (requestSpeedRatio <= 0.0 || double.IsNaN(requestSpeedRatio) || double.IsInfinity(requestSpeedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(requestSpeedRatio), "请求速度倍率必须是有限正数。"); + } + + if (finalSpeedRatio <= 0.0 || double.IsNaN(finalSpeedRatio) || double.IsInfinity(finalSpeedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(finalSpeedRatio), "最终速度倍率必须是有限正数。"); + } + + if (finalDurationSeconds < 0.0 || double.IsNaN(finalDurationSeconds) || double.IsInfinity(finalDurationSeconds)) + { + throw new ArgumentOutOfRangeException(nameof(finalDurationSeconds), "最终发送总时长必须是有限非负数。"); + } + + if (stretchIterationCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(stretchIterationCount), "倍率改写迭代次数必须是非负整数。"); + } + + Samples = samples.Select(static sample => sample).ToArray(); + TriggerBindings = triggerBindings.Select(static binding => binding).ToArray(); + TimingRows = timingRows.Select(static row => row.ToArray()).ToArray(); + JerkRows = jerkRows.Select(static row => row.ToArray()).ToArray(); + RequestSpeedRatio = requestSpeedRatio; + FinalSpeedRatio = finalSpeedRatio; + FinalDurationSeconds = finalDurationSeconds; + StretchIterationCount = stretchIterationCount; + } + + /// + /// 获取最终 8ms 发送点列。 + /// + public IReadOnlyList Samples { get; } + + /// + /// 获取与最终发送点列对齐的触发绑定结果。 + /// + public IReadOnlyList TriggerBindings { get; } + + /// + /// 获取与最终发送点列一致的时间映射诊断行。 + /// + public IReadOnlyList> TimingRows { get; } + + /// + /// 获取与最终发送点列一致的跃度诊断行。 + /// + public IReadOnlyList> JerkRows { get; } + + /// + /// 获取请求的执行倍率。 + /// + public double RequestSpeedRatio { get; } + + /// + /// 获取最终发送队列采用的采样倍率;请求倍率保留在 中用于诊断。 + /// + public double FinalSpeedRatio { get; } + + /// + /// 获取最终发送总时长,单位为秒。 + /// + public double FinalDurationSeconds { get; } + + /// + /// 获取历史诊断字段;当前执行侧不自动改写 speed_ratio,因此正常为 0。 + /// + public int StretchIterationCount { get; } +} + +/// +/// 表示飞拍最终发送队列中的一个 8ms 发送点。 +/// +public sealed class FlyshotPreparedSample +{ + /// + /// 初始化一条最终发送点。 + /// + /// 发送周期序号。 + /// 物理发送时间,单位为秒。 + /// 回映射到规划轨迹的采样时间,单位为秒。 + /// 生成该发送点时采用的队列采样倍率,已规划调速路径通常为 1。 + /// J519 下发使用的角度制关节目标。 + public FlyshotPreparedSample( + long sampleIndex, + double sendTime, + double trajectoryTime, + double speedRatio, + IReadOnlyList jointsDegrees) + { + ArgumentNullException.ThrowIfNull(jointsDegrees); + SampleIndex = sampleIndex; + SendTime = sendTime; + TrajectoryTime = trajectoryTime; + SpeedRatio = speedRatio; + JointsDegrees = jointsDegrees.ToArray(); + } + + /// + /// 获取发送周期序号。 + /// + public long SampleIndex { get; } + + /// + /// 获取物理发送时间,单位为秒。 + /// + public double SendTime { get; } + + /// + /// 获取回映射到规划轨迹的采样时间,单位为秒。 + /// + public double TrajectoryTime { get; } + + /// + /// 获取生成该发送点时采用的队列采样倍率。 + /// + public double SpeedRatio { get; } + + /// + /// 获取 J519 下发使用的角度制关节目标。 + /// + public IReadOnlyList JointsDegrees { get; } +} + +/// +/// 表示理论触发事件最终绑定到哪个发送点的结果。 +/// +public sealed class FlyshotPreparedTriggerBinding +{ + /// + /// 初始化一条最终触发绑定结果。 + /// + /// 理论触发事件。 + /// 最终绑定的发送点。 + /// 最终绑定到的发送点索引。 + /// 是否在理论搜索窗口内找到该发送点。 + public FlyshotPreparedTriggerBinding( + TrajectoryDoEvent trigger, + FlyshotPreparedSample sample, + int sampleIndex, + bool foundInWindow) + { + Trigger = trigger ?? throw new ArgumentNullException(nameof(trigger)); + Sample = sample ?? throw new ArgumentNullException(nameof(sample)); + SampleIndex = sampleIndex; + FoundInWindow = foundInWindow; + } + + /// + /// 获取理论触发事件。 + /// + public TrajectoryDoEvent Trigger { get; } + + /// + /// 获取最终绑定的发送点。 + /// + public FlyshotPreparedSample Sample { get; } + + /// + /// 获取最终绑定到的发送点索引。 + /// + public int SampleIndex { get; } + + /// + /// 获取是否在理论搜索窗口内完成绑定。 + /// + public bool FoundInWindow { get; } +} diff --git a/src/Flyshot.Core.Domain/RobotKinematicsModel.cs b/src/Flyshot.Core.Domain/RobotKinematicsModel.cs index c06c7f4..9afb373 100644 --- a/src/Flyshot.Core.Domain/RobotKinematicsModel.cs +++ b/src/Flyshot.Core.Domain/RobotKinematicsModel.cs @@ -3,7 +3,7 @@ using System.Text.Json.Serialization; namespace Flyshot.Core.Domain; /// -/// 描述机器人运动学链所需的完整关节几何信息,从 .robot GLB 中提取。 +/// 描述机器人运动学链所需的完整关节几何信息,从现场固化的机器人 JSON 中提取。 /// /// 为什么与 RobotProfile 分开? /// --- diff --git a/src/Flyshot.Core.Domain/RobotProfile.cs b/src/Flyshot.Core.Domain/RobotProfile.cs index 05f92b8..a7f623a 100644 --- a/src/Flyshot.Core.Domain/RobotProfile.cs +++ b/src/Flyshot.Core.Domain/RobotProfile.cs @@ -3,12 +3,12 @@ using System.Text.Json.Serialization; namespace Flyshot.Core.Domain; /// -/// Describes the robot model contract consumed by planning and runtime orchestration. +/// 描述规划与运行时编排共同使用的机器人模型契约。 /// public sealed class RobotProfile { /// - /// Initializes a new robot profile with validated joint limits and coupling metadata. + /// 使用已校验的关节约束与耦合元数据初始化机器人画像。 /// public RobotProfile( string name, @@ -47,7 +47,7 @@ public sealed class RobotProfile ArgumentNullException.ThrowIfNull(jointLimits); ArgumentNullException.ThrowIfNull(jointCouplings); - // Snapshot the collections once so downstream layers cannot mutate domain state in place. + // 先对集合做一次快照,避免下游直接原地修改领域状态。 var copiedJointLimits = jointLimits.ToArray(); var copiedJointCouplings = jointCouplings.ToArray(); @@ -66,55 +66,55 @@ public sealed class RobotProfile } /// - /// Gets the robot profile name exposed to the rest of the runtime. + /// 获取对运行时其余模块暴露的机器人画像名称。 /// [JsonPropertyName("name")] public string Name { get; } /// - /// Gets the source path of the robot model file. + /// 获取机器人模型文件的来源路径。 /// [JsonPropertyName("modelPath")] public string ModelPath { get; } /// - /// Gets the active revolute degree-of-freedom count. + /// 获取当前生效的旋转关节自由度数量。 /// [JsonPropertyName("degreesOfFreedom")] public int DegreesOfFreedom { get; } /// - /// Gets the validated per-joint kinematic limits. + /// 获取按关节校验后的运动学约束。 /// [JsonPropertyName("jointLimits")] public IReadOnlyList JointLimits { get; } /// - /// Gets optional joint coupling metadata parsed from the robot model. + /// 获取从机器人模型解析出的可选关节耦合元数据。 /// [JsonPropertyName("jointCouplings")] public IReadOnlyList JointCouplings { get; } /// - /// Gets the servo scheduling period used by the runtime. + /// 获取运行时使用的伺服调度周期。 /// [JsonPropertyName("servoPeriod")] public TimeSpan ServoPeriod { get; } /// - /// Gets the trigger scheduling period used by shot-event alignment. + /// 获取飞拍事件对齐使用的触发调度周期。 /// [JsonPropertyName("triggerPeriod")] public TimeSpan TriggerPeriod { get; } } /// -/// Describes a single revolute joint limit set required by the planners. +/// 描述规划器所需的单个旋转关节约束集合。 /// public sealed class JointLimit { /// - /// Initializes a validated joint limit record. + /// 初始化一个已校验的关节约束记录。 /// public JointLimit(string jointName, double velocityLimit, double accelerationLimit, double jerkLimit) { @@ -145,37 +145,37 @@ public sealed class JointLimit } /// - /// Gets the joint name associated with the limits. + /// 获取该约束对应的关节名称。 /// [JsonPropertyName("jointName")] public string JointName { get; } /// - /// Gets the velocity limit in joint space units. + /// 获取关节空间单位下的速度上限。 /// [JsonPropertyName("velocityLimit")] public double VelocityLimit { get; } /// - /// Gets the acceleration limit in joint space units. + /// 获取关节空间单位下的加速度上限。 /// [JsonPropertyName("accelerationLimit")] public double AccelerationLimit { get; } /// - /// Gets the jerk limit in joint space units. + /// 获取关节空间单位下的跃度上限。 /// [JsonPropertyName("jerkLimit")] public double JerkLimit { get; } } /// -/// Describes a joint-coupling rule that must be applied before kinematics or planning. +/// 描述在运动学计算或轨迹规划前必须应用的关节耦合规则。 /// public sealed class JointCoupling { /// - /// Initializes a validated joint-coupling description. + /// 初始化一个已校验的关节耦合描述。 /// public JointCoupling(string slaveJointName, string masterJointName, double multiplier, double offset) { @@ -201,25 +201,25 @@ public sealed class JointCoupling } /// - /// Gets the dependent joint name. + /// 获取从属(被驱动)关节名称。 /// [JsonPropertyName("slaveJointName")] public string SlaveJointName { get; } /// - /// Gets the source joint name. + /// 获取主导(驱动)关节名称。 /// [JsonPropertyName("masterJointName")] public string MasterJointName { get; } /// - /// Gets the coupling multiplier applied to the master joint angle. + /// 获取作用在主导关节角度上的耦合倍率。 /// [JsonPropertyName("multiplier")] public double Multiplier { get; } /// - /// Gets the additive offset applied after the multiplier. + /// 获取在耦合倍率之后叠加的偏移量。 /// [JsonPropertyName("offset")] public double Offset { get; } diff --git a/src/Flyshot.Core.Domain/TrajectoryResult.cs b/src/Flyshot.Core.Domain/TrajectoryResult.cs index e0ada1c..d90e515 100644 --- a/src/Flyshot.Core.Domain/TrajectoryResult.cs +++ b/src/Flyshot.Core.Domain/TrajectoryResult.cs @@ -21,7 +21,11 @@ public sealed class TrajectoryResult string? failureReason, bool usedCache, int originalWaypointCount, - int plannedWaypointCount) + int plannedWaypointCount, + int triggerSampleIndexOffsetCycles = 0, + IEnumerable>? denseJointTrajectory = null, + FlyshotPreparedExecution? preparedFlyshotExecution = null, + IEnumerable>? denseCartesianTrajectory = null) { if (string.IsNullOrWhiteSpace(programName)) { @@ -43,6 +47,11 @@ public sealed class TrajectoryResult throw new ArgumentOutOfRangeException(nameof(plannedWaypointCount), "Planned waypoint count must be greater than or equal to the original waypoint count."); } + if (triggerSampleIndexOffsetCycles < 0) + { + throw new ArgumentOutOfRangeException(nameof(triggerSampleIndexOffsetCycles), "Trigger sample index offset cycles must be zero or positive."); + } + ArgumentNullException.ThrowIfNull(shotEvents); ArgumentNullException.ThrowIfNull(triggerTimeline); ArgumentNullException.ThrowIfNull(artifacts); @@ -51,6 +60,8 @@ public sealed class TrajectoryResult var copiedShotEvents = shotEvents.ToArray(); var copiedTriggerTimeline = triggerTimeline.ToArray(); var copiedArtifacts = artifacts.ToArray(); + var copiedDenseJointTrajectory = denseJointTrajectory?.Select(static row => row.ToArray()).ToArray(); + var copiedDenseCartesianTrajectory = denseCartesianTrajectory?.Select(static row => row.ToArray()).ToArray(); ProgramName = programName; Method = method; @@ -63,6 +74,10 @@ public sealed class TrajectoryResult UsedCache = usedCache; OriginalWaypointCount = originalWaypointCount; PlannedWaypointCount = plannedWaypointCount; + TriggerSampleIndexOffsetCycles = triggerSampleIndexOffsetCycles; + DenseJointTrajectory = copiedDenseJointTrajectory; + DenseCartesianTrajectory = copiedDenseCartesianTrajectory; + PreparedFlyshotExecution = preparedFlyshotExecution; } /// @@ -130,6 +145,32 @@ public sealed class TrajectoryResult /// [JsonPropertyName("plannedWaypointCount")] public int PlannedWaypointCount { get; } + + /// + /// Gets the configured command-sample offset applied after trigger binding picks the best sample. + /// + [JsonPropertyName("triggerSampleIndexOffsetCycles")] + public int TriggerSampleIndexOffsetCycles { get; } + + /// + /// Gets the dense joint trajectory samples where each row is [time, j1, j2, ...]. + /// Null when dense sampling was not performed (e.g. simulation fallback). + /// + [JsonPropertyName("denseJointTrajectory")] + public IReadOnlyList>? DenseJointTrajectory { get; } + + /// + /// 获取稠密直角坐标轨迹采样点,每行格式为 [time, x, y, z, w, p, r]。 + /// Null 表示本结果不是直角流式运动。 + /// + [JsonPropertyName("denseCartesianTrajectory")] + public IReadOnlyList>? DenseCartesianTrajectory { get; } + + /// + /// Gets the prepared flyshot execution queue when the flyshot chain has already built the final 8ms send sequence. + /// + [JsonIgnore] + public FlyshotPreparedExecution? PreparedFlyshotExecution { get; } } /// @@ -140,7 +181,13 @@ public sealed class TrajectoryDoEvent /// /// Initializes a validated runtime trigger event. /// - public TrajectoryDoEvent(int waypointIndex, double triggerTime, int offsetCycles, int holdCycles, IoAddressGroup addressGroup) + public TrajectoryDoEvent( + int waypointIndex, + double triggerTime, + int offsetCycles, + int holdCycles, + IoAddressGroup addressGroup, + IEnumerable? referenceJointsDegrees = null) { if (waypointIndex < 0) { @@ -162,6 +209,7 @@ public sealed class TrajectoryDoEvent OffsetCycles = offsetCycles; HoldCycles = holdCycles; AddressGroup = addressGroup ?? throw new ArgumentNullException(nameof(addressGroup)); + ReferenceJointsDegrees = referenceJointsDegrees?.ToArray(); } /// @@ -193,6 +241,13 @@ public sealed class TrajectoryDoEvent /// [JsonPropertyName("addressGroup")] public IoAddressGroup AddressGroup { get; } + + /// + /// Gets the teach waypoint joints converted to degrees. + /// This is used to select the closest real send sample inside the trigger time window. + /// + [JsonPropertyName("referenceJointsDegrees")] + public IReadOnlyList? ReferenceJointsDegrees { get; } } /// diff --git a/src/Flyshot.Core.Planning/Export/TrajectoryExporter.cs b/src/Flyshot.Core.Planning/Export/TrajectoryExporter.cs index 19f1a4f..2f224f4 100644 --- a/src/Flyshot.Core.Planning/Export/TrajectoryExporter.cs +++ b/src/Flyshot.Core.Planning/Export/TrajectoryExporter.cs @@ -1,6 +1,7 @@ using System.Text; using System.Text.Json; using Flyshot.Core.Domain; +using Flyshot.Core.Planning.Sampling; namespace Flyshot.Core.Planning.Export; @@ -15,12 +16,28 @@ namespace Flyshot.Core.Planning.Export; /// public static class TrajectoryExporter { + /// + /// 导出规划关节轨迹关键点到文本文件。 + /// + public static void WriteJointTrajectory(string path, IReadOnlyList> rows) + { + WriteRows(path, rows); + } + /// /// 导出稠密关节轨迹到文本文件。 /// public static void WriteJointDenseTrajectory(string path, IReadOnlyList> rows) { - WriteDenseRows(path, rows); + WriteRows(path, rows); + } + + /// + /// 导出规划笛卡尔轨迹关键点到文本文件。 + /// + public static void WriteCartesianTrajectory(string path, IReadOnlyList> rows) + { + WriteRows(path, rows); } /// @@ -28,7 +45,7 @@ public static class TrajectoryExporter /// public static void WriteCartesianDenseTrajectory(string path, IReadOnlyList> rows) { - WriteDenseRows(path, rows); + WriteRows(path, rows); } /// @@ -36,13 +53,51 @@ public static class TrajectoryExporter /// public static void WriteShotEvents(string path, IReadOnlyList events) { + WriteShotEvents(path, events, bindings: null); + } + + /// + /// 导出触发事件到 JSON 文件,并可选附带最终绑定到的实际 sample 信息。 + /// + public static void WriteShotEvents( + string path, + IReadOnlyList events, + IReadOnlyList? bindings) + { + var bindingByWaypointIndex = bindings? + .GroupBy(static binding => binding.Trigger.WaypointIndex) + .ToDictionary(static group => group.Key, static group => group.First()); + var payload = events.Select(e => new { waypoint_index = e.WaypointIndex, trigger_time = Math.Round(e.TriggerTime, 6), sample_index = e.SampleIndex, sample_time = Math.Round(e.SampleTime, 6), - addrs = e.AddressGroup.Addresses.ToList() + addrs = e.AddressGroup.Addresses.ToList(), + trigger_window_seconds = bindingByWaypointIndex is not null && bindingByWaypointIndex.TryGetValue(e.WaypointIndex, out var binding) + ? Math.Round(TriggerSampleBinder.TriggerBindingToleranceSeconds, 6) + : (double?)null, + selected_sample_index = bindingByWaypointIndex is not null && bindingByWaypointIndex.TryGetValue(e.WaypointIndex, out binding) + ? binding.SampleIndex + : (int?)null, + selected_send_time = bindingByWaypointIndex is not null && bindingByWaypointIndex.TryGetValue(e.WaypointIndex, out binding) + ? Math.Round(binding.Sample.SendTime, 6) + : (double?)null, + selected_trajectory_time = bindingByWaypointIndex is not null && bindingByWaypointIndex.TryGetValue(e.WaypointIndex, out binding) + ? Math.Round(binding.Sample.TrajectoryTime, 6) + : (double?)null, + io_mask = bindingByWaypointIndex is not null && bindingByWaypointIndex.TryGetValue(e.WaypointIndex, out binding) + ? ComputeIoValue(binding.Trigger.AddressGroup) + : (ushort?)null, + reference_joints_deg = bindingByWaypointIndex is not null + && bindingByWaypointIndex.TryGetValue(e.WaypointIndex, out binding) + && binding.Trigger.ReferenceJointsDegrees is { Count: > 0 } + ? binding.Trigger.ReferenceJointsDegrees.Select(static value => Math.Round(value, 6)).ToArray() + : null, + trigger_joints_deg = bindingByWaypointIndex is not null && bindingByWaypointIndex.TryGetValue(e.WaypointIndex, out binding) + ? binding.Sample.JointsDegrees.Select(static value => Math.Round(value, 6)).ToArray() + : null }).ToList(); var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions @@ -53,7 +108,7 @@ public static class TrajectoryExporter File.WriteAllText(path, json, new UTF8Encoding(false)); } - private static void WriteDenseRows(string path, IReadOnlyList> rows) + private static void WriteRows(string path, IReadOnlyList> rows) { var sb = new StringBuilder(); foreach (var row in rows) @@ -63,4 +118,21 @@ public static class TrajectoryExporter File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false)); } + + /// + /// 把 IO 地址组中的地址号映射为 writeIoValue 的位掩码,和运行时 / saveTrajectory 导出保持一致。 + /// + private static ushort ComputeIoValue(IoAddressGroup group) + { + ushort value = 0; + foreach (var addr in group.Addresses) + { + if (addr is >= 1 and <= 16) + { + value |= (ushort)(1 << (addr - 1)); + } + } + + return value; + } } diff --git a/src/Flyshot.Core.Planning/Flyshot.Core.Planning.csproj b/src/Flyshot.Core.Planning/Flyshot.Core.Planning.csproj index 1ccc45d..d159671 100644 --- a/src/Flyshot.Core.Planning/Flyshot.Core.Planning.csproj +++ b/src/Flyshot.Core.Planning/Flyshot.Core.Planning.csproj @@ -6,6 +6,10 @@ enable + + + + diff --git a/src/Flyshot.Core.Planning/ICSP-算法说明.md b/src/Flyshot.Core.Planning/ICSP-算法说明.md new file mode 100644 index 0000000..4424737 --- /dev/null +++ b/src/Flyshot.Core.Planning/ICSP-算法说明.md @@ -0,0 +1,194 @@ +# ICSP 算法说明(`ICspPlanner`) + +本文档用于解释 `Flyshot.Core.Planning.ICspPlanner` 当前实现的 **ICSP 规划算法**在本仓库中的真实含义与计算逻辑,便于与逆向结论对照、以及指导后续改造(例如“按约束生成中间点位”)。 + +> 适用范围:本文描述的是当前 C# 实现的 **“CubicSpline + 逐段时间缩放迭代(retiming)”** 版本。 +> 重要澄清:`ICspPlanner` 的主要输出是 **时间轴**(每个示教点的时间戳),而不是直接输出固定周期的稠密点序列;稠密点在后续采样层生成。 + +--- + +## 1. 名词与数据形态 + +### 1.1 输入 + +- **示教点(路点)**:`request.Program.Waypoints` + 每个路点是关节空间向量 \(q_i \in \mathbb{R}^{dof}\)。 +- **关节约束**:`request.Robot.JointLimits[d]` 提供每轴上限: + - 速度上限 \(v_{lim}[d]\) + - 加速度上限 \(a_{lim}[d]\) + - 跃度(jerk)上限 \(j_{lim}[d]\) + +### 1.2 输出(`PlannedTrajectory`) + +`ICspPlanner` 的输出 **不是稠密轨迹点序列**,而是: + +- `PlannedWaypoints`:规划后路点(对于普通 `icsp`,与输入示教点相同;补点发生在 `SelfAdaptIcspPlanner`) +- `WaypointTimes`:每个路点的绝对时间 \(t_i\)(秒) +- `SegmentDurations`:每段时长 \(T_i = t_{i+1}-t_i\)(秒) +- `SegmentScales`:每段缩放因子 `scale_i` +- `Iterations` / `Threshold`:收敛信息 + +后续模块会基于 `PlannedWaypoints + WaypointTimes` 重建样条并采样,生成稠密点: + +- 规划层稠密采样:`TrajectorySampler.SampleJointTrajectory(...)` +- 运行时 J519 重采样(速度倍率映射 + rad->deg):`J519SendTrajectorySampler.SampleDenseJointTrajectory(...)` + +--- + +## 2. 算法总体目标(retiming) + +给定一组关节示教点 \(\{q_i\}_{i=0}^{N-1}\),在不改变路点位置的前提下,为每段分配时长 \(\{T_i\}_{i=0}^{N-2}\),使得用 **clamped-zero 三次样条**连接后的轨迹在每段上满足: + +- \(\max|\dot q_d(t)| \le v_{lim}[d]\) +- \(\max|\ddot q_d(t)| \le a_{lim}[d]\) +- \(\max|\dddot q_d(t)| \le j_{lim}[d]\) + +实现策略是“逐段缩放时长”的迭代法:每轮用当前 \(\{T_i\}\) 构造样条并解析求导峰值,再根据超限程度把相应段时长乘以缩放因子,使峰值回落。 + +--- + +## 3. 计算步骤(与代码一致) + +### 3.1 前置条件 + +- 路点数 \(N \ge 4\)(否则抛异常) + +### 3.2 初始段时长 + +段数 \(nseg = N-1\)。 + +初始段时长取相邻路点关节空间欧氏距离: + +\[ +T_i^{(0)} = \|q_{i+1}-q_i\|_2 +\] + +### 3.3 由段时长构造绝对时间轴 + +\[ +t_0 = 0,\quad t_{i+1} = t_i + T_i +\] + +### 3.4 用 clamped-zero 边界构造三次样条 + +以 \((t_i, q_i)\) 为节点构造分段三次多项式: + +\[ +S_i(t) = a_i t^3 + b_i t^2 + c_i t + d_i,\quad t \in [t_i, t_{i+1}] +\] + +边界条件为 **clamped-zero**(起点/终点一阶导为 0),用于与逆向锁定的参考行为对齐。 + +### 3.5 解析计算每段导数峰值 + +对每段、每轴,解析求最大绝对值: + +- 一阶导(速度)是二次函数:端点与顶点候选取最大 +- 二阶导(加速度)是一次函数:端点取最大 +- 三阶导(跃度)是常数:直接取绝对值 + +得到三张矩阵: + +- `maxDq[seg,d] = max_t |dq/dt|` +- `maxDdq[seg,d] = max_t |d²q/dt²|` +- `maxDddq[seg,d] = max_t |d³q/dt³|` + +### 3.6 计算每段缩放因子(核心公式) + +对段 `seg`,对每个关节 `d` 计算三类“超限比”: + +\[ +s_v = \left|\frac{maxDq[seg,d]}{v_{lim}[d]}\right| +\] + +\[ +s_a = \sqrt{\left|\frac{maxDdq[seg,d]}{a_{lim}[d]}\right|} +\] + +\[ +s_j = \sqrt[3]{\left|\frac{maxDddq[seg,d]}{j_{lim}[d]}\right|} +\] + +段缩放因子取所有轴、三类约束的最大值: + +\[ +scale_{seg}=\max_d \max(s_v, s_a, s_j) +\] + +> 指数来源:时间拉长 \(k\) 倍时,速度按 \(1/k\) 缩小、加速度按 \(1/k^2\) 缩小、跃度按 \(1/k^3\) 缩小,因此超限比需要分别取一次方/平方根/立方根来求“应当拉长多少倍”。 + +### 3.7 收敛指标与最优解保存 + +每轮用如下指标衡量“离约束满足还差多少”: + +\[ +threshold = \sum_{seg} |scale_{seg} - 1| +\] + +若本轮 `threshold` 小于历史最佳,则保存当前解作为 `best`(包含 `bestDurations / bestScales / bestWaypointTimes` 等)。 + +### 3.8 收敛判定与段时长更新 + +- 若 `threshold < _threshold`(默认 `1e-3`),认为收敛并提前结束迭代 +- 否则更新每段时长: + +\[ +T_{seg} \leftarrow T_{seg} \cdot scale_{seg} +\] + +并进入下一轮。 + +--- + +## 4. 最终判定(global_scale) + +迭代结束后取历史最优缩放因子的最大值: + +\[ +globalScale=\max_{seg}(scale_{seg}) +\] + +若启用强制判定(`enforceFinalScale=true`)且: + +\[ +globalScale > 1 + \text{finalScaleTolerance} +\] + +则判定“未收敛/不可执行”并抛异常。默认容差 `finalScaleTolerance=1e-2`,用于容忍 C# spline 与参考实现间的小量数值差异。 + +--- + +## 5. 与“补点/中间点位”的关系(常见误解澄清) + +### 5.1 `ICspPlanner` 不负责生成固定周期的中间点位 + +`ICspPlanner` 的核心工作是 **时间轴规划(retiming)**:在不改变示教点位置的情况下,通过缩放每段时长让样条导数峰值满足约束。 + +固定周期(例如 8ms/16ms)的“中间点位序列”属于 **采样层**: + +- `TrajectorySampler`:按 `samplePeriod` 在样条上取样,得到 `[time, j1..jN]`(关节单位仍为 rad) +- `J519SendTrajectorySampler`:按 `servoPeriod` 生成真实发送序列,用 `speedRatio` 把 `sendTime` 映射到 `trajectoryTime` 并线性插值,再做 `rad -> deg` + +### 5.2 `SelfAdaptIcspPlanner` 才包含“补点”逻辑,但它很粗 + +`self-adapt-icsp` 的补点策略在 `SelfAdaptIcspPlanner` 中:当某些段 `scale > 1 + tolerance` 时,对这些段插入关节空间**中点**再重规划。该策略的目的主要是“救收敛”,不是生成最终稠密序列。 + +--- + +## 6. 后续改造建议(定位落点) + +如果需求是“根据示教点 + v/a/j 限制,直接生成可下发的稠密点位序列”,通常有两条路径: + +1. **保留 ICSP retiming**:继续用 `ICspPlanner` 求时间轴,再在采样层按固定周期生成中间点位(当前架构就是这条路)。此时需要讨论的是采样周期、速度倍率映射、以及是否要对采样序列再做约束校验或二次整形。 +2. **做真正的自适应插点/细分**:把“插点策略”升级为基于约束的细分(而不只是插中点),这更自然的落点是 `SelfAdaptIcspPlanner` 或新增一个“约束驱动细分器”,而不是把稠密点生成塞进 `ICspPlanner`。 + +--- + +## 7. 关联实现位置(便于跳转) + +- 算法入口:`src/Flyshot.Core.Planning/ICspPlanner.cs` +- 自适应补点:`src/Flyshot.Core.Planning/SelfAdaptIcspPlanner.cs` +- 三次样条实现(clamped-zero + 解析导峰值):`src/Flyshot.Core.Planning/CubicSplineInterpolator.cs` +- 规划层稠密采样:`src/Flyshot.Core.Planning/Sampling/TrajectorySampler.cs` +- J519 实发重采样:`src/Flyshot.Core.Planning/Sampling/J519SendTrajectorySampler.cs` + diff --git a/src/Flyshot.Core.Planning/ICspPlanner.cs b/src/Flyshot.Core.Planning/ICspPlanner.cs index 89a3e83..65f571a 100644 --- a/src/Flyshot.Core.Planning/ICspPlanner.cs +++ b/src/Flyshot.Core.Planning/ICspPlanner.cs @@ -1,4 +1,5 @@ using Flyshot.Core.Domain; +using Microsoft.Extensions.Logging; namespace Flyshot.Core.Planning; @@ -24,6 +25,54 @@ public sealed class ICspPlanner /// public const int DefaultMaxIterations = 1000; + /// + /// 默认最终 scale 容差。当前 C# spline 与旧系统对齐样本存在约 1% 内的数值余量。 + /// + public const double DefaultFinalScaleTolerance = 1e-2; + + private readonly double _threshold; + private readonly int _maxIterations; + private readonly bool _enforceFinalScale; + private readonly double _finalScaleTolerance; + private readonly ILogger? _logger; + + /// + /// 初始化 ICSP 规划器。 + /// + /// 收敛阈值。 + /// 最大迭代轮数。 + /// 是否在最终最优 scale 仍大于 1.0 时抛出失败。 + /// 最终 scale 判定容差。 + /// 日志记录器;允许 null,供无日志场景使用。 + public ICspPlanner( + double threshold = DefaultThreshold, + int maxIterations = DefaultMaxIterations, + bool enforceFinalScale = true, + double finalScaleTolerance = DefaultFinalScaleTolerance, + ILogger? logger = null) + { + if (threshold <= 0.0 || double.IsNaN(threshold) || double.IsInfinity(threshold)) + { + throw new ArgumentOutOfRangeException(nameof(threshold), "收敛阈值必须为有限正数。"); + } + + if (maxIterations < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxIterations), "最大迭代轮数不能为负数。"); + } + + if (finalScaleTolerance < 0.0 || double.IsNaN(finalScaleTolerance) || double.IsInfinity(finalScaleTolerance)) + { + throw new ArgumentOutOfRangeException(nameof(finalScaleTolerance), "最终 scale 容差必须为有限非负数。"); + } + + _threshold = threshold; + _maxIterations = maxIterations; + _enforceFinalScale = enforceFinalScale; + _finalScaleTolerance = finalScaleTolerance; + _logger = logger; + } + /// /// 执行 ICSP 规划,返回包含完整时间轴和收敛信息的轨迹。 /// @@ -37,9 +86,22 @@ public sealed class ICspPlanner throw new ArgumentException("ICSP 至少需要 4 个示教点。", nameof(request)); } + _logger?.LogInformation( + "ICSP 规划开始: 名称={Name}, 路点数={WaypointCount}, 自由度={Dof}, threshold={Threshold}, maxIterations={MaxIterations}", + request.Program.Name, waypoints.Count, request.Robot.DegreesOfFreedom, _threshold, _maxIterations); + _logger?.LogDebug( + "ICSP 输入路点: {Waypoints}", + string.Join(" | ", waypoints.Select(wp => $"[{string.Join(", ", wp.Positions.Select(j => j.ToString("F4")))}]"))); + var qs = WaypointsToArray(waypoints); var (velLimits, accLimits, jerkLimits) = ExtractLimits(request.Robot); + _logger?.LogDebug( + "ICSP 约束限值: vel=[{Vel}], acc=[{Acc}], jerk=[{Jerk}]", + string.Join(", ", velLimits.Select(v => v.ToString("F2"))), + string.Join(", ", accLimits.Select(a => a.ToString("F2"))), + string.Join(", ", jerkLimits.Select(j => j.ToString("F2")))); + // 初始段时长直接取相邻示教点的关节空间欧氏距离。 var segmentDurations = ComputeInitialDurations(qs); int nseg = segmentDurations.Length; @@ -52,7 +114,7 @@ public sealed class ICspPlanner int bestIterations = 0; double[]? bestWaypointTimes = null; - for (int iteration = 0; iteration <= DefaultMaxIterations; iteration++) + for (int iteration = 0; iteration <= _maxIterations; iteration++) { var waypointTimes = CumulativeTimes(segmentDurations); var spline = new CubicSplineInterpolator(waypointTimes, qs); @@ -89,8 +151,11 @@ public sealed class ICspPlanner bestWaypointTimes = (double[])waypointTimes.Clone(); } - if (currentThreshold < DefaultThreshold) + if (currentThreshold < _threshold) { + _logger?.LogDebug( + "ICSP 第 {Iteration} 轮收敛: threshold={CurrentThreshold:E6}", + iteration + 1, currentThreshold); break; } @@ -105,6 +170,25 @@ public sealed class ICspPlanner throw new InvalidOperationException("ICSP 规划未能产生有效结果。"); } + var globalScale = bestScales.Max(); + if (_enforceFinalScale && globalScale > 1.0 + _finalScaleTolerance) + { + _logger?.LogError( + "ICSP 规划未收敛: global_scale={GlobalScale:F6} > {Tolerance:F6}, 段缩放=[{Scales}]", + globalScale, 1.0 + _finalScaleTolerance, + string.Join(", ", bestScales.Select(s => s.ToString("F4")))); + throw new InvalidOperationException( + $"ICSP 规划未收敛,global_scale={globalScale:F6} > {1.0 + _finalScaleTolerance:F6},轨迹不可执行。"); + } + + _logger?.LogInformation( + "ICSP 规划完成: 名称={Name}, 迭代轮数={Iterations}, 收敛阈值={Threshold:E6}, 总时长={Duration:F4}s, global_scale={GlobalScale:F6}", + request.Program.Name, bestIterations, bestThreshold, bestWaypointTimes[^1], globalScale); + _logger?.LogDebug( + "ICSP 段时长: [{Durations}], 段缩放: [{Scales}]", + string.Join(", ", bestDurations.Select(d => d.ToString("F4"))), + string.Join(", ", bestScales.Select(s => s.ToString("F4")))); + return new PlannedTrajectory( robot: request.Robot, originalProgram: request.Program, diff --git a/src/Flyshot.Core.Planning/Sampling/FlyshotExecutionSendSequenceBuilder.cs b/src/Flyshot.Core.Planning/Sampling/FlyshotExecutionSendSequenceBuilder.cs new file mode 100644 index 0000000..a29c776 --- /dev/null +++ b/src/Flyshot.Core.Planning/Sampling/FlyshotExecutionSendSequenceBuilder.cs @@ -0,0 +1,146 @@ +using Flyshot.Core.Domain; + +namespace Flyshot.Core.Planning.Sampling; + +/// +/// 负责在飞拍进入运行时前构建最终 8ms 发送队列,并执行发送口径的离散限幅校验。 +/// +public static class FlyshotExecutionSendSequenceBuilder +{ + /// + /// 根据连续规划轨迹构建最终发送队列;请求 speedRatio 只作为诊断信息保留。 + /// + /// 机器人关节限值配置。 + /// 规划阶段产出的连续轨迹。 + /// 规划结果。 + /// J519 物理发送周期,单位为秒。 + /// 请求的执行倍率,必须已经在规划阶段折算进轨迹。 + /// 最终队列采样倍率;为空时使用 1.0,表示直接采样已调速后的规划轨迹。 + /// 通过离散校验后的飞拍最终发送结果。 + public static FlyshotPreparedExecution Build( + RobotProfile robot, + PlannedTrajectory plannedTrajectory, + TrajectoryResult result, + double servoPeriodSeconds, + double requestedSpeedRatio, + double? samplingSpeedRatio = null) + { + ArgumentNullException.ThrowIfNull(robot); + ArgumentNullException.ThrowIfNull(plannedTrajectory); + ArgumentNullException.ThrowIfNull(result); + + if (requestedSpeedRatio <= 0.0 || double.IsNaN(requestedSpeedRatio) || double.IsInfinity(requestedSpeedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(requestedSpeedRatio), "speed_ratio 必须是有限正数。"); + } + + if (servoPeriodSeconds <= 0.0 || double.IsNaN(servoPeriodSeconds) || double.IsInfinity(servoPeriodSeconds)) + { + throw new ArgumentOutOfRangeException(nameof(servoPeriodSeconds), "伺服周期必须是有限正数。"); + } + + var finalSpeedRatio = samplingSpeedRatio ?? 1.0; + if (finalSpeedRatio <= 0.0 || double.IsNaN(finalSpeedRatio) || double.IsInfinity(finalSpeedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(samplingSpeedRatio), "最终队列采样倍率必须是有限正数。"); + } + + var samples = J519SendTrajectorySampler.SamplePlannedTrajectory( + plannedTrajectory, + servoPeriodSeconds, + finalSpeedRatio); + + try + { + TrajectoryLimitValidator.ValidateJ519SendSamples( + robot, + samples, + trajectoryName: result.ProgramName, + validateJerk: false, + strictJerkTolerance: true); + } + catch (InvalidOperationException exception) + { + throw new InvalidOperationException( + $"飞拍最终发送队列在请求 speed_ratio={requestedSpeedRatio:F6} 下离散限幅校验失败。" + + "请降低 planning_speed_scale 或 speed_ratio 后重新规划;执行侧不会在发送阶段临场重采样。", + exception); + } + + return BuildPreparedExecution( + result, + samples, + requestedSpeedRatio, + finalSpeedRatio, + stretchIterationCount: 0); + } + + /// + /// 将通过校验的 J519 采样点和触发绑定结果封装为稳定的飞拍执行结果。 + /// + private static FlyshotPreparedExecution BuildPreparedExecution( + TrajectoryResult result, + IReadOnlyList samples, + double requestedSpeedRatio, + double finalSpeedRatio, + int stretchIterationCount) + { + var preparedSamples = samples.Select(static sample => + new FlyshotPreparedSample( + sample.SampleIndex, + sample.SendTime, + sample.TrajectoryTime, + sample.SpeedRatio, + sample.JointsDegrees)).ToArray(); + var sampleLookup = preparedSamples.ToDictionary(static sample => sample.SampleIndex); + var triggerBindings = TriggerSampleBinder.Bind( + result.TriggerTimeline, + samples, + result.TriggerSampleIndexOffsetCycles) + .Select(binding => + new FlyshotPreparedTriggerBinding( + binding.Trigger, + sampleLookup[binding.Sample.SampleIndex], + binding.SampleIndex, + binding.FoundInWindow)) + .ToArray(); + var timingRows = samples + .Select(sample => J519SendTrajectorySampler.BuildTimingRow(sample, requestedSpeedRatio, stretchIterationCount)) + .ToArray(); + var jerkRows = BuildJerkRows(samples); + var finalDurationSeconds = preparedSamples.Length == 0 ? 0.0 : preparedSamples[^1].SendTime; + + return new FlyshotPreparedExecution( + preparedSamples, + triggerBindings, + timingRows, + jerkRows, + requestedSpeedRatio, + finalSpeedRatio, + finalDurationSeconds, + stretchIterationCount); + } + + /// + /// 为最终发送点列构建跃度诊断行,保证导出工件和运行时发送复用同一份数据。 + /// + private static IReadOnlyList> BuildJerkRows(IReadOnlyList samples) + { + var jerkRows = new List>(Math.Max(0, samples.Count - 1)); + double[]? previousVelocity = null; + double[]? previousAcceleration = null; + + for (var index = 1; index < samples.Count; index++) + { + jerkRows.Add(J519SendTrajectorySampler.BuildJerkRow( + samples[index - 1].SendTime, + samples[index].SendTime, + samples[index - 1].JointsDegrees, + samples[index].JointsDegrees, + ref previousVelocity, + ref previousAcceleration)); + } + + return jerkRows; + } +} diff --git a/src/Flyshot.Core.Planning/Sampling/J519SendSample.cs b/src/Flyshot.Core.Planning/Sampling/J519SendSample.cs new file mode 100644 index 0000000..e707e2f --- /dev/null +++ b/src/Flyshot.Core.Planning/Sampling/J519SendSample.cs @@ -0,0 +1,42 @@ +namespace Flyshot.Core.Planning.Sampling; + +/// +/// 表示 J519 伺服链路在某一个物理发送周期上的轨迹采样结果。 +/// +/// 从 0 开始的发送周期序号。 +/// J519 物理发送时间,单位为秒。 +/// 映射回规划轨迹的采样时间,单位为秒。 +/// 生成该采样点时使用的速度倍率。 +/// J519 下发使用的角度制关节目标。 +public sealed class J519SendSample( + long sampleIndex, + double sendTime, + double trajectoryTime, + double speedRatio, + IReadOnlyList jointsDegrees) +{ + /// + /// 获取从 0 开始的发送周期序号。 + /// + public long SampleIndex { get; } = sampleIndex; + + /// + /// 获取 J519 物理发送时间,单位为秒。 + /// + public double SendTime { get; } = sendTime; + + /// + /// 获取映射回规划轨迹的采样时间,单位为秒。 + /// + public double TrajectoryTime { get; } = trajectoryTime; + + /// + /// 获取生成该采样点时使用的速度倍率。 + /// + public double SpeedRatio { get; } = speedRatio; + + /// + /// 获取 J519 下发使用的角度制关节目标。 + /// + public IReadOnlyList JointsDegrees { get; } = jointsDegrees.ToArray(); +} diff --git a/src/Flyshot.Core.Planning/Sampling/J519SendTrajectorySampler.cs b/src/Flyshot.Core.Planning/Sampling/J519SendTrajectorySampler.cs new file mode 100644 index 0000000..1206aee --- /dev/null +++ b/src/Flyshot.Core.Planning/Sampling/J519SendTrajectorySampler.cs @@ -0,0 +1,352 @@ +using Flyshot.Core.Planning; + +namespace Flyshot.Core.Planning.Sampling; + +/// +/// 负责把规划层关节轨迹重采样为 J519 物理发送周期上的角度制目标。 +/// +/// 算法约定: +/// 输入的稠密关节轨迹行格式固定为 [time, j1..jN],time 为规划轨迹时间,关节单位为弧度; +/// 输出的 J519 采样点按物理伺服周期排列,关节单位转换为角度制,供 UDP 60015 实时下发和离线 ActualSend 文件共用。 +/// +/// +/// 采样点数先按轨迹时间步长 trajectoryStep = servoPeriod * speedRatio 计算: +/// sampleCount = ceil(max(0, duration / trajectoryStep - 1e-9)) + 1。 +/// 末尾额外保留一个终点钳制周期,确保轨迹时长不是周期整数倍时仍会输出最终点。 +/// +/// +/// 飞拍最终发送队列优先从 重建连续三次样条并直接取点; +/// 旧稠密点入口仅保留给运行时 fallback 和兼容导出。 +/// 第 k 个采样点的物理发送时间为 sendTime = k * servoPeriod; +/// speedRatio 不改变物理发送周期,只用于把发送时间映射回规划轨迹时间: +/// trajectoryTime = min(sendTime * speedRatio, duration)。 +/// 之后在连续样条或旧稠密轨迹上求取关节,并把每个关节从 rad 转为 deg。 +/// +/// +/// 诊断行也在这里统一生成:Timing 行格式为 sample_index + send_time + trajectory_time + speed_ratio; +/// Jerk 行使用相邻发送点上的角度制关节目标做后向差分,依次近似速度、加速度和跃度,格式为 +/// start_time + end_time + dt + max_abs_jerk + jerk[j1..jN]。 +/// +/// +public static class J519SendTrajectorySampler +{ + /// + /// 根据连续规划样条和 J519 伺服周期生成完整实发采样序列。 + /// + /// 规划阶段产出的连续轨迹信息。 + /// J519 物理发送周期,单位为秒。 + /// 速度倍率;只缩放轨迹采样时间,不改变物理发送周期。 + /// 按 J519 发送周期排列的角度制采样序列。 + public static IReadOnlyList SamplePlannedTrajectory( + PlannedTrajectory trajectory, + double servoPeriodSeconds, + double speedRatio) + { + ArgumentNullException.ThrowIfNull(trajectory); + ValidatePlannedTrajectoryInputs(trajectory, servoPeriodSeconds, speedRatio); + + var durationSeconds = trajectory.WaypointTimes[^1]; + var trajectoryStepSeconds = servoPeriodSeconds * speedRatio; + var sampleCount = CalculateSampleCount(durationSeconds, trajectoryStepSeconds); + var spline = RebuildSpline(trajectory); + var samples = new List((int)Math.Min(sampleCount, int.MaxValue)); + + for (long sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + // 直接在连续样条上按 trajectoryTime 取点,避免先离散再线性回采样引入局部跃度尖峰。 + var sendTime = sampleIndex * servoPeriodSeconds; + var trajectoryTime = Math.Min(sendTime * speedRatio, durationSeconds); + var joints = spline.Evaluate(trajectoryTime).Select(RadiansToDegrees).ToArray(); + samples.Add(new J519SendSample(sampleIndex, sendTime, trajectoryTime, speedRatio, joints)); + } + + return samples; + } + + /// + /// 根据 J519 伺服周期和 speed_ratio 生成完整实发采样序列。 + /// + /// 规划层稠密关节轨迹,每行格式为 [time, j1..jN],关节单位为弧度。 + /// 规划轨迹总时长,单位为秒。 + /// J519 物理发送周期,单位为秒。 + /// 速度倍率;只缩放轨迹采样时间,不改变物理发送周期。 + /// 按 J519 发送周期排列的角度制采样序列。 + public static IReadOnlyList SampleDenseJointTrajectory( + IReadOnlyList> denseJointTrajectory, + double durationSeconds, + double servoPeriodSeconds, + double speedRatio) + { + ArgumentNullException.ThrowIfNull(denseJointTrajectory); + ValidateInputs(denseJointTrajectory, durationSeconds, servoPeriodSeconds, speedRatio); + + var trajectoryStepSeconds = servoPeriodSeconds * speedRatio; + var sampleCount = CalculateSampleCount(durationSeconds, trajectoryStepSeconds); + var samples = new List((int)Math.Min(sampleCount, int.MaxValue)); + var segmentIndex = 0; + + for (long sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + // J519 物理周期固定,speed_ratio 只用于把发送时间映射回原始轨迹时间。 + var sendTime = sampleIndex * servoPeriodSeconds; + var trajectoryTime = Math.Min(sendTime * speedRatio, durationSeconds); + var joints = SampleDenseJointTrajectoryDegrees(denseJointTrajectory, trajectoryTime, ref segmentIndex); + samples.Add(new J519SendSample(sampleIndex, sendTime, trajectoryTime, speedRatio, joints)); + } + + return samples; + } + + /// + /// 按原始轨迹时长和 speed_ratio 后的轨迹时间步长计算 J519 实发采样数。 + /// + /// 规划轨迹总时长,单位为秒。 + /// 每个物理发送周期对应的轨迹时间步长,单位为秒。 + /// 包含终点钳制周期的采样点数量。 + public static long CalculateSampleCount(double durationSeconds, double trajectoryStepSeconds) + { + if (durationSeconds < 0.0) + { + throw new ArgumentOutOfRangeException(nameof(durationSeconds), "轨迹时长不能为负数。"); + } + + if (trajectoryStepSeconds <= 0.0 || double.IsNaN(trajectoryStepSeconds) || double.IsInfinity(trajectoryStepSeconds)) + { + throw new ArgumentOutOfRangeException(nameof(trajectoryStepSeconds), "轨迹采样步长必须是有限正数。"); + } + + // 非周期整数倍时多保留一个终点钳制周期,和真实 J519 下发序列保持一致。 + return (long)Math.Ceiling(Math.Max(0.0, (durationSeconds / trajectoryStepSeconds) - 1e-9)) + 1; + } + + /// + /// 构造实发时间映射文本行,格式为 sample_index + send_time + trajectory_time + final_speed_ratio。 + /// + /// 待写出的 J519 实发采样点。 + /// 与 ActualSendTiming.txt 兼容的数值行。 + public static IReadOnlyList BuildTimingRow(J519SendSample sample) + { + ArgumentNullException.ThrowIfNull(sample); + return + [ + sample.SampleIndex, + Math.Round(sample.SendTime, 6), + Math.Round(sample.TrajectoryTime, 6), + Math.Round(sample.SpeedRatio, 6) + ]; + } + + /// + /// 构造带倍率诊断信息的实发时间映射文本行。 + /// + /// 待写出的 J519 实发采样点。 + /// 用户请求的执行倍率。 + /// 历史诊断字段;当前执行侧不自动改写 speed_ratio,因此正常为 0。 + /// 前 4 列兼容 ActualSendTiming.txt,后续列记录请求倍率和倍率改写次数。 + public static IReadOnlyList BuildTimingRow( + J519SendSample sample, + double requestedSpeedRatio, + int stretchIterationCount) + { + if (requestedSpeedRatio <= 0.0 || double.IsNaN(requestedSpeedRatio) || double.IsInfinity(requestedSpeedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(requestedSpeedRatio), "请求 speed_ratio 必须是有限正数。"); + } + + if (stretchIterationCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(stretchIterationCount), "倍率改写次数不能为负数。"); + } + + var legacyRow = BuildTimingRow(sample); + return + [ + legacyRow[0], + legacyRow[1], + legacyRow[2], + legacyRow[3], + Math.Round(requestedSpeedRatio, 6), + stretchIterationCount + ]; + } + + /// + /// 构造相邻发送点之间的角度制跃度统计行。 + /// + /// 上一帧发送时间,单位为秒。 + /// 当前帧发送时间,单位为秒。 + /// 上一帧角度制关节目标。 + /// 当前帧角度制关节目标。 + /// 上一帧关节速度,调用后更新为当前帧速度。 + /// 上一帧关节加速度,调用后更新为当前帧加速度。 + /// 与 ActualSendJerkStats.txt 兼容的数值行。 + public static IReadOnlyList BuildJerkRow( + double previousTime, + double currentTime, + IReadOnlyList previousJoints, + IReadOnlyList currentJoints, + ref double[]? previousVelocity, + ref double[]? previousAcceleration) + { + ArgumentNullException.ThrowIfNull(previousJoints); + ArgumentNullException.ThrowIfNull(currentJoints); + + var dt = currentTime - previousTime; + if (dt <= 0.0) + { + dt = 1e-9; + } + + var jointCount = currentJoints.Count; + var currentVelocity = new double[jointCount]; + var currentAcceleration = new double[jointCount]; + var currentJerk = new double[jointCount]; + var maxAbsJerk = 0.0; + + for (var index = 0; index < jointCount; index++) + { + currentVelocity[index] = (currentJoints[index] - previousJoints[index]) / dt; + if (previousVelocity is not null) + { + currentAcceleration[index] = (currentVelocity[index] - previousVelocity[index]) / dt; + } + + if (previousAcceleration is not null) + { + currentJerk[index] = (currentAcceleration[index] - previousAcceleration[index]) / dt; + maxAbsJerk = Math.Max(maxAbsJerk, Math.Abs(currentJerk[index])); + } + } + + previousVelocity = currentVelocity; + previousAcceleration = currentAcceleration; + + var row = new double[jointCount + 4]; + row[0] = Math.Round(previousTime, 6); + row[1] = Math.Round(currentTime, 6); + row[2] = Math.Round(dt, 6); + row[3] = Math.Round(maxAbsJerk, 6); + for (var index = 0; index < jointCount; index++) + { + row[index + 4] = Math.Round(currentJerk[index], 6); + } + + return row; + } + + /// + /// 在稠密关节轨迹上按时间线性插值,并转换成 J519 下发使用的角度制目标。 + /// + private static double[] SampleDenseJointTrajectoryDegrees( + IReadOnlyList> denseJointTrajectory, + double trajectoryTime, + ref int segmentIndex) + { + if (denseJointTrajectory.Count == 1 || trajectoryTime <= denseJointTrajectory[0][0]) + { + return denseJointTrajectory[0].Skip(1).Select(RadiansToDegrees).ToArray(); + } + + var lastIndex = denseJointTrajectory.Count - 1; + if (trajectoryTime >= denseJointTrajectory[lastIndex][0]) + { + return denseJointTrajectory[lastIndex].Skip(1).Select(RadiansToDegrees).ToArray(); + } + + while (segmentIndex < lastIndex - 1 && denseJointTrajectory[segmentIndex + 1][0] < trajectoryTime) + { + segmentIndex++; + } + + var start = denseJointTrajectory[segmentIndex]; + var end = denseJointTrajectory[segmentIndex + 1]; + var startTime = start[0]; + var endTime = end[0]; + var segmentDuration = endTime - startTime; + var alpha = segmentDuration <= 0.0 ? 0.0 : (trajectoryTime - startTime) / segmentDuration; + var joints = new double[start.Count - 1]; + for (var index = 0; index < joints.Length; index++) + { + joints[index] = RadiansToDegrees(start[index + 1] + ((end[index + 1] - start[index + 1]) * alpha)); + } + + return joints; + } + + /// + /// 校验 J519 实发采样的基础输入,避免错误时间轴进入运行时链路。 + /// + private static void ValidateInputs( + IReadOnlyList> denseJointTrajectory, + double durationSeconds, + double servoPeriodSeconds, + double speedRatio) + { + if (denseJointTrajectory.Count == 0) + { + throw new InvalidOperationException("稠密关节轨迹为空。"); + } + + if (durationSeconds < 0.0) + { + throw new ArgumentOutOfRangeException(nameof(durationSeconds), "轨迹时长不能为负数。"); + } + + if (servoPeriodSeconds <= 0.0 || double.IsNaN(servoPeriodSeconds) || double.IsInfinity(servoPeriodSeconds)) + { + throw new ArgumentOutOfRangeException(nameof(servoPeriodSeconds), "J519 伺服周期必须是有限正数。"); + } + + if (speedRatio <= 0.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(speedRatio), "speed_ratio 必须是有限正数。"); + } + } + + /// + /// 校验连续规划轨迹采样入口的基础输入,避免异常时间轴进入最终发送队列。 + /// + private static void ValidatePlannedTrajectoryInputs( + PlannedTrajectory trajectory, + double servoPeriodSeconds, + double speedRatio) + { + if (trajectory.WaypointTimes.Count < 2) + { + throw new InvalidOperationException("规划轨迹至少需要两个路点才能重建连续样条。"); + } + + if (trajectory.WaypointTimes[^1] < 0.0) + { + throw new ArgumentOutOfRangeException(nameof(trajectory), "规划轨迹总时长不能为负数。"); + } + + if (servoPeriodSeconds <= 0.0 || double.IsNaN(servoPeriodSeconds) || double.IsInfinity(servoPeriodSeconds)) + { + throw new ArgumentOutOfRangeException(nameof(servoPeriodSeconds), "J519 伺服周期必须是有限正数。"); + } + + if (speedRatio <= 0.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(speedRatio), "speed_ratio 必须是有限正数。"); + } + } + + /// + /// 从规划轨迹重建连续三次样条,供飞拍最终发送队列直接采样。 + /// + private static CubicSplineInterpolator RebuildSpline(PlannedTrajectory trajectory) + { + var times = trajectory.WaypointTimes.ToArray(); + var values = trajectory.PlannedWaypoints.Select(static waypoint => waypoint.Positions.ToArray()).ToArray(); + return new CubicSplineInterpolator(times, values); + } + + /// + /// 角度单位转换:rad -> deg。 + /// + private static double RadiansToDegrees(double radians) + { + return radians * 180.0 / Math.PI; + } +} diff --git a/src/Flyshot.Core.Planning/Sampling/TrajectoryLimitValidator.cs b/src/Flyshot.Core.Planning/Sampling/TrajectoryLimitValidator.cs new file mode 100644 index 0000000..3d0a991 --- /dev/null +++ b/src/Flyshot.Core.Planning/Sampling/TrajectoryLimitValidator.cs @@ -0,0 +1,220 @@ +using Flyshot.Core.Domain; + +namespace Flyshot.Core.Planning.Sampling; + +/// +/// 对最终生成的关节轨迹点做速度、加速度和 Jerk 离散复核。 +/// +public static class TrajectoryLimitValidator +{ + /// + /// 离散差分校验允许的默认浮点容差倍率。 + /// + public const double DefaultLimitTolerance = 1.000001; + + /// + /// 校验弧度制稠密关节轨迹是否满足机器人关节限制。 + /// + /// 机器人约束配置。 + /// 稠密轨迹行,格式为 time + 关节弧度。 + /// 限值容差倍率,用于过滤浮点舍入误差。 + /// 诊断用轨迹名称。 + /// 是否校验离散 jerk;飞拍链路可临时关闭,仅保留速度/加速度约束。 + public static void ValidateDenseJointTrajectory( + RobotProfile robot, + IReadOnlyList> rows, + double toleranceMultiplier = DefaultLimitTolerance, + string? trajectoryName = null, + bool validateJerk = true) + { + ArgumentNullException.ThrowIfNull(robot); + ArgumentNullException.ThrowIfNull(rows); + ValidateTolerance(toleranceMultiplier); + ValidateRows(robot, rows, toleranceMultiplier, trajectoryName ?? "dense-joint-trajectory", validateJerk); + } + + /// + /// 校验 J519 实际发送采样点是否满足机器人关节限制。 + /// + /// 机器人约束配置。 + /// J519 发送采样点,关节单位为角度。 + /// 限值容差倍率,用于过滤浮点舍入误差。 + /// 诊断用轨迹名称。 + /// 是否校验离散 jerk;飞拍链路可临时关闭,仅保留速度/加速度约束。 + /// 是否按原始 jerk 限值做硬约束;false 时保留历史宽容口径。 + public static void ValidateJ519SendSamples( + RobotProfile robot, + IReadOnlyList samples, + double toleranceMultiplier = DefaultLimitTolerance, + string? trajectoryName = null, + bool validateJerk = false, + bool strictJerkTolerance = true) + { + ArgumentNullException.ThrowIfNull(robot); + ArgumentNullException.ThrowIfNull(samples); + ValidateTolerance(toleranceMultiplier); + + var rows = new List>(samples.Count); + foreach (var sample in samples) + { + var row = new double[robot.DegreesOfFreedom + 1]; + row[0] = sample.SendTime; + for (var index = 0; index < robot.DegreesOfFreedom; index++) + { + row[index + 1] = DegreesToRadians(sample.JointsDegrees[index]); + } + + rows.Add(row); + } + + ValidateRows( + robot, + rows, + toleranceMultiplier, + trajectoryName ?? "j519-send-trajectory", + validateJerk, + strictJerkTolerance); + } + + /// + /// 校验容差倍率必须为有限正数。 + /// + private static void ValidateTolerance(double toleranceMultiplier) + { + if (toleranceMultiplier <= 0.0 || double.IsNaN(toleranceMultiplier) || double.IsInfinity(toleranceMultiplier)) + { + throw new ArgumentOutOfRangeException(nameof(toleranceMultiplier), "限值容差倍率必须是有限正数。"); + } + } + + /// + /// 对弧度制轨迹行执行统一的离散差分限幅校验。 + /// + private static void ValidateRows( + RobotProfile robot, + IReadOnlyList> rows, + double toleranceMultiplier, + string trajectoryName, + bool validateJerk, + bool strictJerkTolerance = false) + { + double? previousTime = null; + double[]? previousPositions = null; + double[]? previousVelocities = null; + double[]? previousAccelerations = null; + + for (var rowIndex = 0; rowIndex < rows.Count; rowIndex++) + { + var row = rows[rowIndex]; + if (row.Count < robot.DegreesOfFreedom + 1) + { + throw new InvalidOperationException( + $"轨迹 {trajectoryName} 第 {rowIndex + 1} 行关节列数量不足,期望至少 {robot.DegreesOfFreedom + 1} 列,实际 {row.Count} 列。"); + } + + var currentTime = row[0]; + var currentPositions = new double[robot.DegreesOfFreedom]; + for (var index = 0; index < robot.DegreesOfFreedom; index++) + { + currentPositions[index] = row[index + 1]; + } + + if (previousTime is not null && previousPositions is not null) + { + var dt = currentTime - previousTime.Value; + if (dt <= 0.0) + { + throw new InvalidOperationException( + $"轨迹 {trajectoryName} 时间戳必须严格递增,第 {rowIndex + 1} 行 dt={dt:F9}s。"); + } + + var currentVelocities = new double[robot.DegreesOfFreedom]; + var currentAccelerations = new double[robot.DegreesOfFreedom]; + for (var index = 0; index < robot.DegreesOfFreedom; index++) + { + var jointLimit = robot.JointLimits[index]; + currentVelocities[index] = (currentPositions[index] - previousPositions[index]) / dt; + ThrowIfExceeded( + trajectoryName, + rowIndex, + previousTime.Value, + currentTime, + jointLimit.JointName, + "速度", + currentVelocities[index], + jointLimit.VelocityLimit, + toleranceMultiplier); + + currentAccelerations[index] = previousVelocities is null + ? 0.0 + : (currentVelocities[index] - previousVelocities[index]) / dt; + ThrowIfExceeded( + trajectoryName, + rowIndex, + previousTime.Value, + currentTime, + jointLimit.JointName, + "加速度", + currentAccelerations[index], + jointLimit.AccelerationLimit, + toleranceMultiplier); + + if (validateJerk && previousAccelerations is not null) + { + var jerk = (currentAccelerations[index] - previousAccelerations[index]) / dt; + ThrowIfExceeded( + trajectoryName, + rowIndex, + previousTime.Value, + currentTime, + jointLimit.JointName, + "Jerk", + jerk, + jointLimit.JerkLimit, + strictJerkTolerance ? toleranceMultiplier : toleranceMultiplier * 4.0); + } + } + + previousVelocities = currentVelocities; + previousAccelerations = currentAccelerations; + } + + previousTime = currentTime; + previousPositions = currentPositions; + } + } + + /// + /// 当某个差分指标超过限制时抛出包含关节和时间窗的诊断异常。 + /// + private static void ThrowIfExceeded( + string trajectoryName, + int rowIndex, + double previousTime, + double currentTime, + string jointName, + string metricName, + double actual, + double limit, + double toleranceMultiplier) + { + var absActual = Math.Abs(actual); + var effectiveLimit = limit * toleranceMultiplier; + if (absActual <= effectiveLimit) + { + return; + } + + throw new InvalidOperationException( + $"轨迹 {trajectoryName} 第 {rowIndex + 1} 行 {jointName} {metricName}超限: " + + $"time={previousTime:F6}->{currentTime:F6}s, actual={actual:F6}, limit={limit:F6}, ratio={absActual / limit:F4}。"); + } + + /// + /// 角度单位转换:deg -> rad。 + /// + private static double DegreesToRadians(double degrees) + { + return degrees * Math.PI / 180.0; + } +} diff --git a/src/Flyshot.Core.Planning/Sampling/TrajectorySampler.cs b/src/Flyshot.Core.Planning/Sampling/TrajectorySampler.cs index 35f682c..1d28dff 100644 --- a/src/Flyshot.Core.Planning/Sampling/TrajectorySampler.cs +++ b/src/Flyshot.Core.Planning/Sampling/TrajectorySampler.cs @@ -19,7 +19,8 @@ public static class TrajectorySampler public static IReadOnlyList> SampleJointTrajectory( PlannedTrajectory trajectory, double samplePeriod = 0.016, - int decimals = 6) + int decimals = 6, + bool smoothStartStop = false) { var spline = RebuildSpline(trajectory); double duration = trajectory.WaypointTimes[^1]; @@ -28,7 +29,10 @@ public static class TrajectorySampler foreach (var t in times) { - var pos = spline.Evaluate(t); + var evaluationTime = smoothStartStop + ? MapSmoothStartStopEvaluationTime(t, duration) + : t; + var pos = spline.Evaluate(evaluationTime); var row = new List(pos.Length + 1); row.Add(Math.Round(t, decimals)); foreach (var value in pos) @@ -49,7 +53,8 @@ public static class TrajectorySampler PlannedTrajectory trajectory, RobotKinematicsModel kinematicsModel, double samplePeriod = 0.016, - int decimals = 6) + int decimals = 6, + bool smoothStartStop = false) { var spline = RebuildSpline(trajectory); double duration = trajectory.WaypointTimes[^1]; @@ -58,7 +63,10 @@ public static class TrajectorySampler foreach (var t in times) { - var jointPos = spline.Evaluate(t); + var evaluationTime = smoothStartStop + ? MapSmoothStartStopEvaluationTime(t, duration) + : t; + var jointPos = spline.Evaluate(evaluationTime); var pose = RobotKinematics.ForwardKinematics(kinematicsModel, jointPos); var row = new List(pose.Length + 1); row.Add(Math.Round(t, decimals)); @@ -103,4 +111,26 @@ public static class TrajectorySampler return times; } + + /// + /// 把线性采样时间映射为整段平滑起停的评估时间。 + /// 使用 7 次 smootherstep 时间律,让起点和终点的一到三阶导都自然收敛到 0。 + /// + private static double MapSmoothStartStopEvaluationTime(double sampleTime, double duration) + { + if (duration <= 0.0) + { + return 0.0; + } + + var normalizedTime = Math.Clamp(sampleTime / duration, 0.0, 1.0); + var u2 = normalizedTime * normalizedTime; + var u3 = u2 * normalizedTime; + var u4 = u3 * normalizedTime; + var u5 = u4 * normalizedTime; + var u6 = u5 * normalizedTime; + var u7 = u6 * normalizedTime; + var progress = (35.0 * u4) - (84.0 * u5) + (70.0 * u6) - (20.0 * u7); + return duration * progress; + } } diff --git a/src/Flyshot.Core.Planning/Sampling/TriggerSampleBinder.cs b/src/Flyshot.Core.Planning/Sampling/TriggerSampleBinder.cs new file mode 100644 index 0000000..3f99845 --- /dev/null +++ b/src/Flyshot.Core.Planning/Sampling/TriggerSampleBinder.cs @@ -0,0 +1,134 @@ +using Flyshot.Core.Domain; + +namespace Flyshot.Core.Planning.Sampling; + +/// +/// 负责把理论触发事件绑定到实际 J519 发送 sample。 +/// 绑定规则为:先在理论触发时间前后固定时间窗内筛候选点,再优先选择关节差最小的 sample。 +/// +public static class TriggerSampleBinder +{ + /// + /// 当前触发绑定使用的固定近时窗半宽,单位为秒。 + /// 即:以理论触发时间为中心,在前后各 100ms 的范围内寻找最优 sample。 + /// + public const double TriggerBindingToleranceSeconds = 0.1; + + /// + /// 把一组触发事件绑定到采样后的 J519 sample。 + /// + /// 待绑定的触发事件。 + /// J519 实发 sample。 + /// 在最佳绑定 sample 基础上继续向后偏移的命令周期数。 + /// 按最终 sampleIndex 排序的绑定结果。 + public static IReadOnlyList Bind( + IReadOnlyList triggers, + IReadOnlyList samples, + int sampleIndexOffsetCycles = 0) + { + ArgumentNullException.ThrowIfNull(triggers); + ArgumentNullException.ThrowIfNull(samples); + if (sampleIndexOffsetCycles < 0) + { + throw new ArgumentOutOfRangeException(nameof(sampleIndexOffsetCycles), "触发 sample 偏移周期不能为负数。"); + } + + var bindings = new List(triggers.Count); + if (triggers.Count == 0 || samples.Count == 0) + { + return bindings; + } + + var lastSampleIndex = samples.Count - 1; + foreach (var trigger in triggers) + { + var fallbackIndex = FindClosestTrajectoryTimeSampleIndex(samples, trigger.TriggerTime); + var candidateStart = Math.Max(0, fallbackIndex - 1); + var candidateEnd = Math.Min(lastSampleIndex, fallbackIndex + 1); + var bestIndex = fallbackIndex; + var bestScore = double.PositiveInfinity; + var foundInWindow = false; + + for (var sampleIndex = candidateStart; sampleIndex <= candidateEnd; sampleIndex++) + { + var sample = samples[sampleIndex]; + if (Math.Abs(sample.TrajectoryTime - trigger.TriggerTime) > TriggerBindingToleranceSeconds) + { + continue; + } + + foundInWindow = true; + var score = trigger.ReferenceJointsDegrees is { Count: > 0 } + ? ComputeJointDifferenceScore(trigger.ReferenceJointsDegrees, sample.JointsDegrees) + : Math.Abs(sample.TrajectoryTime - trigger.TriggerTime); + if (score < bestScore) + { + bestScore = score; + bestIndex = sampleIndex; + } + } + + var finalIndex = Math.Min(lastSampleIndex, bestIndex + sampleIndexOffsetCycles); + bindings.Add(new TriggerSampleBinding(trigger, samples[finalIndex], finalIndex, foundInWindow)); + } + + return bindings + .OrderBy(static binding => binding.SampleIndex) + .ThenBy(static binding => binding.Trigger.WaypointIndex) + .ToArray(); + } + + /// + /// 按轨迹时间找到最接近理论触发时刻的 sample。 + /// + public static int FindClosestTrajectoryTimeSampleIndex(IReadOnlyList samples, double triggerTime) + { + ArgumentNullException.ThrowIfNull(samples); + + var bestIndex = 0; + var bestDelta = double.PositiveInfinity; + for (var sampleIndex = 0; sampleIndex < samples.Count; sampleIndex++) + { + var delta = Math.Abs(samples[sampleIndex].TrajectoryTime - triggerTime); + if (delta < bestDelta) + { + bestDelta = delta; + bestIndex = sampleIndex; + } + } + + return bestIndex; + } + + /// + /// 计算参考示教点与候选 sample 的关节差评分,使用平方和即可稳定排序。 + /// + public static double ComputeJointDifferenceScore( + IReadOnlyList? referenceJointsDegrees, + IReadOnlyList sampleJointsDegrees) + { + if (referenceJointsDegrees is null || referenceJointsDegrees.Count == 0) + { + return 0.0; + } + + var jointCount = Math.Min(referenceJointsDegrees.Count, sampleJointsDegrees.Count); + var score = 0.0; + for (var index = 0; index < jointCount; index++) + { + var delta = sampleJointsDegrees[index] - referenceJointsDegrees[index]; + score += delta * delta; + } + + return score; + } +} + +/// +/// 表示一个理论触发事件最终绑定到哪个 J519 sample 的结果。 +/// +public sealed record TriggerSampleBinding( + TrajectoryDoEvent Trigger, + J519SendSample Sample, + int SampleIndex, + bool FoundInWindow); diff --git a/src/Flyshot.Core.Planning/SelfAdaptIcspPlanner.cs b/src/Flyshot.Core.Planning/SelfAdaptIcspPlanner.cs index f03b6a1..aef327a 100644 --- a/src/Flyshot.Core.Planning/SelfAdaptIcspPlanner.cs +++ b/src/Flyshot.Core.Planning/SelfAdaptIcspPlanner.cs @@ -1,182 +1,213 @@ -using Flyshot.Core.Domain; - -namespace Flyshot.Core.Planning; - -/// -/// 在 ICSP 外层包裹补中点策略,实现 self-adapt-icsp 行为。 -/// -/// 为什么需要这层? -/// --- -/// 逆向分析已经指出:原系统里普通 icsp 若仍有段 scale > 1,不会直接返回未收敛结果; -/// 配置中还明确存在 adapt_icsp_try_num。本层把“超限段统一插入中点后再重规划”的逻辑显式落地, -/// 补上 demo 缺失的失败恢复路径。 -/// -/// 补点策略: -/// --- -/// 对当前所有 scale > 1 + tolerance 的段统一插入关节空间中点,然后把新路点集交给 ICSPPlanner -/// 重新规划。这种"先把明显病灶都降一档,再整体重规划"的策略比逐段拆分更稳定, -/// 也更符合服务端 adapt_icsp_try_num 的意图。 -/// -public sealed class SelfAdaptIcspPlanner -{ - /// - /// 判定段是否超限的数值容差,过滤浮点噪声。 - /// - public const double ScaleTolerance = 5e-4; - - private readonly ICspPlanner _innerPlanner = new(); - - /// - /// 执行自适应 ICSP 规划,允许在超限段插入中点后重试。 - /// - /// 轨迹规划请求。 - /// 最大补点重试次数(默认 5)。 - /// 规划后的轨迹结果。 - /// 超过最大重试次数仍未收敛时抛出。 - public PlannedTrajectory Plan(TrajectoryRequest request, int adaptIcspTryNum = 5) - { - ArgumentNullException.ThrowIfNull(request); - - var currentWaypoints = request.Program.Waypoints.ToArray(); - var currentShotFlags = request.Program.ShotFlags.ToArray(); - var currentOffsets = request.Program.OffsetValues.ToArray(); - var currentAddrs = request.Program.AddressGroups.ToArray(); - int originalWaypointCount = currentWaypoints.Length; - - if (originalWaypointCount < 4) - { - throw new ArgumentException("ICSP 至少需要 4 个示教点。", nameof(request)); - } - - var currentProgram = BuildProgram(request.Program.Name, currentWaypoints, currentShotFlags, currentOffsets, currentAddrs); - var currentRequest = new TrajectoryRequest( - robot: request.Robot, - program: currentProgram, - method: PlanningMethod.SelfAdaptIcsp, - moveToStart: request.MoveToStart, - saveTrajectoryArtifacts: request.SaveTrajectoryArtifacts, - useCache: request.UseCache); - - PlannedTrajectory? lastTrajectory = null; - int maxAttempts = Math.Max(0, adaptIcspTryNum); - - for (int attempt = 0; attempt <= maxAttempts; attempt++) - { - var trajectory = _innerPlanner.Plan(currentRequest); - lastTrajectory = trajectory; - - var badSegments = new List(); - for (int seg = 0; seg < trajectory.SegmentScales.Count; seg++) - { - if (trajectory.SegmentScales[seg] > 1.0 + ScaleTolerance) - { - badSegments.Add(seg); - } - } - - if (badSegments.Count == 0) - { - // 所有段都满足约束,收敛成功。返回包含补中点后路点的轨迹。 - return new PlannedTrajectory( - robot: trajectory.Robot, - originalProgram: request.Program, - plannedWaypoints: currentWaypoints, - waypointTimes: trajectory.WaypointTimes, - segmentDurations: trajectory.SegmentDurations, - segmentScales: trajectory.SegmentScales, - method: PlanningMethod.SelfAdaptIcsp, - iterations: trajectory.Iterations, - threshold: trajectory.Threshold); - } - - if (attempt >= maxAttempts) - { - break; - } - - // 对超限段插入中点,并同步扩展 shot 元数据。 - (currentWaypoints, currentShotFlags, currentOffsets, currentAddrs) = - InsertSegmentMidpoints(currentWaypoints, currentShotFlags, currentOffsets, currentAddrs, badSegments); - - currentProgram = BuildProgram(request.Program.Name, currentWaypoints, currentShotFlags, currentOffsets, currentAddrs); - currentRequest = new TrajectoryRequest( - robot: request.Robot, - program: currentProgram, - method: PlanningMethod.SelfAdaptIcsp, - moveToStart: request.MoveToStart, - saveTrajectoryArtifacts: request.SaveTrajectoryArtifacts, - useCache: request.UseCache); - } - - double maxScale = lastTrajectory?.SegmentScales.Max() ?? double.NaN; - throw new InvalidOperationException( - $"self-adapt ICSP 在 {adaptIcspTryNum} 轮补点后仍未收敛,最大段缩放因子={maxScale:F6}。"); - } - - /// - /// 用当前路点集和元数据构造 FlyshotProgram。 - /// - private static FlyshotProgram BuildProgram( - string name, - JointWaypoint[] waypoints, - bool[] shotFlags, - int[] offsets, - IoAddressGroup[] addrs) - { - return new FlyshotProgram( - name: name, - waypoints: waypoints, - shotFlags: shotFlags, - offsetValues: offsets, - addressGroups: addrs); - } - - /// - /// 对超限段统一插入关节空间中点,并同步扩展 shot 元数据。 - /// 新插入的路点默认 shotFlag=false、offset=0、addr=空。 - /// - private static (JointWaypoint[] waypoints, bool[] shotFlags, int[] offsets, IoAddressGroup[] addrs) - InsertSegmentMidpoints( - JointWaypoint[] waypoints, - bool[] shotFlags, - int[] offsets, - IoAddressGroup[] addrs, - List badSegments) - { - if (badSegments.Count == 0) - { - return (waypoints, shotFlags, offsets, addrs); - } - - var badSet = new HashSet(badSegments); - var newWaypoints = new List(waypoints.Length + badSegments.Count); - var newShotFlags = new List(waypoints.Length + badSegments.Count); - var newOffsets = new List(waypoints.Length + badSegments.Count); - var newAddrs = new List(waypoints.Length + badSegments.Count); - - newWaypoints.Add(waypoints[0]); - newShotFlags.Add(shotFlags[0]); - newOffsets.Add(offsets[0]); - newAddrs.Add(addrs[0]); - - for (int seg = 0; seg < waypoints.Length - 1; seg++) - { - if (badSet.Contains(seg)) - { - var mid = new JointWaypoint( - waypoints[seg].Positions.Zip(waypoints[seg + 1].Positions, (a, b) => (a + b) / 2.0)); - newWaypoints.Add(mid); - newShotFlags.Add(false); - newOffsets.Add(0); - newAddrs.Add(new IoAddressGroup(Array.Empty())); - } - - newWaypoints.Add(waypoints[seg + 1]); - newShotFlags.Add(shotFlags[seg + 1]); - newOffsets.Add(offsets[seg + 1]); - newAddrs.Add(addrs[seg + 1]); - } - - return (newWaypoints.ToArray(), newShotFlags.ToArray(), newOffsets.ToArray(), newAddrs.ToArray()); - } -} +using Flyshot.Core.Domain; +using Microsoft.Extensions.Logging; + +namespace Flyshot.Core.Planning; + +/// +/// 在 ICSP 外层包裹补中点策略,实现 self-adapt-icsp 行为。 +/// +/// 为什么需要这层? +/// --- +/// 逆向分析已经指出:原系统里普通 icsp 若仍有段 scale > 1,不会直接返回未收敛结果; +/// 配置中还明确存在 adapt_icsp_try_num。本层把"超限段统一插入中点后再重规划"的逻辑显式落地, +/// 补上 demo 缺失的失败恢复路径。 +/// +/// 补点策略: +/// --- +/// 对当前所有 scale > 1 + tolerance 的段统一插入关节空间中点,然后把新路点集交给 ICSPPlanner +/// 重新规划。这种"先把明显病灶都降一档,再整体重规划"的策略比逐段拆分更稳定, +/// 也更符合服务端 adapt_icsp_try_num 的意图。 +/// +public sealed class SelfAdaptIcspPlanner +{ + /// + /// 判定段是否超限的数值容差,过滤浮点噪声。 + /// + public const double ScaleTolerance = 5e-4; + + private readonly ICspPlanner _innerPlanner; + private readonly ILogger? _logger; + + /// + /// 初始化 SelfAdaptIcspPlanner。 + /// + /// 日志记录器;允许 null。 + public SelfAdaptIcspPlanner(ILogger? logger = null) + { + _innerPlanner = new ICspPlanner(enforceFinalScale: false, logger: null); + _logger = logger; + } + + /// + /// 执行自适应 ICSP 规划,允许在超限段插入中点后重试。 + /// + /// 轨迹规划请求。 + /// 最大补点重试次数(默认 5)。 + /// 规划后的轨迹结果。 + /// 超过最大重试次数仍未收敛时抛出。 + public PlannedTrajectory Plan(TrajectoryRequest request, int adaptIcspTryNum = 5) + { + ArgumentNullException.ThrowIfNull(request); + + var currentWaypoints = request.Program.Waypoints.ToArray(); + var currentShotFlags = request.Program.ShotFlags.ToArray(); + var currentOffsets = request.Program.OffsetValues.ToArray(); + var currentAddrs = request.Program.AddressGroups.ToArray(); + int originalWaypointCount = currentWaypoints.Length; + + if (originalWaypointCount < 4) + { + throw new ArgumentException("ICSP 至少需要 4 个示教点。", nameof(request)); + } + + _logger?.LogInformation( + "SelfAdaptICSP 规划开始: 名称={Name}, 原始路点数={WaypointCount}, 最大补点次数={MaxAttempts}", + request.Program.Name, originalWaypointCount, adaptIcspTryNum); + + var currentProgram = BuildProgram(request.Program.Name, currentWaypoints, currentShotFlags, currentOffsets, currentAddrs); + var currentRequest = new TrajectoryRequest( + robot: request.Robot, + program: currentProgram, + method: PlanningMethod.SelfAdaptIcsp, + moveToStart: request.MoveToStart, + saveTrajectoryArtifacts: request.SaveTrajectoryArtifacts, + useCache: request.UseCache); + + PlannedTrajectory? lastTrajectory = null; + int maxAttempts = Math.Max(0, adaptIcspTryNum); + + for (int attempt = 0; attempt <= maxAttempts; attempt++) + { + var trajectory = _innerPlanner.Plan(currentRequest); + lastTrajectory = trajectory; + + var badSegments = new List(); + for (int seg = 0; seg < trajectory.SegmentScales.Count; seg++) + { + if (trajectory.SegmentScales[seg] > 1.0 + ScaleTolerance) + { + badSegments.Add(seg); + } + } + + if (badSegments.Count == 0) + { + _logger?.LogInformation( + "SelfAdaptICSP 规划完成: 名称={Name}, 补点轮数={Attempts}, 最终路点数={WaypointCount}, 迭代次数={Iterations}, 总时长={Duration:F4}s", + request.Program.Name, attempt, currentWaypoints.Length, trajectory.Iterations, trajectory.WaypointTimes[^1]); + // 所有段都满足约束,收敛成功。返回包含补中点后路点的轨迹。 + return new PlannedTrajectory( + robot: trajectory.Robot, + originalProgram: request.Program, + plannedWaypoints: currentWaypoints, + waypointTimes: trajectory.WaypointTimes, + segmentDurations: trajectory.SegmentDurations, + segmentScales: trajectory.SegmentScales, + method: PlanningMethod.SelfAdaptIcsp, + iterations: trajectory.Iterations, + threshold: trajectory.Threshold); + } + + _logger?.LogWarning( + "SelfAdaptICSP 第 {Attempt} 轮存在超限段: 超限段数={BadCount}, 段索引=[{Segments}], 最大缩放={MaxScale:F4}", + attempt, badSegments.Count, string.Join(", ", badSegments), trajectory.SegmentScales.Max()); + + if (attempt >= maxAttempts) + { + break; + } + + // 对超限段插入中点,并同步扩展 shot 元数据。 + int waypointCountBefore = currentWaypoints.Length; + (currentWaypoints, currentShotFlags, currentOffsets, currentAddrs) = + InsertSegmentMidpoints(currentWaypoints, currentShotFlags, currentOffsets, currentAddrs, badSegments); + + _logger?.LogDebug( + "SelfAdaptICSP 补中点: 路点数 {Before} -> {After}, 插入段=[{Segments}]", + waypointCountBefore, currentWaypoints.Length, string.Join(", ", badSegments)); + + currentProgram = BuildProgram(request.Program.Name, currentWaypoints, currentShotFlags, currentOffsets, currentAddrs); + currentRequest = new TrajectoryRequest( + robot: request.Robot, + program: currentProgram, + method: PlanningMethod.SelfAdaptIcsp, + moveToStart: request.MoveToStart, + saveTrajectoryArtifacts: request.SaveTrajectoryArtifacts, + useCache: request.UseCache); + } + + double maxScale = lastTrajectory?.SegmentScales.Max() ?? double.NaN; + _logger?.LogError( + "SelfAdaptICSP 规划失败: 名称={Name}, 在 {Attempts} 轮补点后仍未收敛, 最大段缩放因子={MaxScale:F6}", + request.Program.Name, adaptIcspTryNum, maxScale); + throw new InvalidOperationException( + $"self-adapt ICSP 在 {adaptIcspTryNum} 轮补点后仍未收敛,最大段缩放因子={maxScale:F6}。"); + } + + /// + /// 用当前路点集和元数据构造 FlyshotProgram。 + /// + private static FlyshotProgram BuildProgram( + string name, + JointWaypoint[] waypoints, + bool[] shotFlags, + int[] offsets, + IoAddressGroup[] addrs) + { + return new FlyshotProgram( + name: name, + waypoints: waypoints, + shotFlags: shotFlags, + offsetValues: offsets, + addressGroups: addrs); + } + + /// + /// 对超限段统一插入关节空间中点,并同步扩展 shot 元数据。 + /// 新插入的路点默认 shotFlag=false、offset=0、addr=空。 + /// + private static (JointWaypoint[] waypoints, bool[] shotFlags, int[] offsets, IoAddressGroup[] addrs) + InsertSegmentMidpoints( + JointWaypoint[] waypoints, + bool[] shotFlags, + int[] offsets, + IoAddressGroup[] addrs, + List badSegments) + { + if (badSegments.Count == 0) + { + return (waypoints, shotFlags, offsets, addrs); + } + + var badSet = new HashSet(badSegments); + var newWaypoints = new List(waypoints.Length + badSegments.Count); + var newShotFlags = new List(waypoints.Length + badSegments.Count); + var newOffsets = new List(waypoints.Length + badSegments.Count); + var newAddrs = new List(waypoints.Length + badSegments.Count); + + newWaypoints.Add(waypoints[0]); + newShotFlags.Add(shotFlags[0]); + newOffsets.Add(offsets[0]); + newAddrs.Add(addrs[0]); + + for (int seg = 0; seg < waypoints.Length - 1; seg++) + { + if (badSet.Contains(seg)) + { + var mid = new JointWaypoint( + waypoints[seg].Positions.Zip(waypoints[seg + 1].Positions, (a, b) => (a + b) / 2.0)); + newWaypoints.Add(mid); + newShotFlags.Add(false); + newOffsets.Add(0); + newAddrs.Add(new IoAddressGroup(Array.Empty())); + } + + newWaypoints.Add(waypoints[seg + 1]); + newShotFlags.Add(shotFlags[seg + 1]); + newOffsets.Add(offsets[seg + 1]); + newAddrs.Add(addrs[seg + 1]); + } + + return (newWaypoints.ToArray(), newShotFlags.ToArray(), newOffsets.ToArray(), newAddrs.ToArray()); + } +} diff --git a/src/Flyshot.Core.Triggering/Flyshot.Core.Triggering.csproj b/src/Flyshot.Core.Triggering/Flyshot.Core.Triggering.csproj index d3171ec..e0cc473 100644 --- a/src/Flyshot.Core.Triggering/Flyshot.Core.Triggering.csproj +++ b/src/Flyshot.Core.Triggering/Flyshot.Core.Triggering.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Flyshot.Core.Triggering/ShotTimelineBuilder.cs b/src/Flyshot.Core.Triggering/ShotTimelineBuilder.cs index f95778e..affbea5 100644 --- a/src/Flyshot.Core.Triggering/ShotTimelineBuilder.cs +++ b/src/Flyshot.Core.Triggering/ShotTimelineBuilder.cs @@ -1,5 +1,6 @@ using Flyshot.Core.Domain; using Flyshot.Core.Planning; +using Microsoft.Extensions.Logging; namespace Flyshot.Core.Triggering; @@ -7,16 +8,33 @@ namespace Flyshot.Core.Triggering; /// 根据规划轨迹和飞拍配置生成触发时间轴,把示教点上的 shot_flags / offset_values / addr /// 映射成带理论时间和离散化时间的 ShotEvent,以及可直接注入伺服流的 TrajectoryDoEvent。 /// +/// +/// 本构建器只负责“时间轴映射”和“事件打包”,不负责轨迹规划与运行时发送。 +/// 其中: +/// - ShotEvent 用于诊断、导出和日志观察; +/// - TrajectoryDoEvent 用于运行时伺服链路注入 DO 脉冲。 +/// public sealed class ShotTimelineBuilder { + /// + /// 将原始示教点索引映射到规划后时间轴上的时间戳解析器。 + /// private readonly WaypointTimestampResolver _resolver; + /// + /// 可选日志记录器,用于输出时间轴构建统计信息。 + /// + private readonly ILogger? _logger; + /// /// 初始化 ShotTimelineBuilder,依赖一个时间戳解析器来对齐补中点后的轨迹与原始示教点。 /// - public ShotTimelineBuilder(WaypointTimestampResolver resolver) + /// 时间戳解析器。 + /// 日志记录器;允许 null。 + public ShotTimelineBuilder(WaypointTimestampResolver resolver, ILogger? logger = null) { _resolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); + _logger = logger; } /// @@ -25,8 +43,20 @@ public sealed class ShotTimelineBuilder /// 规划后的轨迹(含补中点信息和机器人配置)。 /// IO 保持周期数(对应原系统的 io_keep_cycles)。 /// 稠密采样周期,用于离散化 sample_index 和 sample_time。 + /// 是否生成可注入伺服流的 DO 事件。 /// 包含 ShotEvent 和 TrajectoryDoEvent 的触发时间轴。 - public ShotTimeline Build(PlannedTrajectory trajectory, int holdCycles, TimeSpan samplePeriod) + /// 当 为 null 时抛出。 + /// + /// 当 小于 0,或 小于等于 0 时抛出。 + /// + /// + /// 时间计算规则: + /// - 理论触发时间:timestamp[i] + offset_values[i] * trigger_period; + /// - 离散采样索引:Round(trigger_time / sample_period); + /// - 离散采样时间:sample_index * sample_period。 + /// 该离散化与运行时 8ms 周期下发模型一致,可用于对齐导出与诊断。 + /// + public ShotTimeline Build(PlannedTrajectory trajectory, int holdCycles, TimeSpan samplePeriod, bool useDo = true) { ArgumentNullException.ThrowIfNull(trajectory); @@ -49,6 +79,7 @@ public sealed class ShotTimelineBuilder var shotEvents = new List(); var triggerTimeline = new List(); + // 遍历原始示教点,仅对 shot_flag=true 的点生成触发事件。 for (int i = 0; i < program.Waypoints.Count; i++) { if (!program.ShotFlags[i]) @@ -56,6 +87,7 @@ public sealed class ShotTimelineBuilder continue; } + // 先得到连续时间上的理论触发时刻,再映射到离散采样周期。 double triggerTime = timestamps[i] + program.OffsetValues[i] * triggerPeriodSeconds; int sampleIndex = (int)Math.Round(triggerTime / samplePeriodSeconds); double sampleTime = sampleIndex * samplePeriodSeconds; @@ -69,14 +101,26 @@ public sealed class ShotTimelineBuilder sampleTime: sampleTime, addressGroup: addressGroup)); - triggerTimeline.Add(new TrajectoryDoEvent( - waypointIndex: i, - triggerTime: triggerTime, - offsetCycles: program.OffsetValues[i], - holdCycles: holdCycles, - 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, + referenceJointsDegrees: program.Waypoints[i].Positions.Select(static radians => radians * 180.0 / Math.PI))); + } } + _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); } } diff --git a/src/Flyshot.Runtime.Common/Flyshot.Runtime.Common.csproj b/src/Flyshot.Runtime.Common/Flyshot.Runtime.Common.csproj index fa71b7a..82ae933 100644 --- a/src/Flyshot.Runtime.Common/Flyshot.Runtime.Common.csproj +++ b/src/Flyshot.Runtime.Common/Flyshot.Runtime.Common.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/src/Flyshot.Runtime.Common/IControllerRuntime.cs b/src/Flyshot.Runtime.Common/IControllerRuntime.cs new file mode 100644 index 0000000..c3ae7e5 --- /dev/null +++ b/src/Flyshot.Runtime.Common/IControllerRuntime.cs @@ -0,0 +1,123 @@ +using Flyshot.Core.Domain; + +namespace Flyshot.Runtime.Common; + +/// +/// 定义控制器运行时的最小状态与执行契约,供兼容层在不关心底层 Socket 细节的情况下调度轨迹。 +/// +public interface IControllerRuntime +{ + /// + /// 重置当前机器人模型并清空控制器运行时状态。 + /// + /// 当前机器人配置。 + /// 兼容层传入的机器人名称。 + void ResetRobot(RobotProfile robot, string robotName); + + /// + /// 选择当前活动控制器类型。 + /// + /// 是否使用仿真控制器。 + void SetActiveController(bool sim); + + /// + /// 建立到控制器 IP 的连接。 + /// + /// 控制器 IP。 + void Connect(string robotIp); + + /// + /// 断开当前控制器连接。 + /// + void Disconnect(); + + /// + /// 使能机器人并记录底层缓冲区大小。 + /// + /// 运行时缓冲区大小。 + void EnableRobot(int bufferSize); + + /// + /// 关闭机器人使能。 + /// + void DisableRobot(); + + /// + /// 停止当前运动。 + /// + void StopMove(); + + /// + /// 获取当前速度倍率。 + /// + /// 速度倍率。 + double GetSpeedRatio(); + + /// + /// 设置当前速度倍率。 + /// + /// 目标速度倍率。 + void SetSpeedRatio(double ratio); + + /// + /// 获取当前 TCP 坐标。 + /// + /// TCP 三维坐标。 + IReadOnlyList GetTcp(); + + /// + /// 设置当前 TCP 坐标。 + /// + /// TCP X。 + /// TCP Y。 + /// TCP Z。 + void SetTcp(double x, double y, double z); + + /// + /// 读取指定 IO 端口。 + /// + /// IO 端口。 + /// IO 类型。 + /// IO 当前值。 + bool GetIo(int port, string ioType); + + /// + /// 写入指定 IO 端口。 + /// + /// IO 端口。 + /// 目标 IO 值。 + /// IO 类型。 + void SetIo(int port, bool value, string ioType); + + /// + /// 获取当前关节位置。 + /// + /// 当前关节位置。 + IReadOnlyList GetJointPositions(); + + /// + /// 获取当前末端位姿。 + /// + /// 当前末端位姿。 + IReadOnlyList GetPose(); + + /// + /// 获取当前运行时状态快照。 + /// + /// 控制器状态快照。 + ControllerStateSnapshot GetSnapshot(); + + /// + /// 执行一条已经完成规划的轨迹,并更新最终关节位置。 + /// + /// 规划结果。 + /// 轨迹执行结束后的关节位置。 + void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList finalJointPositions); + + /// + /// 执行一条已经完成规划的直角坐标轨迹,并更新最终 TCP 位姿。 + /// + /// 规划结果,必须包含稠密直角坐标轨迹。 + /// 轨迹执行结束后的 [x,y,z,w,p,r] 位姿。 + void ExecuteCartesianTrajectory(TrajectoryResult result, IReadOnlyList finalPose); +} diff --git a/src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs b/src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs new file mode 100644 index 0000000..b8a86b1 --- /dev/null +++ b/src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs @@ -0,0 +1,1340 @@ +using System.Diagnostics; +using System.Text; +using Flyshot.Core.Domain; +using Flyshot.Core.Planning.Export; +using Flyshot.Core.Planning.Sampling; +using Flyshot.Runtime.Common; +using Flyshot.Runtime.Fanuc.Protocol; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Flyshot.Runtime.Fanuc; + +/// +/// FANUC 控制器运行时,将上层兼容层指令转换为三条真实 Socket 通道的交互。 +/// 仿真模式下仍保持内存桩行为,便于离线测试与回退。 +/// +public sealed class FanucControllerRuntime : IControllerRuntime, IDisposable +{ + private readonly object _stateLock = new(); + private readonly Dictionary<(string IoType, int Port), bool> _ioValues = new(); + private readonly FanucCommandClient _commandClient; + private readonly FanucStateClient _stateClient; + private readonly FanucJ519Client _j519Client; + private readonly ILogger? _logger; + + private RobotProfile? _robot; + private string? _robotName; + private bool? _activeControllerIsSimulation; + private string? _connectedRobotIp; + private bool _isEnabled; + private bool _isInMotion; + private int _bufferSize; + private double _speedRatio = 1.0; + private double[] _tcp = [0.0, 0.0, 0.0]; + private double[] _jointPositions = Array.Empty(); + private double[] _pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]; + private bool _disposed; + private CancellationTokenSource? _sendCts; + + /// + /// 初始化 FANUC 控制器运行时。 + /// + /// 运行时日志记录器。 + /// J519 客户端日志记录器。 + public FanucControllerRuntime(ILogger logger, ILogger j519Logger) + { + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(j519Logger); + + _commandClient = new FanucCommandClient(); + _stateClient = new FanucStateClient(); + _j519Client = new FanucJ519Client(j519Logger); + _logger = logger; + } + + /// + /// 初始化一份无日志的 FANUC 控制器运行时,供离线测试或手工构造场景使用。 + /// + public FanucControllerRuntime() + : this(NullLogger.Instance, NullLogger.Instance) + { + } + + /// + /// 供测试注入 mock 客户端的内部构造函数。 + /// + internal FanucControllerRuntime(FanucCommandClient commandClient, FanucStateClient stateClient, FanucJ519Client j519Client, ILogger? logger = null) + { + _commandClient = commandClient; + _stateClient = stateClient; + _j519Client = j519Client; + _logger = logger; + } + + /// + public void ResetRobot(RobotProfile robot, string robotName) + { + ArgumentNullException.ThrowIfNull(robot); + if (string.IsNullOrWhiteSpace(robotName)) + { + throw new ArgumentException("机器人名称不能为空。", nameof(robotName)); + } + + _logger?.LogInformation("ResetRobot: robotName={RobotName}, dof={Dof}", robotName, robot.DegreesOfFreedom); + + lock (_stateLock) + { + DisconnectClients(); + _robot = robot; + _robotName = robotName; + _activeControllerIsSimulation = null; + _connectedRobotIp = null; + _isEnabled = false; + _isInMotion = false; + _bufferSize = 0; + _speedRatio = 1.0; + _tcp = [0.0, 0.0, 0.0]; + _jointPositions = new double[robot.DegreesOfFreedom]; + _pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]; + _ioValues.Clear(); + } + } + + /// + public void SetActiveController(bool sim) + { + _logger?.LogInformation("SetActiveController: sim={Sim}", sim); + lock (_stateLock) + { + EnsureRobotSetup(); + DisconnectClients(); + _activeControllerIsSimulation = sim; + _connectedRobotIp = null; + _isEnabled = false; + _isInMotion = false; + } + } + + /// + public void Connect(string robotIp) + { + if (string.IsNullOrWhiteSpace(robotIp)) + { + throw new ArgumentException("控制器 IP 不能为空。", nameof(robotIp)); + } + + _logger?.LogInformation("Connect 开始: robotIp={RobotIp}, 仿真={IsSim}", robotIp, _activeControllerIsSimulation); + + lock (_stateLock) + { + EnsureActiveControllerSelected(); + if (_activeControllerIsSimulation!.Value) + { + _connectedRobotIp = robotIp; + _isEnabled = false; + _isInMotion = false; + _logger?.LogInformation("Connect 完成(仿真): robotIp={RobotIp}", robotIp); + return; + } + + // 真机模式:顺序建立三条通道 — 状态 → 命令 → 运动。 + _stateClient.ConnectAsync(robotIp, 10010).GetAwaiter().GetResult(); + _commandClient.ConnectAsync(robotIp, 10012).GetAwaiter().GetResult(); + _j519Client.ConnectAsync(robotIp, 60015).GetAwaiter().GetResult(); + + _connectedRobotIp = robotIp; + _isEnabled = false; + _isInMotion = false; + } + + _logger?.LogInformation("Connect 完成(真机): robotIp={RobotIp}, 三条通道已建立", robotIp); + } + + /// + public void Disconnect() + { + _logger?.LogInformation("Disconnect 开始"); + lock (_stateLock) + { + EnsureRobotSetup(); + CancelSendTaskLocked(); + DisconnectClients(); + _connectedRobotIp = null; + _isEnabled = false; + _isInMotion = false; + } + _logger?.LogInformation("Disconnect 完成"); + } + + /// + public void EnableRobot(int bufferSize) + { + if (bufferSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize), "buffer_size 必须大于 0。"); + } + + _logger?.LogInformation("EnableRobot 开始: bufferSize={BufferSize}, 仿真={IsSim}", bufferSize, _activeControllerIsSimulation); + + lock (_stateLock) + { + EnsureConnected(); + _bufferSize = bufferSize; + _j519Client.SetSequenceBufferSize(bufferSize); + + if (IsSimulationMode) + { + _isEnabled = true; + _logger?.LogInformation("EnableRobot 完成(仿真)"); + return; + } + + // 真机模式:按 all-reconnect.pcap 的重连序列启动 RVBUSTSM,暂不发送 StopProg。 + _commandClient.StopProgramAsync("RVBUSTSM").GetAwaiter().GetResult(); + _commandClient.ResetRobotAsync().GetAwaiter().GetResult(); + //_commandClient.GetProgramStatusAsync("RVBUSTSM").GetAwaiter().GetResult(); + _commandClient.StartProgramAsync("RVBUSTSM").GetAwaiter().GetResult(); + + _j519Client.StartMotion(); + _isEnabled = true; + } + + _logger?.LogInformation("EnableRobot 完成(真机): RVBUSTSM 已启动, J519 运动循环已开启"); + } + + /// + public void DisableRobot() + { + _logger?.LogInformation("DisableRobot 开始"); + lock (_stateLock) + { + EnsureRobotSetup(); + CancelSendTaskLocked(); + if (!IsSimulationMode) + { + _j519Client.StopMotionAsync().GetAwaiter().GetResult(); + _commandClient.StopProgramAsync("RVBUSTSM").GetAwaiter().GetResult(); + } + + _isEnabled = false; + _isInMotion = false; + } + _logger?.LogInformation("DisableRobot 完成"); + } + + /// + public void StopMove() + { + _logger?.LogInformation("StopMove 开始"); + lock (_stateLock) + { + EnsureRobotSetup(); + CancelSendTaskLocked(); + if (!IsSimulationMode) + { + _j519Client.StopMotionAsync().GetAwaiter().GetResult(); + } + + _isInMotion = false; + } + _logger?.LogInformation("StopMove 完成"); + } + + /// + public double GetSpeedRatio() + { + lock (_stateLock) + { + EnsureConnected(); + if (!IsSimulationMode) + { + var response = _commandClient.GetSpeedRatioAsync().GetAwaiter().GetResult(); + _speedRatio = response.Ratio; + } + + return _speedRatio; + } + } + + /// + public void SetSpeedRatio(double ratio) + { + if (double.IsNaN(ratio) || double.IsInfinity(ratio)) + { + throw new ArgumentOutOfRangeException(nameof(ratio), "ratio 必须是有限数值。"); + } + + _logger?.LogInformation("SetSpeedRatio: ratio={Ratio}", ratio); + + lock (_stateLock) + { + //EnsureConnected(); + var clampedRatio = Math.Clamp(ratio, 0.0, 1.0); + if (!IsSimulationMode) + { + //_commandClient.SetSpeedRatioAsync(clampedRatio).GetAwaiter().GetResult(); + } + + _speedRatio = clampedRatio; + } + + _logger?.LogInformation("SetSpeedRatio 完成: clampedRatio={ClampedRatio}", _speedRatio); + } + + /// + public IReadOnlyList GetTcp() + { + lock (_stateLock) + { + EnsureRobotSetup(); + if (_activeControllerIsSimulation is false && !string.IsNullOrWhiteSpace(_connectedRobotIp)) + { + var response = _commandClient.GetTcpAsync(1).GetAwaiter().GetResult(); + _tcp = response.Pose.Take(3).ToArray(); + } + + return _tcp.ToArray(); + } + } + + /// + public void SetTcp(double x, double y, double z) + { + lock (_stateLock) + { + EnsureRobotSetup(); + if (_activeControllerIsSimulation is false) + { + EnsureConnected(); + _commandClient.SetTcpAsync(1, CreateTcpPose(x, y, z)).GetAwaiter().GetResult(); + } + + _tcp = [x, y, z]; + } + } + + /// + public bool GetIo(int port, string ioType) + { + if (port < 0) + { + throw new ArgumentOutOfRangeException(nameof(port), "IO 端口不能为负数。"); + } + + var normalizedIoType = NormalizeIoType(ioType); + + lock (_stateLock) + { + EnsureConnected(); + if (!IsSimulationMode) + { + var response = _commandClient.GetIoAsync(port, normalizedIoType).GetAwaiter().GetResult(); + _ioValues[(normalizedIoType, port)] = response.Value; + return response.Value; + } + + return _ioValues.TryGetValue((normalizedIoType, port), out var value) && value; + } + } + + /// + public void SetIo(int port, bool value, string ioType) + { + if (port < 0) + { + throw new ArgumentOutOfRangeException(nameof(port), "IO 端口不能为负数。"); + } + + var normalizedIoType = NormalizeIoType(ioType); + + lock (_stateLock) + { + EnsureEnabled(); + if (!IsSimulationMode) + { + _commandClient.SetIoAsync(port, value, normalizedIoType).GetAwaiter().GetResult(); + } + + _ioValues[(normalizedIoType, port)] = value; + } + } + + /// + public IReadOnlyList GetJointPositions() + { + lock (_stateLock) + { + EnsureRobotSetup(); + if (!IsSimulationMode) + { + var frame = GetFreshStateFrame(); + if (frame?.JointRadians.Count >= _jointPositions.Length) + { + return frame.JointRadians.Take(_jointPositions.Length).Select(v => (double)v).ToArray(); + } + } + + return _jointPositions.ToArray(); + } + } + + /// + public IReadOnlyList GetPose() + { + lock (_stateLock) + { + EnsureRobotSetup(); + if (!IsSimulationMode) + { + var frame = GetFreshStateFrame(); + if (frame?.CartesianPose.Count >= 6) + { + return frame.CartesianPose.Take(6).Select(v => (double)v).ToArray(); + } + } + + return _pose.ToArray(); + } + } + + /// + public ControllerStateSnapshot GetSnapshot() + { + lock (_stateLock) + { + var jointPositions = _jointPositions; + var cartesianPose = _pose; + var isInMotion = _isInMotion; + IReadOnlyList stateTailWords = Array.Empty(); + byte? j519Status = null; + uint? j519Sequence = null; + bool? j519AcceptsCommand = null; + bool? j519ReceivedCommand = null; + bool? j519SystemReady = null; + bool? j519RobotInMotion = null; + + if (!IsSimulationMode) + { + var frame = GetFreshStateFrame(); + if (frame is not null) + { + if (frame.JointRadians.Count >= jointPositions.Length) + { + jointPositions = frame.JointRadians.Take(jointPositions.Length).Select(v => (double)v).ToArray(); + } + + if (frame.CartesianPose.Count >= 6) + { + cartesianPose = frame.CartesianPose.Take(6).Select(v => (double)v).ToArray(); + } + + stateTailWords = frame.RawTailWords.ToArray(); + } + + var j519Response = _j519Client.GetLatestResponse(); + if (j519Response is not null) + { + isInMotion = j519Response.RobotInMotion; + j519Status = j519Response.Status; + j519Sequence = j519Response.Sequence; + j519AcceptsCommand = j519Response.AcceptsCommand; + j519ReceivedCommand = j519Response.ReceivedCommand; + j519SystemReady = j519Response.SystemReady; + j519RobotInMotion = j519Response.RobotInMotion; + } + } + + return new ControllerStateSnapshot( + capturedAt: DateTimeOffset.UtcNow, + connectionState: ResolveConnectionState(), + isEnabled: _isEnabled, + isInMotion: isInMotion, + speedRatio: _speedRatio, + jointPositions: jointPositions, + cartesianPose: cartesianPose, + activeAlarms: Array.Empty(), + stateTailWords: stateTailWords, + j519Status: j519Status, + j519Sequence: j519Sequence, + j519AcceptsCommand: j519AcceptsCommand, + j519ReceivedCommand: j519ReceivedCommand, + j519SystemReady: j519SystemReady, + j519RobotInMotion: j519RobotInMotion); + } + } + + /// + public void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList finalJointPositions) + { + ArgumentNullException.ThrowIfNull(result); + ArgumentNullException.ThrowIfNull(finalJointPositions); + CancellationToken denseSendCancellationToken = default; + CancellationTokenSource? denseSendCancellationSource = null; + var shouldRunDenseTrajectory = false; + + _logger?.LogInformation( + "ExecuteTrajectory 开始: program={ProgramName}, method={Method}, 时长={Duration}s, 稠密采样={HasDense}, 触发事件数={TriggerCount}, speedRatio={SpeedRatio}", + result.ProgramName, result.Method, result.Duration.TotalSeconds, + result.DenseJointTrajectory is not null, result.TriggerTimeline.Count, _speedRatio); + + lock (_stateLock) + { + EnsureEnabled(); + EnsureValidTrajectory(result); + EnsureJointCount(finalJointPositions.Count); + CancelSendTaskLocked(); + + if (!IsSimulationMode && result.DenseJointTrajectory is not null) + { + EnsureJ519ReadyForDenseExecution(); + + // 真机模式且存在稠密路点:准备可被 StopMove 取消的同步发送任务。 + _isInMotion = true; + _sendCts = new CancellationTokenSource(); + denseSendCancellationSource = _sendCts; + denseSendCancellationToken = _sendCts.Token; + shouldRunDenseTrajectory = true; + + _logger?.LogInformation("ExecuteTrajectory 开始同步稠密发送任务"); + } + else + { + if (!IsSimulationMode) + { + // 真机模式无稠密路点:回退到单点收敛。 + var command = new FanucJ519Command( + sequence: 0, + targetJoints: finalJointPositions.Select(j => (double)j).ToArray()); + _j519Client.UpdateCommand(command); + } + + _isInMotion = true; + _jointPositions = finalJointPositions.ToArray(); + _isInMotion = false; + _logger?.LogInformation("ExecuteTrajectory 完成(单点模式)"); + } + } + + if (shouldRunDenseTrajectory) + { + try + { + // 稠密轨迹必须等 J519 队列被机器人状态包实际取完后再向上层返回。 + SendDenseTrajectory(result, finalJointPositions, denseSendCancellationToken); + _logger?.LogInformation("ExecuteTrajectory 完成(稠密模式)"); + } + finally + { + lock (_stateLock) + { + if (ReferenceEquals(_sendCts, denseSendCancellationSource)) + { + denseSendCancellationSource?.Dispose(); + _sendCts = null; + } + } + } + } + } + + /// + public void ExecuteCartesianTrajectory(TrajectoryResult result, IReadOnlyList finalPose) + { + ArgumentNullException.ThrowIfNull(result); + ArgumentNullException.ThrowIfNull(finalPose); + CancellationToken denseSendCancellationToken = default; + CancellationTokenSource? denseSendCancellationSource = null; + var shouldRunDenseTrajectory = false; + + _logger?.LogInformation( + "ExecuteCartesianTrajectory 开始: program={ProgramName}, 时长={Duration}s, 稠密采样={HasDense}, speedRatio={SpeedRatio}", + result.ProgramName, result.Duration.TotalSeconds, result.DenseCartesianTrajectory is not null, _speedRatio); + + lock (_stateLock) + { + EnsureEnabled(); + EnsureValidTrajectory(result); + EnsurePoseCount(finalPose.Count); + CancelSendTaskLocked(); + + if (!IsSimulationMode && result.DenseCartesianTrajectory is not null) + { + EnsureJ519ReadyForDenseExecution(); + + // 真机直角模式同样预装完整队列,由机器人状态包节拍驱动出队。 + _isInMotion = true; + _sendCts = new CancellationTokenSource(); + denseSendCancellationSource = _sendCts; + denseSendCancellationToken = _sendCts.Token; + shouldRunDenseTrajectory = true; + + _logger?.LogInformation("ExecuteCartesianTrajectory 开始同步直角稠密发送任务"); + } + else + { + if (!IsSimulationMode) + { + var command = new FanucJ519Command( + sequence: 0, + targetValues: BuildCartesianTargetValues(finalPose), + dataStyle: 0); + _j519Client.UpdateCommand(command); + } + + _isInMotion = true; + _pose = MergeFinalCartesianPose(finalPose); + _isInMotion = false; + _logger?.LogInformation("ExecuteCartesianTrajectory 完成(单点模式)"); + } + } + + if (shouldRunDenseTrajectory) + { + try + { + SendDenseCartesianTrajectory(result, finalPose, denseSendCancellationToken); + _logger?.LogInformation("ExecuteCartesianTrajectory 完成(稠密模式)"); + } + finally + { + lock (_stateLock) + { + if (ReferenceEquals(_sendCts, denseSendCancellationSource)) + { + denseSendCancellationSource?.Dispose(); + _sendCts = null; + } + } + } + } + } + + /// + /// 释放运行时持有的所有 Socket 客户端。 + /// + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + CancelSendTaskLocked(); + DisconnectClients(); + _commandClient.Dispose(); + _stateClient.Dispose(); + _j519Client.Dispose(); + } + + /// + /// 稠密轨迹发送任务:预生成完整 J519 命令队列,并等待机器人状态包驱动队列执行完成。 + /// + private void SendDenseTrajectory(TrajectoryResult result, IReadOnlyList finalJointPositions, CancellationToken cancellationToken) + { + var triggers = result.TriggerTimeline; + var servoPeriodSeconds = _robot!.ServoPeriod.TotalSeconds; + var preparedExecution = result.PreparedFlyshotExecution; + var durationSeconds = preparedExecution?.FinalDurationSeconds ?? result.Duration.TotalSeconds; + var samples = preparedExecution is null + ? J519SendTrajectorySampler.SampleDenseJointTrajectory( + result.DenseJointTrajectory!, + durationSeconds, + servoPeriodSeconds, + speedRatio: 1.0) + : preparedExecution.Samples.Select(static sample => new J519SendSample( + sample.SampleIndex, + sample.SendTime, + sample.TrajectoryTime, + sample.SpeedRatio, + sample.JointsDegrees)).ToArray(); + TrajectoryLimitValidator.ValidateJ519SendSamples( + _robot!, + samples, + trajectoryName: result.ProgramName); + var sampleCount = samples.Count; + + _logger?.LogInformation( + "SendDenseTrajectory 开始: program={ProgramName}, 采样数={SampleCount}, 时长={Duration}s, runtimeSpeedRatio={SpeedRatio}, 周期={Period}ms, 触发事件数={TriggerCount}", + result.ProgramName, sampleCount, durationSeconds, _speedRatio, servoPeriodSeconds * 1000, triggers.Count); + + var triggerBindings = preparedExecution is null + ? TriggerSampleBinder.Bind( + triggers, + samples, + result.TriggerSampleIndexOffsetCycles) + : preparedExecution.TriggerBindings.Select(static binding => + new TriggerSampleBinding( + binding.Trigger, + new J519SendSample( + binding.Sample.SampleIndex, + binding.Sample.SendTime, + binding.Sample.TrajectoryTime, + binding.Sample.SpeedRatio, + binding.Sample.JointsDegrees), + binding.SampleIndex, + binding.FoundInWindow)).ToArray(); + + var stopwatch = Stopwatch.StartNew(); + ushort ioValue = 0; + ushort ioMask = 0; + int holdRemaining = -1; + long logInterval = Math.Max(1, sampleCount / 10); + int triggerFiredCount = 0; + var nextBindingIndex = 0; + var commands = new List(sampleCount); + var sentJointRows = new List>(sampleCount); + var sentTimingRows = new List>(sampleCount); + var sentJerkRows = new List>(); + var outputDir = CreateDenseSendOutputDirectory(result.ProgramName); + double? previousSendTime = null; + double[]? previousJoints = null; + double[]? previousVelocity = null; + double[]? previousAcceleration = null; + var preparedJerkRows = preparedExecution?.JerkRows; + var diagnosticRequestedSpeedRatio = _speedRatio > 0.0 && !double.IsNaN(_speedRatio) && !double.IsInfinity(_speedRatio) + ? _speedRatio + : 1.0; + + try + { + foreach (var sample in samples) + { + cancellationToken.ThrowIfCancellationRequested(); + + // 递减 IO 保持计数器;若已到期则清零。 + var clearMaskAfterSend = false; + if (holdRemaining > 0) + { + holdRemaining--; + } + else if (holdRemaining == 0) + { + ioValue = 0; + holdRemaining = -1; + clearMaskAfterSend = true; + } + + // 检查当前周期是否有新的触发事件。 + if (holdRemaining < 0 && !clearMaskAfterSend) + { + while (nextBindingIndex < triggerBindings.Count + && triggerBindings[nextBindingIndex].SampleIndex < sample.SampleIndex) + { + nextBindingIndex++; + } + + if (nextBindingIndex < triggerBindings.Count + && triggerBindings[nextBindingIndex].SampleIndex == sample.SampleIndex) + { + var binding = triggerBindings[nextBindingIndex]; + ioMask = ComputeIoValue(binding.Trigger.AddressGroup); + ioValue = ioMask; + holdRemaining = Math.Max(binding.Trigger.HoldCycles - 1, 0); + triggerFiredCount++; + nextBindingIndex++; + _logger?.LogInformation( + "J519 IO触发: waypointIndex={WaypointIndex}, theoryTime={TheoryTime:F4}s, sampleIndex={SampleIndex}, sampleSendTime={SampleSendTime:F4}s, sampleTrajectoryTime={SampleTrajectoryTime:F4}s, addr=[{Addr}], holdCycles={HoldCycles}", + binding.Trigger.WaypointIndex, + binding.Trigger.TriggerTime, + binding.SampleIndex, + binding.Sample.SendTime, + binding.Sample.TrajectoryTime, + string.Join(",", binding.Trigger.AddressGroup.Addresses), + binding.Trigger.HoldCycles); + } + } + + var command = new FanucJ519Command( + sequence: 0, + targetJoints: sample.JointsDegrees, + writeIoType: 2, + writeIoIndex: 1, + writeIoMask: ioMask, + writeIoValue: ioValue); + + commands.Add(command); + sentJointRows.Add(BuildDenseSendJointRow(sample.SendTime, sample.JointsDegrees, ioMask, ioValue)); + sentTimingRows.Add(preparedExecution is null + ? J519SendTrajectorySampler.BuildTimingRow(sample, diagnosticRequestedSpeedRatio, stretchIterationCount: 0) + : J519SendTrajectorySampler.BuildTimingRow( + sample, + preparedExecution.RequestSpeedRatio, + preparedExecution.StretchIterationCount)); + + if (preparedJerkRows is not null) + { + if (sample.SampleIndex > 0 && sample.SampleIndex - 1 < preparedJerkRows.Count) + { + sentJerkRows.Add(preparedJerkRows[(int)sample.SampleIndex - 1]); + } + } + else if (previousSendTime is not null && previousJoints is not null) + { + var jerkRow = J519SendTrajectorySampler.BuildJerkRow( + previousSendTime.Value, + sample.SendTime, + previousJoints, + sample.JointsDegrees, + ref previousVelocity, + ref previousAcceleration); + sentJerkRows.Add(jerkRow); + } + + if (clearMaskAfterSend) + { + ioMask = 0; + } + + previousSendTime = sample.SendTime; + previousJoints = sample.JointsDegrees.ToArray(); + + // 周期性记录进度(Debug 级别,避免高频 Info 日志)。 + if (sample.SampleIndex > 0 && sample.SampleIndex % logInterval == 0) + { + _logger?.LogDebug( + "SendDenseTrajectory 进度: {Percent}% ({Current}/{Total}), sendTime={SendTime:F4}s, trajectoryTime={TrajectoryTime:F4}s", + (int)((double)sample.SampleIndex / sampleCount * 100), + sample.SampleIndex, + sampleCount, + sample.SendTime, + sample.TrajectoryTime); + } + } + TryWriteDenseSendArtifacts(result, outputDir, sentJointRows, sentTimingRows, sentJerkRows, triggerBindings); + + // 上层只负责生成完整目标序列,真实出队节拍交给 J519 状态包驱动。 + _j519Client.LoadCommandQueue(commands); + if (_j519Client.IsConnected) + { + _j519Client.WaitForCommandQueueDrainedAsync(cancellationToken).GetAwaiter().GetResult(); + } + _logger?.LogInformation( + "SendDenseTrajectory 正常完成: 采样数={SampleCount}, 触发次数={TriggerFiredCount}, 队列装载耗时={ElapsedMs}ms", + sampleCount, triggerFiredCount, stopwatch.ElapsedMilliseconds); + } + catch (OperationCanceledException) + { + _logger?.LogWarning( + "SendDenseTrajectory 被取消: 已生成 {Current}/{Total} 条命令, 触发次数={TriggerFiredCount}", + commands.Count, sampleCount, triggerFiredCount); + // 正常取消,轨迹被中断。 + } + finally + { + lock (_stateLock) + { + _isInMotion = false; + _jointPositions = finalJointPositions.ToArray(); + } + } + } + + /// + /// 直角稠密轨迹发送任务:预生成 Data format=0 的 J519 命令队列,并等待状态包驱动执行完成。 + /// + private void SendDenseCartesianTrajectory(TrajectoryResult result, IReadOnlyList finalPose, CancellationToken cancellationToken) + { + var rows = result.DenseCartesianTrajectory ?? throw new InvalidOperationException("Cartesian trajectory requires dense Cartesian samples."); + var commands = new List(rows.Count); + var sentPoseRows = new List>(rows.Count); + var outputDir = CreateDenseSendOutputDirectory(result.ProgramName); + var stopwatch = Stopwatch.StartNew(); + + try + { + foreach (var row in rows) + { + cancellationToken.ThrowIfCancellationRequested(); + if (row.Count != 7) + { + throw new InvalidOperationException("Cartesian dense trajectory rows must contain time plus six pose values."); + } + + var pose = row.Skip(1).Take(6).ToArray(); + var command = new FanucJ519Command( + sequence: 0, + targetValues: BuildCartesianTargetValues(pose), + dataStyle: 0, + writeIoType: 2, + writeIoIndex: 1, + writeIoMask: 0, + writeIoValue: 0); + + commands.Add(command); + sentPoseRows.Add(BuildDenseSendPoseRow(row[0], pose)); + } + + TryWriteDenseCartesianSendArtifacts(outputDir, sentPoseRows); + + _j519Client.LoadCommandQueue(commands); + if (_j519Client.IsConnected) + { + _j519Client.WaitForCommandQueueDrainedAsync(cancellationToken).GetAwaiter().GetResult(); + } + + _logger?.LogInformation( + "SendDenseCartesianTrajectory 正常完成: 采样数={SampleCount}, 队列装载耗时={ElapsedMs}ms", + commands.Count, + stopwatch.ElapsedMilliseconds); + } + catch (OperationCanceledException) + { + _logger?.LogWarning( + "SendDenseCartesianTrajectory 被取消: 已生成 {Current}/{Total} 条命令", + commands.Count, + rows.Count); + } + finally + { + lock (_stateLock) + { + _isInMotion = false; + _pose = MergeFinalCartesianPose(finalPose); + } + } + } + + /// + /// 若已有 J519 响应,则在启动稠密轨迹前检查伺服侧是否接受命令并处于系统就绪状态。 + /// + private void EnsureJ519ReadyForDenseExecution() + { + EnsureJ519ReadyForDenseExecutionCore( + () => _j519Client.GetLatestResponse(), + () => + { + // 若当前状态未就绪,则先尝试一次 EnableRobot 走现场既有恢复链路。 + EnableRobot(_bufferSize); + }, + static () => Thread.Sleep(TimeSpan.FromMilliseconds(500))); + } + + /// + /// 执行稠密发送前的 J519 就绪校验;未就绪时允许先做一次启机重试。 + /// + /// 读取最新 J519 状态的委托。 + /// 状态未就绪时触发一次 EnableRobot 重试的委托。 + /// 重试后等待状态刷新的委托。 + internal void EnsureJ519ReadyForDenseExecutionCore( + Func getLatestResponse, + Action retryEnableRobot, + Action waitAfterRetry) + { + ArgumentNullException.ThrowIfNull(getLatestResponse); + ArgumentNullException.ThrowIfNull(retryEnableRobot); + ArgumentNullException.ThrowIfNull(waitAfterRetry); + + var response = getLatestResponse(); + + if (response is null || IsJ519ReadyForDenseExecution(response)) + { + return; + } + _logger?.LogInformation("当前机器人不满足执行条件,重新启用机器人"); + try + { + retryEnableRobot(); + waitAfterRetry(); + } + catch (InvalidOperationException exception) + { + // 重试链路自身失败时,优先保留 J519 状态位诊断,底层连接异常作为 inner exception 继续上抛。 + throw new InvalidOperationException(BuildJ519DenseExecutionNotReadyMessage(response), exception); + } + + response = getLatestResponse(); + if (response is null || IsJ519ReadyForDenseExecution(response)) + { + return; + } + + throw new InvalidOperationException(BuildJ519DenseExecutionNotReadyMessage(response)); + } + + /// + /// 判断当前 J519 响应是否已经满足稠密执行前置条件。 + /// + /// 待检查的 J519 状态响应。 + /// 接受命令且系统就绪时返回 true,否则返回 false。 + internal static bool IsJ519ReadyForDenseExecution(FanucJ519Response response) + { + ArgumentNullException.ThrowIfNull(response); + return response is { AcceptsCommand: true, SystemReady: true }; + } + + /// + /// 组装 J519 未就绪时的异常消息,便于保留现场关键状态位。 + /// + /// 最近一次收到的 J519 状态响应。 + /// 包含状态位和序号的诊断消息。 + internal static string BuildJ519DenseExecutionNotReadyMessage(FanucJ519Response response) + { + ArgumentNullException.ThrowIfNull(response); + return "J519 status is not ready for dense execution: " + + $"accept_cmd={response.AcceptsCommand}, " + + $"received_cmd={response.ReceivedCommand}, " + + $"sysrdy={response.SystemReady}, " + + $"rbt_inmotion={response.RobotInMotion}, " + + $"seq={response.Sequence}, " + + $"status=0x{response.Status:X2}."; + } + + /// + /// 为单次稠密发送创建按日期时间归档的输出目录。 + /// + private static string CreateDenseSendOutputDirectory(string programName) + { + var safeProgramName = SanitizeDirectoryName(string.IsNullOrWhiteSpace(programName) ? "unnamed-program" : programName); + var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff"); + var outputDir = Path.Combine( + AppContext.BaseDirectory, + "Config", + "Data", + safeProgramName, + "DenseSend", + timestamp); + Directory.CreateDirectory(outputDir); + return outputDir; + } + + /// + /// 构造实际发送点位文本行,格式为 send_time + 关节角度 + io_mask + io_value。 + /// + private static IReadOnlyList BuildDenseSendJointRow(double sendTime, IReadOnlyList joints, ushort ioMask, ushort ioValue) + { + var row = new double[joints.Count + 3]; + row[0] = Math.Round(sendTime, 6); + for (var index = 0; index < joints.Count; index++) + { + row[index + 1] = Math.Round(joints[index], 6); + } + + row[^2] = ioMask; + row[^1] = ioValue; + return row; + } + + /// + /// 构造实际发送直角位姿文本行,格式为 send_time + X/Y/Z/W/P/R。 + /// + private static IReadOnlyList BuildDenseSendPoseRow(double sendTime, IReadOnlyList pose) + { + var row = new double[pose.Count + 1]; + row[0] = Math.Round(sendTime, 6); + for (var index = 0; index < pose.Count; index++) + { + row[index + 1] = Math.Round(pose[index], 6); + } + + return row; + } + + /// + /// 构造 J519 直角命令的 9 个目标槽位,当前现场无扩展轴时 E1/E2/E3 补零。 + /// + private static double[] BuildCartesianTargetValues(IReadOnlyList pose) + { + EnsurePoseCount(pose.Count); + return [pose[0], pose[1], pose[2], pose[3], pose[4], pose[5], 0.0, 0.0, 0.0]; + } + + /// + /// 将六维 WPR 直角目标合并回运行时缓存,保留旧 GetPose 仿真路径的 7 维外形。 + /// + private double[] MergeFinalCartesianPose(IReadOnlyList pose) + { + EnsurePoseCount(pose.Count); + return _pose.Length >= 7 + ? [pose[0], pose[1], pose[2], pose[3], pose[4], pose[5], _pose[6]] + : pose.ToArray(); + } + + /// + /// 尝试把实际发送点位、时间映射和跃度统计写入纯文本文件;若落盘失败,只记录日志,不影响运动主流程。 + /// + private void TryWriteDenseSendArtifacts( + TrajectoryResult result, + string outputDir, + IReadOnlyList> sentJointRows, + IReadOnlyList> sentTimingRows, + IReadOnlyList> sentJerkRows, + IReadOnlyList triggerBindings) + { + try + { + WriteDenseRows(Path.Combine(outputDir, "ActualSendJointTraj.txt"), sentJointRows); + WriteDenseRows(Path.Combine(outputDir, "ActualSendTiming.txt"), sentTimingRows); + WriteDenseRows(Path.Combine(outputDir, "ActualSendJerkStats.txt"), sentJerkRows); + TrajectoryExporter.WriteShotEvents(Path.Combine(outputDir, "ShotEvents.json"), result.ShotEvents, triggerBindings); + _logger?.LogInformation( + "SendDenseTrajectory 已写出实际发送记录: outputDir={OutputDir}, pointRows={PointRows}, timingRows={TimingRows}, jerkRows={JerkRows}, triggerBindings={TriggerBindings}", + outputDir, + sentJointRows.Count, + sentTimingRows.Count, + sentJerkRows.Count, + triggerBindings.Count); + } + catch (Exception exception) + { + _logger?.LogWarning(exception, "SendDenseTrajectory 写出实际发送记录失败: outputDir={OutputDir}", outputDir); + } + } + + /// + /// 尝试把直角实际发送点位写入文本文件;若落盘失败,只记录日志,不影响运动主流程。 + /// + private void TryWriteDenseCartesianSendArtifacts(string outputDir, IReadOnlyList> sentPoseRows) + { + try + { + WriteDenseRows(Path.Combine(outputDir, "ActualSendPoseTraj.txt"), sentPoseRows); + _logger?.LogInformation( + "SendDenseCartesianTrajectory 已写出实际发送记录: outputDir={OutputDir}, poseRows={PoseRows}", + outputDir, + sentPoseRows.Count); + } + catch (Exception exception) + { + _logger?.LogWarning(exception, "SendDenseCartesianTrajectory 写出实际发送记录失败: outputDir={OutputDir}", outputDir); + } + } + + /// + /// 以旧轨迹文本兼容的空格分隔格式写出数值行。 + /// + private static void WriteDenseRows(string path, IReadOnlyList> rows) + { + var sb = new StringBuilder(); + foreach (var row in rows) + { + sb.AppendLine(string.Join(" ", row.Select(static value => $"{value:F6}"))); + } + + File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false)); + } + + /// + /// 清理程序名中的非法路径字符,避免输出目录受外部输入污染。 + /// + private static string SanitizeDirectoryName(string name) + { + var invalidChars = Path.GetInvalidFileNameChars(); + var chars = name.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray(); + return new string(chars); + } + + /// + /// 取消当前稠密发送任务,避免旧轨迹继续向机器人下发。 + /// + private void CancelSendTaskLocked() + { + _sendCts?.Cancel(); + + _sendCts?.Dispose(); + _sendCts = null; + } + + /// + /// 把 IO 地址组中的地址号映射为 writeIoValue 的位掩码。 + /// 手册约定 writeIoIndex=1 时,LSB 对应第 1 个 IO 点,因此地址 N 对应 bit(N-1)。 + /// + internal static ushort ComputeIoValue(IoAddressGroup group) + { + ushort value = 0; + foreach (var addr in group.Addresses) + { + if (addr is >= 1 and <= 16) + { + value |= (ushort)(1 << (addr - 1)); + } + } + + return value; + } + + /// + /// 判断当前是否处于仿真模式;若尚未选择控制器则抛出异常。 + /// + private bool IsSimulationMode + { + get + { + if (_activeControllerIsSimulation is null) + { + return false; + } + + return _activeControllerIsSimulation.Value; + } + } + + /// + /// 断开所有真实 Socket 通道,不影响内存状态。 + /// + private void DisconnectClients() + { + _j519Client.Disconnect(); + _commandClient.Disconnect(); + _stateClient.Disconnect(); + } + + /// + /// 归一化 IO 类型字符串,避免调用方大小写差异影响缓存键。 + /// + private static string NormalizeIoType(string ioType) + { + if (string.IsNullOrWhiteSpace(ioType)) + { + throw new ArgumentException("IO 类型不能为空。", nameof(ioType)); + } + + return ioType.Trim().ToUpperInvariant(); + } + + /// + /// 将 HTTP 层三维 TCP 请求扩展为 FANUC 命令通道需要的 7 维 Pose。 + /// + private static double[] CreateTcpPose(double x, double y, double z) + { + return [x, y, z, 0.0, 0.0, 0.0, 1.0]; + } + + /// + /// 校验轨迹规划结果可执行。 + /// + private static void EnsureValidTrajectory(TrajectoryResult result) + { + if (!result.IsValid) + { + throw new InvalidOperationException(result.FailureReason ?? "Trajectory result is invalid."); + } + } + + /// + /// 根据当前内部状态生成连接状态标签。 + /// + private string ResolveConnectionState() + { + if (_robot is null) + { + return "NotConfigured"; + } + + if (string.IsNullOrWhiteSpace(_connectedRobotIp)) + { + return "Disconnected"; + } + + return _activeControllerIsSimulation is false + ? ResolveRealConnectionState(_stateClient.GetStatus()) + : "Connected"; + } + + /// + /// 把真实 10010 状态通道健康度映射为上层快照连接状态。 + /// + internal static string ResolveRealConnectionState(FanucStateClientStatus status) + { + ArgumentNullException.ThrowIfNull(status); + + return status.State switch + { + FanucStateConnectionState.Connected when status.IsFrameStale => "StateTimeout", + FanucStateConnectionState.Connected => "Connected", + FanucStateConnectionState.TimedOut => "StateTimeout", + FanucStateConnectionState.Reconnecting => "Reconnecting", + FanucStateConnectionState.Connecting => "Connecting", + _ => "Disconnected", + }; + } + + /// + /// 判断 runtime 是否可以把某个状态通道帧作为当前机器人状态使用。 + /// + internal static bool ShouldUseStateFrame(FanucStateClientStatus status) + { + ArgumentNullException.ThrowIfNull(status); + return status.State == FanucStateConnectionState.Connected && !status.IsFrameStale; + } + + /// + /// 获取未超时的状态帧;超时或重连期间不把旧状态作为当前机器人状态使用。 + /// + private FanucStateFrame? GetFreshStateFrame() + { + var status = _stateClient.GetStatus(); + return ShouldUseStateFrame(status) ? _stateClient.GetLatestFrame() : null; + } + + /// + /// 校验给定关节数组长度与当前机器人自由度一致。 + /// + private void EnsureJointCount(int jointCount) + { + var expectedJointCount = _robot?.DegreesOfFreedom ?? throw new InvalidOperationException("Robot has not been setup."); + if (jointCount != expectedJointCount) + { + throw new InvalidOperationException($"Expected {expectedJointCount} joints but received {jointCount}."); + } + } + + /// + /// 校验直角位姿固定为 [x,y,z,w,p,r] 六维。 + /// + private static void EnsurePoseCount(int poseCount) + { + if (poseCount != 6) + { + throw new InvalidOperationException($"Expected 6 Cartesian pose values but received {poseCount}."); + } + } + + /// + /// 校验机器人已经完成初始化。 + /// + private void EnsureRobotSetup() + { + if (_robot is null || string.IsNullOrWhiteSpace(_robotName)) + { + throw new InvalidOperationException("Robot has not been setup."); + } + } + + /// + /// 校验活动控制器已经被选择。 + /// + private void EnsureActiveControllerSelected() + { + EnsureRobotSetup(); + if (_activeControllerIsSimulation is null) + { + throw new InvalidOperationException("Active controller has not been selected."); + } + } + + /// + /// 校验控制器已经建立连接。 + /// + private void EnsureConnected() + { + EnsureActiveControllerSelected(); + if (string.IsNullOrWhiteSpace(_connectedRobotIp)) + { + throw new InvalidOperationException("Controller has not been connected."); + } + } + + /// + /// 校验机器人已经处于使能态。 + /// + private void EnsureEnabled() + { + EnsureConnected(); + if (!_isEnabled || _bufferSize <= 0) + { + throw new InvalidOperationException("Robot has not been enabled."); + } + } +} diff --git a/src/Flyshot.Runtime.Fanuc/Flyshot.Runtime.Fanuc.csproj b/src/Flyshot.Runtime.Fanuc/Flyshot.Runtime.Fanuc.csproj new file mode 100644 index 0000000..487c72c --- /dev/null +++ b/src/Flyshot.Runtime.Fanuc/Flyshot.Runtime.Fanuc.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + + + + + + + + <_Parameter1>Flyshot.Core.Tests + + + + + + + + + + diff --git a/src/Flyshot.Runtime.Fanuc/Protocol/FanucCommandClient.cs b/src/Flyshot.Runtime.Fanuc/Protocol/FanucCommandClient.cs new file mode 100644 index 0000000..3a17cbd --- /dev/null +++ b/src/Flyshot.Runtime.Fanuc/Protocol/FanucCommandClient.cs @@ -0,0 +1,454 @@ +using System.Net.Sockets; +using Microsoft.Extensions.Logging; + +namespace Flyshot.Runtime.Fanuc.Protocol; + +/// +/// FANUC TCP 10012 命令通道客户端,提供 Req/Res 同步命令下发能力。 +/// +public sealed class FanucCommandClient : IDisposable +{ + private readonly SemaphoreSlim _sendLock = new(1, 1); + private readonly ILogger? _logger; + private TcpClient? _tcpClient; + private NetworkStream? _stream; + private bool _disposed; + + /// + /// 获取当前是否已建立连接。 + /// + public bool IsConnected => _tcpClient?.Connected ?? false; + + /// + /// 初始化 FANUC 命令通道客户端。 + /// + /// 日志记录器;允许 null。 + public FanucCommandClient(ILogger? logger = null) + { + _logger = logger; + } + + /// + /// 建立到 FANUC 控制柜 TCP 10012 命令通道的连接。 + /// + /// 控制柜 IP 地址。 + /// 命令通道端口,默认 10012。 + /// 取消令牌。 + public async Task ConnectAsync(string ip, int port = 10012, CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (string.IsNullOrWhiteSpace(ip)) + { + throw new ArgumentException("IP 不能为空。", nameof(ip)); + } + + if (_tcpClient is not null) + { + throw new InvalidOperationException("命令通道已经连接,请先 Disconnect。"); + } + + _logger?.LogInformation("CommandClient ConnectAsync: {Ip}:{Port}", ip, port); + + _tcpClient = new TcpClient { NoDelay = true }; + await _tcpClient.ConnectAsync(ip, port, cancellationToken).ConfigureAwait(false); + _stream = _tcpClient.GetStream(); + + _logger?.LogInformation("CommandClient 已连接: {Ip}:{Port}", ip, port); + } + + /// + /// 断开命令通道并释放资源。 + /// + public void Disconnect() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + _logger?.LogInformation("CommandClient Disconnect"); + + _stream?.Dispose(); + _stream = null; + _tcpClient?.Dispose(); + _tcpClient = null; + } + + /// + /// 发送通用命令并等待响应。 + /// + /// 命令消息号。 + /// 命令业务体。 + /// 取消令牌。 + /// 原始响应帧。 + public async Task SendCommandAsync(uint messageId, ReadOnlyMemory body, CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (_stream is null) + { + throw new InvalidOperationException("命令通道未连接。"); + } + + await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + var frame = FanucCommandProtocol.PackFrame(messageId, body.Span); + await _stream.WriteAsync(frame, cancellationToken).ConfigureAwait(false); + + return await ReadResponseFrameAsync(cancellationToken).ConfigureAwait(false); + } + finally + { + _sendLock.Release(); + } + } + + /// + /// 发送携带程序名的命令并等待响应。 + /// + /// 命令消息号。 + /// 程序名。 + /// 取消令牌。 + /// 结果响应。 + public async Task SendProgramCommandAsync(uint messageId, string programName, CancellationToken cancellationToken = default) + { + var frame = FanucCommandProtocol.PackProgramCommand(messageId, programName); + var response = await SendRawFrameAsync(frame, cancellationToken).ConfigureAwait(false); + return EnsureSuccess(FanucCommandProtocol.ParseResultResponse(response)); + } + + /// + /// 停止指定程序。 + /// + /// 程序名。 + /// 取消令牌。 + /// 结果响应。 + public async Task StopProgramAsync(string programName, CancellationToken cancellationToken = default) + { + _logger?.LogInformation("CommandClient StopProgram: {ProgramName}", programName); + var result = await SendProgramCommandAsync(FanucCommandMessageIds.StopProgram, programName, cancellationToken).ConfigureAwait(false); + _logger?.LogDebug("CommandClient StopProgram 成功: {ProgramName}", programName); + return result; + } + + /// + /// 复位控制器。 + /// + /// 取消令牌。 + /// 结果响应。 + public async Task ResetRobotAsync(CancellationToken cancellationToken = default) + { + _logger?.LogInformation("CommandClient ResetRobot"); + var frame = FanucCommandProtocol.PackEmptyCommand(FanucCommandMessageIds.ResetRobot); + var response = await SendRawFrameAsync(frame, cancellationToken).ConfigureAwait(false); + _logger?.LogDebug("CommandClient ResetRobot 成功"); + return EnsureSuccess(FanucCommandProtocol.ParseResultResponse(response)); + } + + /// + /// 查询指定程序状态。 + /// + /// 程序名。 + /// 取消令牌。 + /// 程序状态响应。 + public async Task GetProgramStatusAsync(string programName, CancellationToken cancellationToken = default) + { + _logger?.LogInformation("CommandClient GetProgramStatus: {ProgramName}", programName); + var frame = FanucCommandProtocol.PackProgramCommand(FanucCommandMessageIds.GetProgramStatus, programName); + var response = await SendRawFrameAsync(frame, cancellationToken).ConfigureAwait(false); + _logger?.LogDebug("CommandClient GetProgramStatus 成功: {ProgramName}", programName); + return EnsureSuccess(FanucCommandProtocol.ParseProgramStatusResponse(response)); + } + + /// + /// 启动指定程序。 + /// + /// 程序名。 + /// 取消令牌。 + /// 结果响应。 + public async Task StartProgramAsync(string programName, CancellationToken cancellationToken = default) + { + _logger?.LogInformation("CommandClient StartProgram: {ProgramName}", programName); + var result = await SendProgramCommandAsync(FanucCommandMessageIds.StartProgram, programName, cancellationToken).ConfigureAwait(false); + _logger?.LogDebug("CommandClient StartProgram 成功: {ProgramName}", programName); + return result; + } + + /// + /// 读取控制器速度倍率。 + /// + /// 取消令牌。 + /// 速度倍率响应。 + public async Task GetSpeedRatioAsync(CancellationToken cancellationToken = default) + { + _logger?.LogDebug("CommandClient GetSpeedRatio"); + var frame = FanucCommandProtocol.PackGetSpeedRatioCommand(); + var response = await SendRawFrameAsync(frame, cancellationToken).ConfigureAwait(false); + var result = EnsureSuccess(FanucCommandProtocol.ParseSpeedRatioResponse(response)); + _logger?.LogDebug("CommandClient GetSpeedRatio 成功: ratio={Ratio}", result.Ratio); + return result; + } + + /// + /// 设置控制器速度倍率。 + /// + /// 目标速度倍率。 + /// 取消令牌。 + /// 结果响应。 + public async Task SetSpeedRatioAsync(double ratio, CancellationToken cancellationToken = default) + { + _logger?.LogInformation("CommandClient SetSpeedRatio: ratio={Ratio}", ratio); + var frame = FanucCommandProtocol.PackSetSpeedRatioCommand(ratio); + var response = await SendRawFrameAsync(frame, cancellationToken).ConfigureAwait(false); + _logger?.LogDebug("CommandClient SetSpeedRatio 成功: ratio={Ratio}", ratio); + return EnsureSuccess(FanucCommandProtocol.ParseResultResponse(response)); + } + + /// + /// 读取控制器 TCP 位姿。 + /// + /// TCP ID。 + /// 取消令牌。 + /// TCP 位姿响应。 + public async Task GetTcpAsync(uint tcpId = 1, CancellationToken cancellationToken = default) + { + _logger?.LogDebug("CommandClient GetTcp: tcpId={TcpId}", tcpId); + var frame = FanucCommandProtocol.PackGetTcpCommand(tcpId); + var response = await SendRawFrameAsync(frame, cancellationToken).ConfigureAwait(false); + var result = EnsureSuccess(FanucCommandProtocol.ParseTcpResponse(response)); + _logger?.LogDebug("CommandClient GetTcp 成功: tcpId={TcpId}, pose=[{Pose}]", tcpId, string.Join(", ", result.Pose.Select(v => v.ToString("F2")))); + return result; + } + + /// + /// 设置控制器 TCP 位姿。 + /// + /// TCP ID。 + /// 7 维 TCP 位姿。 + /// 取消令牌。 + /// 结果响应。 + public async Task SetTcpAsync(uint tcpId, IReadOnlyList pose, CancellationToken cancellationToken = default) + { + _logger?.LogInformation("CommandClient SetTcp: tcpId={TcpId}, pose=[{Pose}]", tcpId, string.Join(", ", pose.Take(3).Select(v => v.ToString("F2")))); + var frame = FanucCommandProtocol.PackSetTcpCommand(tcpId, pose); + var response = await SendRawFrameAsync(frame, cancellationToken).ConfigureAwait(false); + _logger?.LogDebug("CommandClient SetTcp 成功: tcpId={TcpId}", tcpId); + return EnsureSuccess(FanucCommandProtocol.ParseResultResponse(response)); + } + + /// + /// 读取控制器 IO。 + /// + /// IO 索引。 + /// IO 类型字符串。 + /// 取消令牌。 + /// IO 读取响应。 + public async Task GetIoAsync(int port, string ioType, CancellationToken cancellationToken = default) + { + _logger?.LogDebug("CommandClient GetIo: port={Port}, ioType={IoType}", port, ioType); + var frame = FanucCommandProtocol.PackGetIoCommand(FanucIoTypes.FromName(ioType), port); + var response = await SendRawFrameAsync(frame, cancellationToken).ConfigureAwait(false); + var result = EnsureSuccess(FanucCommandProtocol.ParseIoResponse(response)); + _logger?.LogDebug("CommandClient GetIo 成功: port={Port}, value={Value}", port, result.Value); + return result; + } + + /// + /// 设置控制器 IO。 + /// + /// IO 索引。 + /// 目标 IO 值。 + /// IO 类型字符串。 + /// 取消令牌。 + /// 结果响应。 + public async Task SetIoAsync(int port, bool value, string ioType, CancellationToken cancellationToken = default) + { + _logger?.LogInformation("CommandClient SetIo: port={Port}, value={Value}, ioType={IoType}", port, value, ioType); + var frame = FanucCommandProtocol.PackSetIoCommand(FanucIoTypes.FromName(ioType), port, value); + var response = await SendRawFrameAsync(frame, cancellationToken).ConfigureAwait(false); + _logger?.LogDebug("CommandClient SetIo 成功: port={Port}, value={Value}", port, value); + return EnsureSuccess(FanucCommandProtocol.ParseResultResponse(response)); + } + + /// + /// 释放客户端资源。 + /// + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + _stream?.Dispose(); + _stream = null; + _tcpClient?.Dispose(); + _tcpClient = null; + _sendLock.Dispose(); + } + + /// + /// 直接发送已封装的帧并读取响应。 + /// + private async Task SendRawFrameAsync(byte[] frame, CancellationToken cancellationToken) + { + if (_stream is null) + { + throw new InvalidOperationException("命令通道未连接。"); + } + + await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + await _stream.WriteAsync(frame, cancellationToken).ConfigureAwait(false); + var response = await ReadResponseFrameAsync(cancellationToken).ConfigureAwait(false); + _logger?.LogDebug("CommandClient 发送帧成功: 帧长度={FrameLength}, 响应长度={ResponseLength}", frame.Length, response.Length); + return response; + } + finally + { + _sendLock.Release(); + } + } + + /// + /// 校验普通命令响应结果码,失败时抛出包含消息号和结果码的诊断异常。 + /// + private FanucCommandResultResponse EnsureSuccess(FanucCommandResultResponse response) + { + if (!response.IsSuccess) + { + _logger?.LogError("FANUC 命令失败: msgId=0x{MessageId:X4}, resultCode={ResultCode}", response.MessageId, response.ResultCode); + throw CreateCommandFailureException(response.MessageId, response.ResultCode); + } + + return response; + } + + /// + /// 校验程序状态响应结果码,失败时抛出包含消息号和结果码的诊断异常。 + /// + private FanucProgramStatusResponse EnsureSuccess(FanucProgramStatusResponse response) + { + if (!response.IsSuccess) + { + _logger?.LogError("FANUC 命令失败: msgId=0x{MessageId:X4}, resultCode={ResultCode}", response.MessageId, response.ResultCode); + throw CreateCommandFailureException(response.MessageId, response.ResultCode); + } + + return response; + } + + /// + /// 校验速度倍率响应结果码,失败时抛出包含消息号和结果码的诊断异常。 + /// + private FanucSpeedRatioResponse EnsureSuccess(FanucSpeedRatioResponse response) + { + if (!response.IsSuccess) + { + _logger?.LogError("FANUC 命令失败: msgId=0x{MessageId:X4}, resultCode={ResultCode}", response.MessageId, response.ResultCode); + throw CreateCommandFailureException(response.MessageId, response.ResultCode); + } + + return response; + } + + /// + /// 校验 TCP 位姿响应结果码,失败时抛出包含消息号和结果码的诊断异常。 + /// + private FanucTcpResponse EnsureSuccess(FanucTcpResponse response) + { + if (!response.IsSuccess) + { + _logger?.LogError("FANUC 命令失败: msgId=0x{MessageId:X4}, resultCode={ResultCode}", response.MessageId, response.ResultCode); + throw CreateCommandFailureException(response.MessageId, response.ResultCode); + } + + return response; + } + + /// + /// 校验 IO 读取响应结果码,失败时抛出包含消息号和结果码的诊断异常。 + /// + private FanucIoResponse EnsureSuccess(FanucIoResponse response) + { + if (!response.IsSuccess) + { + _logger?.LogError("FANUC 命令失败: msgId=0x{MessageId:X4}, resultCode={ResultCode}", response.MessageId, response.ResultCode); + throw CreateCommandFailureException(response.MessageId, response.ResultCode); + } + + return response; + } + + /// + /// 构造包含 FANUC 命令上下文的失败异常。 + /// + private static InvalidOperationException CreateCommandFailureException(uint messageId, uint resultCode) + { + return new InvalidOperationException( + $"FANUC command 0x{messageId:X4} failed with result_code {resultCode}."); + } + + /// + /// 从流中读取一条完整的 doz/zod 响应帧。 + /// + private async Task ReadResponseFrameAsync(CancellationToken cancellationToken) + { + if (_stream is null) + { + throw new InvalidOperationException("命令通道未连接。"); + } + + // 先读取 11 字节头:doz(3) + length(4) + msg_id(4) + var header = new byte[11]; + await ReadExactAsync(header, cancellationToken).ConfigureAwait(false); + + if (header[0] != (byte)'d' || header[1] != (byte)'o' || header[2] != (byte)'z') + { + throw new InvalidDataException("响应帧头 magic 不正确。"); + } + + var declaredLength = System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(header.AsSpan(3, 4)); + if (declaredLength < 14) + { + throw new InvalidDataException("响应帧声明长度过短。"); + } + + var remaining = (int)declaredLength - 11; + var frame = new byte[declaredLength]; + header.CopyTo(frame, 0); + await ReadExactAsync(frame.AsMemory(11, remaining), cancellationToken).ConfigureAwait(false); + + // 校验帧尾 + if (frame[^3] != (byte)'z' || frame[^2] != (byte)'o' || frame[^1] != (byte)'d') + { + throw new InvalidDataException("响应帧尾 magic 不正确。"); + } + + return frame; + } + + /// + /// 从流中精确读取指定长度的字节。 + /// + private async Task ReadExactAsync(Memory buffer, CancellationToken cancellationToken) + { + if (_stream is null) + { + throw new InvalidOperationException("命令通道未连接。"); + } + + var totalRead = 0; + while (totalRead < buffer.Length) + { + var read = await _stream.ReadAsync(buffer.Slice(totalRead), cancellationToken).ConfigureAwait(false); + if (read == 0) + { + throw new IOException("命令通道已断开,读取到 EOF。"); + } + + totalRead += read; + } + } +} diff --git a/src/Flyshot.Runtime.Fanuc/Protocol/FanucCommandProtocol.cs b/src/Flyshot.Runtime.Fanuc/Protocol/FanucCommandProtocol.cs new file mode 100644 index 0000000..5f29dc2 --- /dev/null +++ b/src/Flyshot.Runtime.Fanuc/Protocol/FanucCommandProtocol.cs @@ -0,0 +1,621 @@ +using System.Buffers.Binary; +using System.Text; + +namespace Flyshot.Runtime.Fanuc.Protocol; + +/// +/// 定义 FANUC TCP 10012 命令通道已经由抓包和逆向资料确认的消息号。 +/// +public static class FanucCommandMessageIds +{ + /// + /// 获取控制器程序状态的消息号。 + /// + public const uint GetProgramStatus = 0x2003; + + /// + /// 复位控制器的消息号。 + /// + public const uint ResetRobot = 0x2100; + + /// + /// 启动控制器程序的消息号。 + /// + public const uint StartProgram = 0x2102; + + /// + /// 停止控制器程序的消息号。 + /// + public const uint StopProgram = 0x2103; + + /// + /// 读取控制器 TCP 的消息号。 + /// + public const uint GetTcp = 0x2200; + + /// + /// 设置控制器 TCP 的消息号。 + /// + public const uint SetTcp = 0x2201; + + /// + /// 读取控制器速度倍率的消息号。 + /// + public const uint GetSpeedRatio = 0x2206; + + /// + /// 设置控制器速度倍率的消息号。 + /// + public const uint SetSpeedRatio = 0x2207; + + /// + /// 读取控制器 IO 的消息号。 + /// + public const uint GetIo = 0x2208; + + /// + /// 设置控制器 IO 的消息号。 + /// + public const uint SetIo = 0x2209; +} + +/// +/// 定义旧 ControllerClient 公开的 FANUC IO 类型枚举值。 +/// +public static class FanucIoTypes +{ + /// + /// FANUC 数字输入 DI。 + /// + public const uint DigitalInput = 1; + + /// + /// FANUC 数字输出 DO。 + /// + public const uint DigitalOutput = 2; + + /// + /// FANUC 机器人输入 RI。 + /// + public const uint RobotInput = 8; + + /// + /// FANUC 机器人输出 RO。 + /// + public const uint RobotOutput = 9; + + /// + /// 将 HTTP/兼容层传入的 IO 类型字符串转换为 FANUC 命令通道枚举值。 + /// + /// IO 类型字符串,例如 DI、DO、RI、RO。 + /// 命令通道使用的 IO 类型数值。 + public static uint FromName(string ioType) + { + if (string.IsNullOrWhiteSpace(ioType)) + { + throw new ArgumentException("IO 类型不能为空。", nameof(ioType)); + } + + return ioType.Trim().ToUpperInvariant() switch + { + "DI" or "KIOTYPEDI" => DigitalInput, + "DO" or "KIOTYPEDO" => DigitalOutput, + "RI" or "KIOTYPERI" => RobotInput, + "RO" or "KIOTYPERO" => RobotOutput, + _ => throw new ArgumentOutOfRangeException(nameof(ioType), ioType, "未知 IO 类型。") + }; + } +} + +/// +/// 表示 FANUC TCP 10012 命令通道中只携带结果码的响应。 +/// +public sealed class FanucCommandResultResponse +{ + /// + /// 初始化命令结果响应。 + /// + /// 响应对应的消息号。 + /// 控制器返回的结果码。 + public FanucCommandResultResponse(uint messageId, uint resultCode) + { + MessageId = messageId; + ResultCode = resultCode; + } + + /// + /// 获取响应对应的消息号。 + /// + public uint MessageId { get; } + + /// + /// 获取控制器返回的结果码。 + /// + public uint ResultCode { get; } + + /// + /// 获取当前响应是否表示成功。 + /// + public bool IsSuccess => ResultCode == 0; +} + +/// +/// 表示 FANUC TCP 10012 速度倍率响应。 +/// +public sealed class FanucSpeedRatioResponse +{ + /// + /// 初始化速度倍率响应。 + /// + /// 响应对应的消息号。 + /// 控制器返回的整数百分比。 + /// 控制器返回的结果码。 + public FanucSpeedRatioResponse(uint messageId, uint ratioInt, uint resultCode) + { + MessageId = messageId; + RatioInt = ratioInt; + ResultCode = resultCode; + } + + /// + /// 获取响应对应的消息号。 + /// + public uint MessageId { get; } + + /// + /// 获取控制器返回的整数百分比。 + /// + public uint RatioInt { get; } + + /// + /// 获取控制器返回的结果码。 + /// + public uint ResultCode { get; } + + /// + /// 获取转换后的 0.0 到 1.0 速度倍率。 + /// + public double Ratio => RatioInt / 100.0; + + /// + /// 获取当前响应是否表示成功。 + /// + public bool IsSuccess => ResultCode == 0; +} + +/// +/// 表示 FANUC TCP 10012 TCP 位姿响应。 +/// +public sealed class FanucTcpResponse +{ + /// + /// 初始化 TCP 位姿响应。 + /// + /// 响应对应的消息号。 + /// 控制器返回的结果码。 + /// 控制器返回的 TCP ID。 + /// 7 维 TCP 位姿。 + public FanucTcpResponse(uint messageId, uint resultCode, uint tcpId, IReadOnlyList pose) + { + MessageId = messageId; + ResultCode = resultCode; + TcpId = tcpId; + Pose = pose.ToArray(); + } + + /// + /// 获取响应对应的消息号。 + /// + public uint MessageId { get; } + + /// + /// 获取控制器返回的结果码。 + /// + public uint ResultCode { get; } + + /// + /// 获取控制器返回的 TCP ID。 + /// + public uint TcpId { get; } + + /// + /// 获取 7 维 TCP 位姿。 + /// + public IReadOnlyList Pose { get; } + + /// + /// 获取当前响应是否表示成功。 + /// + public bool IsSuccess => ResultCode == 0; +} + +/// +/// 表示 FANUC TCP 10012 IO 读取响应。 +/// +public sealed class FanucIoResponse +{ + /// + /// 初始化 IO 读取响应。 + /// + /// 响应对应的消息号。 + /// 控制器返回的结果码。 + /// 控制器返回的 float IO 数值。 + public FanucIoResponse(uint messageId, uint resultCode, double numericValue) + { + MessageId = messageId; + ResultCode = resultCode; + NumericValue = numericValue; + } + + /// + /// 获取响应对应的消息号。 + /// + public uint MessageId { get; } + + /// + /// 获取控制器返回的结果码。 + /// + public uint ResultCode { get; } + + /// + /// 获取控制器返回的原始数值。 + /// + public double NumericValue { get; } + + /// + /// 获取按布尔 IO 解释后的值。 + /// + public bool Value => Math.Abs(NumericValue) > double.Epsilon; + + /// + /// 获取当前响应是否表示成功。 + /// + public bool IsSuccess => ResultCode == 0; +} + +/// +/// 表示 FANUC TCP 10012 程序状态响应。 +/// +public sealed class FanucProgramStatusResponse +{ + /// + /// 初始化程序状态响应。 + /// + /// 响应对应的消息号。 + /// 控制器返回的结果码。 + /// 控制器程序状态。 + public FanucProgramStatusResponse(uint messageId, uint resultCode, uint programStatus) + { + MessageId = messageId; + ResultCode = resultCode; + ProgramStatus = programStatus; + } + + /// + /// 获取响应对应的消息号。 + /// + public uint MessageId { get; } + + /// + /// 获取控制器返回的结果码。 + /// + public uint ResultCode { get; } + + /// + /// 获取控制器程序状态值。 + /// + public uint ProgramStatus { get; } + + /// + /// 获取当前响应是否表示成功。 + /// + public bool IsSuccess => ResultCode == 0; +} + +/// +/// 提供 FANUC TCP 10012 命令通道的基础封包与响应解析能力。 +/// +public static class FanucCommandProtocol +{ + /// + /// 将无业务体命令封装为 TCP 10012 二进制帧。 + /// + /// 命令消息号。 + /// 可直接写入命令通道 Socket 的完整帧。 + public static byte[] PackEmptyCommand(uint messageId) + { + return PackFrame(messageId, ReadOnlySpan.Empty); + } + + /// + /// 将程序名命令封装为 TCP 10012 二进制帧。 + /// + /// 命令消息号。 + /// 控制器程序名。 + /// 可直接写入命令通道 Socket 的完整帧。 + public static byte[] PackProgramCommand(uint messageId, string programName) + { + if (string.IsNullOrWhiteSpace(programName)) + { + throw new ArgumentException("程序名不能为空。", nameof(programName)); + } + + var programNameBytes = Encoding.ASCII.GetBytes(programName); + var body = new byte[sizeof(uint) + programNameBytes.Length]; + BinaryPrimitives.WriteUInt32BigEndian(body.AsSpan(0, sizeof(uint)), (uint)programNameBytes.Length); + programNameBytes.CopyTo(body.AsSpan(sizeof(uint))); + + return PackFrame(messageId, body); + } + + /// + /// 封装读取速度倍率命令。 + /// + /// 可直接写入命令通道 Socket 的完整帧。 + public static byte[] PackGetSpeedRatioCommand() + { + return PackEmptyCommand(FanucCommandMessageIds.GetSpeedRatio); + } + + /// + /// 封装设置速度倍率命令,按旧系统逻辑转换为 0..100 的整数百分比。 + /// + /// 目标速度倍率。 + /// 可直接写入命令通道 Socket 的完整帧。 + public static byte[] PackSetSpeedRatioCommand(double ratio) + { + if (double.IsNaN(ratio) || double.IsInfinity(ratio)) + { + throw new ArgumentOutOfRangeException(nameof(ratio), "ratio 必须是有限数值。"); + } + + var ratioInt = (uint)Math.Clamp((int)(ratio * 100.0), 0, 100); + var body = new byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32BigEndian(body, ratioInt); + return PackFrame(FanucCommandMessageIds.SetSpeedRatio, body); + } + + /// + /// 封装读取 TCP 位姿命令。 + /// + /// 目标 TCP ID。 + /// 可直接写入命令通道 Socket 的完整帧。 + public static byte[] PackGetTcpCommand(uint tcpId) + { + var body = new byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32BigEndian(body, tcpId); + return PackFrame(FanucCommandMessageIds.GetTcp, body); + } + + /// + /// 封装设置 TCP 位姿命令。 + /// + /// 目标 TCP ID。 + /// 7 维 TCP 位姿。 + /// 可直接写入命令通道 Socket 的完整帧。 + public static byte[] PackSetTcpCommand(uint tcpId, IReadOnlyList pose) + { + ArgumentNullException.ThrowIfNull(pose); + if (pose.Count != 7) + { + throw new ArgumentException("TCP 位姿必须包含 7 个数值。", nameof(pose)); + } + + var body = new byte[sizeof(uint) + sizeof(float) * 7]; + BinaryPrimitives.WriteUInt32BigEndian(body.AsSpan(0, sizeof(uint)), tcpId); + for (int i = 0; i < 7; i++) + { + BinaryPrimitives.WriteSingleBigEndian(body.AsSpan(sizeof(uint) + i * sizeof(float), sizeof(float)), (float)pose[i]); + } + + return PackFrame(FanucCommandMessageIds.SetTcp, body); + } + + /// + /// 封装读取 IO 命令,字段顺序为 io_type 后接 io_index。 + /// + /// IO 类型数值。 + /// IO 索引。 + /// 可直接写入命令通道 Socket 的完整帧。 + public static byte[] PackGetIoCommand(uint ioType, int ioIndex) + { + if (ioIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(ioIndex), "IO 索引不能为负数。"); + } + + var body = new byte[sizeof(uint) * 2]; + BinaryPrimitives.WriteUInt32BigEndian(body.AsSpan(0, sizeof(uint)), ioType); + BinaryPrimitives.WriteUInt32BigEndian(body.AsSpan(sizeof(uint), sizeof(uint)), (uint)ioIndex); + return PackFrame(FanucCommandMessageIds.GetIo, body); + } + + /// + /// 封装设置 IO 命令,字段顺序为 io_type、io_index、float io_value。 + /// + /// IO 类型数值。 + /// IO 索引。 + /// 目标 IO 布尔值。 + /// 可直接写入命令通道 Socket 的完整帧。 + public static byte[] PackSetIoCommand(uint ioType, int ioIndex, bool value) + { + if (ioIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(ioIndex), "IO 索引不能为负数。"); + } + + var body = new byte[sizeof(uint) * 2 + sizeof(float)]; + BinaryPrimitives.WriteUInt32BigEndian(body.AsSpan(0, sizeof(uint)), ioType); + BinaryPrimitives.WriteUInt32BigEndian(body.AsSpan(sizeof(uint), sizeof(uint)), (uint)ioIndex); + BinaryPrimitives.WriteSingleBigEndian(body.AsSpan(sizeof(uint) * 2, sizeof(float)), value ? 1.0f : 0.0f); + return PackFrame(FanucCommandMessageIds.SetIo, body); + } + + /// + /// 解析只携带结果码的 TCP 10012 响应帧。 + /// + /// 完整响应帧。 + /// 命令结果响应。 + public static FanucCommandResultResponse ParseResultResponse(ReadOnlySpan frame) + { + var messageId = ValidateAndReadMessageId(frame); + var body = GetBody(frame); + if (body.Length < sizeof(uint)) + { + throw new InvalidDataException("FANUC 命令响应体长度不足。"); + } + + return new FanucCommandResultResponse( + messageId, + BinaryPrimitives.ReadUInt32BigEndian(body[..sizeof(uint)])); + } + + /// + /// 解析 GetSpeedRatio 的 TCP 10012 响应帧。 + /// + /// 完整响应帧。 + /// 速度倍率响应。 + public static FanucSpeedRatioResponse ParseSpeedRatioResponse(ReadOnlySpan frame) + { + var messageId = ValidateAndReadMessageId(frame); + var body = GetBody(frame); + if (body.Length < sizeof(uint) * 2) + { + throw new InvalidDataException("FANUC 速度倍率响应体长度不足。"); + } + + // GetSpeedRatio 的字段顺序特殊:ratio_int 在前,result_code 在后。 + var ratioInt = BinaryPrimitives.ReadUInt32BigEndian(body[..sizeof(uint)]); + var resultCode = BinaryPrimitives.ReadUInt32BigEndian(body.Slice(sizeof(uint), sizeof(uint))); + return new FanucSpeedRatioResponse(messageId, ratioInt, resultCode); + } + + /// + /// 解析 GetTCP 的 TCP 10012 响应帧。 + /// + /// 完整响应帧。 + /// TCP 位姿响应。 + public static FanucTcpResponse ParseTcpResponse(ReadOnlySpan frame) + { + var messageId = ValidateAndReadMessageId(frame); + var body = GetBody(frame); + if (body.Length < sizeof(uint) * 2 + sizeof(float) * 7) + { + throw new InvalidDataException("FANUC TCP 响应体长度不足。"); + } + + var resultCode = BinaryPrimitives.ReadUInt32BigEndian(body[..sizeof(uint)]); + var tcpId = BinaryPrimitives.ReadUInt32BigEndian(body.Slice(sizeof(uint), sizeof(uint))); + var pose = new double[7]; + for (int i = 0; i < pose.Length; i++) + { + pose[i] = BinaryPrimitives.ReadSingleBigEndian(body.Slice(sizeof(uint) * 2 + i * sizeof(float), sizeof(float))); + } + + return new FanucTcpResponse(messageId, resultCode, tcpId, pose); + } + + /// + /// 解析 GetIO 的 TCP 10012 响应帧。 + /// + /// 完整响应帧。 + /// IO 读取响应。 + public static FanucIoResponse ParseIoResponse(ReadOnlySpan frame) + { + var messageId = ValidateAndReadMessageId(frame); + var body = GetBody(frame); + if (body.Length < sizeof(uint) + sizeof(float)) + { + throw new InvalidDataException("FANUC IO 响应体长度不足。"); + } + + var resultCode = BinaryPrimitives.ReadUInt32BigEndian(body[..sizeof(uint)]); + var ioValue = BinaryPrimitives.ReadSingleBigEndian(body.Slice(sizeof(uint), sizeof(float))); + return new FanucIoResponse(messageId, resultCode, ioValue); + } + + /// + /// 解析 GetProgStatus 的 TCP 10012 响应帧。 + /// + /// 完整响应帧。 + /// 程序状态响应。 + public static FanucProgramStatusResponse ParseProgramStatusResponse(ReadOnlySpan frame) + { + var messageId = ValidateAndReadMessageId(frame); + var body = GetBody(frame); + if (body.Length < sizeof(uint) * 2) + { + throw new InvalidDataException("FANUC 程序状态响应体长度不足。"); + } + + // all-reconnect.pcap 中字段顺序为 prog_status 后接 result_code。 + var programStatus = BinaryPrimitives.ReadUInt32BigEndian(body[..sizeof(uint)]); + var resultCode = BinaryPrimitives.ReadUInt32BigEndian(body.Slice(sizeof(uint), sizeof(uint))); + return new FanucProgramStatusResponse(messageId, resultCode, programStatus); + } + + /// + /// 按 FANUC 命令通道 framing 规则封装完整帧。 + /// + /// 命令消息号。 + /// 业务体。 + /// 完整命令帧。 + internal static byte[] PackFrame(uint messageId, ReadOnlySpan body) + { + var frameLength = 3 + sizeof(uint) + sizeof(uint) + body.Length + 3; + var frame = new byte[frameLength]; + + frame[0] = (byte)'d'; + frame[1] = (byte)'o'; + frame[2] = (byte)'z'; + BinaryPrimitives.WriteUInt32BigEndian(frame.AsSpan(3, sizeof(uint)), (uint)frameLength); + BinaryPrimitives.WriteUInt32BigEndian(frame.AsSpan(7, sizeof(uint)), messageId); + body.CopyTo(frame.AsSpan(11)); + frame[^3] = (byte)'z'; + frame[^2] = (byte)'o'; + frame[^1] = (byte)'d'; + + return frame; + } + + /// + /// 校验完整帧并读取消息号。 + /// + /// 完整响应帧。 + /// 响应消息号。 + private static uint ValidateAndReadMessageId(ReadOnlySpan frame) + { + if (frame.Length < 14) + { + throw new InvalidDataException("FANUC 命令帧长度不足。"); + } + + if (frame[0] != (byte)'d' || frame[1] != (byte)'o' || frame[2] != (byte)'z') + { + throw new InvalidDataException("FANUC 命令帧头 magic 不正确。"); + } + + if (frame[^3] != (byte)'z' || frame[^2] != (byte)'o' || frame[^1] != (byte)'d') + { + throw new InvalidDataException("FANUC 命令帧尾 magic 不正确。"); + } + + var declaredLength = BinaryPrimitives.ReadUInt32BigEndian(frame.Slice(3, sizeof(uint))); + if (declaredLength != frame.Length) + { + throw new InvalidDataException("FANUC 命令帧长度字段与实际长度不一致。"); + } + + return BinaryPrimitives.ReadUInt32BigEndian(frame.Slice(7, sizeof(uint))); + } + + /// + /// 获取完整帧中的业务体切片。 + /// + /// 完整响应帧。 + /// 业务体切片。 + private static ReadOnlySpan GetBody(ReadOnlySpan frame) + { + return frame.Slice(11, frame.Length - 14); + } +} diff --git a/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Client.cs b/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Client.cs new file mode 100644 index 0000000..7f033c3 --- /dev/null +++ b/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Client.cs @@ -0,0 +1,654 @@ +using System.Diagnostics; +using System.Net.Sockets; +using Microsoft.Extensions.Logging; + +namespace Flyshot.Runtime.Fanuc.Protocol; + +/// +/// FANUC UDP 60015 J519/ICSP 伺服运动客户端,提供状态包驱动的命令发送与响应接收能力。 +/// +public sealed class FanucJ519Client : IDisposable +{ + private readonly object _commandLock = new(); + private readonly object _responseLock = new(); + private readonly ILogger? _logger; + private UdpClient? _udpClient; + private CancellationTokenSource? _cts; + private Thread? _receiveThread; + private FanucJ519Command? _currentCommand; + private FanucJ519Command? _lastSentCommand; + // 稠密轨迹执行时预装的命令队列,由机器人状态包节拍逐帧出队。 + private Queue? _commandQueue; + private TaskCompletionSource? _commandQueueDrainedCompletion; + private List? _commandHistoryForTests; + private FanucJ519Response? _latestResponse; + private long _slowSendCount; + private long _maxReceiveToSendTicks; + private uint _sequenceBufferSize; + // 标记 StartMotion 前是否刚装载过新目标,用于区分新命令和上次运动残留目标。 + private bool _hasPendingCommandForStart; + private bool _motionStarted; + private bool _disposed; + + /// + /// 获取当前是否已创建 UDP 套接字。 + /// + public bool IsConnected => _udpClient is not null; + + /// + /// 初始化 FANUC J519 客户端。 + /// + /// 日志记录器;允许 null,供无日志场景使用。 + public FanucJ519Client(ILogger? logger = null) + { + _logger = logger; + } + + /// + /// 建立到 FANUC 控制柜 UDP 60015 运动通道的连接并启动接收循环。 + /// + /// 控制柜 IP 地址。 + /// 运动通道端口,默认 60015。 + /// 取消令牌。 + public async Task ConnectAsync(string ip, int port = 60015, CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (string.IsNullOrWhiteSpace(ip)) + { + throw new ArgumentException("IP 不能为空。", nameof(ip)); + } + + if (_udpClient is not null) + { + throw new InvalidOperationException("J519 通道已经连接,请先 Disconnect。"); + } + + _logger?.LogInformation("J519 ConnectAsync: {Ip}:{Port}", ip, port); + + _udpClient = new UdpClient(); + _udpClient.Connect(ip, port); + ConfigureRealtimeSocket(_udpClient.Client); + ConfigureProcessPriority(); + + // 发送初始化包。 + await _udpClient.SendAsync(FanucJ519Protocol.PackInitPacket(), cancellationToken).ConfigureAwait(false); + _logger?.LogInformation("J519 初始化包已发送"); + + _cts = new CancellationTokenSource(); + _receiveThread = new Thread(ReceiveLoop) + { + IsBackground = true, + Name = "J519 UDP realtime loop", + Priority = ThreadPriority.Highest + }; + _receiveThread.Start(); + } + + /// + /// 启动 J519 命令发送许可;实际发包由机器人状态包节拍驱动。 + /// + public void StartMotion() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (_udpClient is null) + { + throw new InvalidOperationException("J519 通道未连接。"); + } + + lock (_commandLock) + { + _lastSentCommand = null; + if (_motionStarted) + { + _logger?.LogDebug("J519 StartMotion: 状态包驱动发送已启用"); + return; + } + + if (!_hasPendingCommandForStart) + { + _currentCommand = null; + CompleteCommandQueueLocked(); + } + + _hasPendingCommandForStart = false; + _motionStarted = true; + + } + + _logger?.LogInformation("J519 StartMotion: 已启用状态包驱动发送"); + } + + /// + /// 配置状态包驱动回发时附加到机器人 sequence 的前视缓冲深度。 + /// + /// 要附加到状态序号上的缓冲深度。 + public void SetSequenceBufferSize(int bufferSize) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize), "J519 sequence buffer size 不能为负数。"); + } + + lock (_commandLock) + { + _sequenceBufferSize = (uint)bufferSize; + } + } + + /// + /// 发送状态输出停止包并停止 J519 命令发送。 + /// + public async Task StopMotionAsync(CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (_udpClient is null) + { + return; + } + + _logger?.LogInformation("J519 StopMotionAsync: 停止状态包驱动发送"); + lock (_commandLock) + { + _motionStarted = false; + _hasPendingCommandForStart = false; + CompleteCommandQueueLocked(); + } + + // FANUC 手册中 packet type=2 表示停止状态包输出;当前保留现场抓包兼容行为。 + await _udpClient.SendAsync(FanucJ519Protocol.PackEndPacket(), cancellationToken).ConfigureAwait(false); + _logger?.LogInformation("J519 StopMotionAsync: 状态输出停止包已发送"); + } + + /// + /// 原子更新下一周期要发送的 J519 命令。 + /// + /// 新的 J519 命令。 + public void UpdateCommand(FanucJ519Command command) + { + ArgumentNullException.ThrowIfNull(command); + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_commandLock) + { + CompleteCommandQueueLocked(); + _currentCommand = command; + _hasPendingCommandForStart = true; + _commandHistoryForTests?.Add(command); + } + + if (_logger?.IsEnabled(LogLevel.Debug) == true) + { + //_logger.LogDebug( + // "J519 UpdateCommand: joints={Joints}, ioMask={IoMask}, ioValue={IoValue}", + // command.TargetJoints, + // command.WriteIoMask, + // command.WriteIoValue); + } + } + + /// + /// 装载一整段 J519 命令队列;后续每个可接收命令的机器人状态包会自动取出下一帧。 + /// + /// 按执行顺序排列的 J519 命令列表,至少包含一帧。 + public void LoadCommandQueue(IReadOnlyList commands) + { + ArgumentNullException.ThrowIfNull(commands); + ObjectDisposedException.ThrowIf(_disposed, this); + if (commands.Count == 0) + { + throw new ArgumentException("J519 命令队列至少需要包含一帧。", nameof(commands)); + } + + lock (_commandLock) + { + CompleteCommandQueueLocked(); + _commandQueue = new Queue(commands); + // 队列耗尽后继续保持最后一帧目标,避免运动结束后回落到旧目标或空目标。 + _currentCommand = commands[^1]; + _hasPendingCommandForStart = true; + _commandQueueDrainedCompletion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _commandHistoryForTests?.AddRange(commands); + } + _logger?.LogInformation("J519 命令队列已装载: count={Count}", commands.Count); + _logger?.LogInformation("开始运动前向机器人发送的sequence={Sequence}",_lastSentCommand?.Sequence ?? 0); + } + + /// + /// 等待当前预装命令队列被状态包全部取出;无队列时立即完成。 + /// + /// 取消令牌。 + /// 表示等待过程的任务。 + internal Task WaitForCommandQueueDrainedAsync(CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + Task waitTask; + lock (_commandLock) + { + waitTask = _commandQueueDrainedCompletion?.Task ?? Task.CompletedTask; + } + + return waitTask.WaitAsync(cancellationToken); + } + + /// 判断当前是否没有等待出队的命令;仅供单元测试断言。 + /// + /// 如果队列为空或尚未装载队列,则返回 true。 + internal bool IsCommandQueueDrainedForTests() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_commandLock) + { + return _commandQueue is null || _commandQueue.Count == 0; + } + } + + /// + /// 打开命令历史记录,仅供单元测试验证运行时生成的命令序列。 + /// + internal void EnableCommandHistoryForTests() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_commandLock) + { + _commandHistoryForTests = []; + } + } + + /// + /// 获取测试记录的命令历史。 + /// + /// 命令历史快照。 + internal IReadOnlyList GetCommandHistoryForTests() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_commandLock) + { + return _commandHistoryForTests?.ToArray() ?? Array.Empty(); + } + } + + /// + /// 获取最近一次通过 UpdateCommand 设置的 J519 命令;供测试断言使用。 + /// + /// 当前 J519 命令或 null。 + internal FanucJ519Command? GetCurrentCommand() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_commandLock) + { + return _currentCommand; + } + } + + /// + /// 获取最近一次解析的 J519 响应;若尚未收到任何响应则返回 null。 + /// + /// 最新 J519 响应或 null。 + public FanucJ519Response? GetLatestResponse() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_responseLock) + { + return _latestResponse; + } + } + + /// + /// 断开 J519 通道并释放资源。 + /// + public void Disconnect() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + _cts?.Cancel(); + + try + { + _udpClient?.Dispose(); + _receiveThread?.Join(TimeSpan.FromSeconds(1)); + } + catch (ObjectDisposedException) + { + // 忽略释放期间的套接字关闭异常。 + } + + _receiveThread = null; + + _cts?.Dispose(); + _cts = null; + + _udpClient = null; + + lock (_commandLock) + { + _currentCommand = null; + _lastSentCommand = null; + CompleteCommandQueueLocked(); + _commandHistoryForTests = null; + _hasPendingCommandForStart = false; + _motionStarted = false; + } + + lock (_responseLock) + { + _latestResponse = null; + } + } + + /// + /// 释放客户端资源。 + /// + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + _cts?.Cancel(); + + try + { + _udpClient?.Dispose(); + _receiveThread?.Join(TimeSpan.FromSeconds(1)); + } + catch (ObjectDisposedException) + { + // 忽略释放期间的套接字关闭异常。 + } + + _cts?.Dispose(); + + lock (_commandLock) + { + CompleteCommandQueueLocked(); + _hasPendingCommandForStart = false; + } + } + + private static void ConfigureProcessPriority() + { + try + { + var process = Process.GetCurrentProcess(); + if (process.PriorityClass < ProcessPriorityClass.High) + { + process.PriorityClass = ProcessPriorityClass.High; + } + } + catch (Exception) + { + // 某些部署环境不允许提升进程优先级;实时链路仍按普通优先级运行。 + } + } + + /// + /// 配置 J519 UDP 套接字的低延迟参数。 + /// + /// 已连接 FANUC 60015 的 UDP 套接字。 + private void ConfigureRealtimeSocket(Socket socket) + { + socket.ReceiveBufferSize = 1024 * 1024; + socket.SendBufferSize = 1024 * 1024; + + try + { + // DSCP EF(46) 标记低延迟流量,是否生效取决于现场网卡、交换机和控制柜网络策略。 + socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, 0xB8); + } + catch (SocketException ex) + { + _logger?.LogWarning(ex, "J519 UDP 套接字无法设置 DSCP EF 优先级标记"); + } + } + + /// + /// 后台接收循环:在专用高优先级线程中同步接收 132B 响应并立即回发命令。 + /// + private void ReceiveLoop() + { + var udpClient = _udpClient; + var cancellationToken = _cts?.Token ?? CancellationToken.None; + if (udpClient is null) + { + return; + } + + _logger?.LogInformation("J519 ReceiveLoop 启动"); + long receiveCount = 0; + FanucJ519Response? lastLoggedResponse = null; + var receiveBuffer = new byte[FanucJ519Protocol.ResponsePacketLength]; + var commandBuffer = new byte[FanucJ519Protocol.CommandPacketLength]; + + try + { + while (!cancellationToken.IsCancellationRequested) + { + var received = udpClient.Client.Receive(receiveBuffer); + if (received == FanucJ519Protocol.ResponsePacketLength) + { + var receiveTicks = Stopwatch.GetTimestamp(); + var response = FanucJ519Protocol.ParseResponse(receiveBuffer); + + // 先按状态包节拍回发命令,再做低频日志处理,减少受信周期内的非必要工作。 + if (response.AcceptsCommand) + { + SendCommandForStatus(udpClient.Client, response, commandBuffer, receiveTicks); + } + + lock (_responseLock) + { + _latestResponse = response; + } + + receiveCount++; + + // 仅在状态变化时记录 Info,避免高频日志。 + if (lastLoggedResponse is null + || lastLoggedResponse.Status != response.Status + || lastLoggedResponse.RobotInMotion != response.RobotInMotion + || lastLoggedResponse.SystemReady != response.SystemReady + || lastLoggedResponse.AcceptsCommand != response.AcceptsCommand) + { + _logger?.LogInformation( + "J519 响应: status=0x{Status:X2}, seq={Seq}, accept={Accept}, received={received}, SystemReady={SystemReady}, RobotInMotion={RobotInMotion}, pose=[{Pose}], joints=[{Joints}]", + response.Status, + response.Sequence, + response.AcceptsCommand, + response.ReceivedCommand, + response.SystemReady, + response.RobotInMotion, + string.Join(", ", response.Pose.Select(v => v.ToString("F3"))), + string.Join(", ", response.JointDegrees.Take(6).Select(v => v.ToString("F3")))); + + var lastSentTargetJoints = GetLastSentTargetJointsLogText(); + _logger?.LogInformation("J519 最后一条发送目标关节轴: joints=[{Joints}]", lastSentTargetJoints); + lastLoggedResponse = response; + + // 如果状态从AcceptsCommand true 变为false,说明机器人报错,清空队列 + if (!response.AcceptsCommand) + { + lock (_commandLock) + { + _currentCommand = null; + CompleteCommandQueueLocked(); + } + _logger?.LogWarning("J519 接收状态包显示机器人不可接受命令,已清空命令队列"); + } + + } + else if (receiveCount % 1000 == 0) + { + var maxReceiveToSendMs = Stopwatch.GetElapsedTime(0, Interlocked.Read(ref _maxReceiveToSendTicks)).TotalMilliseconds; + _logger?.LogDebug( + "J519 已接收 {Count} 个响应包,receive-to-send 最大耗时约 {MaxMs:F3}ms,超过 0.5ms 次数 {SlowCount}", + receiveCount, + maxReceiveToSendMs, + Interlocked.Read(ref _slowSendCount)); + } + } + } + } + catch (OperationCanceledException) + { + _logger?.LogInformation("J519 ReceiveLoop 正常取消,共接收 {Count} 个包", receiveCount); + } + catch (ObjectDisposedException) + { + _logger?.LogInformation("J519 ReceiveLoop 因 UDP 释放退出,共接收 {Count} 个包", receiveCount); + } + catch (SocketException ex) when (cancellationToken.IsCancellationRequested) + { + _logger?.LogInformation(ex, "J519 ReceiveLoop 因取消关闭套接字退出,共接收 {Count} 个包", receiveCount); + } + } + + /// + /// 按机器人状态包的 sequence 立即回发当前 J519 命令。 + /// + /// 已连接的 UDP 套接字。 + /// 刚收到的机器人状态包。 + /// 可复用的 64B 命令包缓冲区。 + /// 收到状态包后的时间戳。 + private void SendCommandForStatus(Socket socket, FanucJ519Response response, byte[] commandBuffer, long receiveTicks) + { + FanucJ519Command? command; + var willDrainQueue = false; + lock (_commandLock) + { + if (!_motionStarted) + { + command = null; + } + else if (_commandQueue is { Count: > 0 } queue) + { + // 状态包是唯一节拍源:每收到一帧可接收状态,才取出下一条目标。 + var queuedCommand = queue.Dequeue(); + _currentCommand = queuedCommand; + willDrainQueue = queue.Count == 0; + command = queuedCommand; + } + else + { + command = _currentCommand; + } + } + + if (command is null) + { + command = TryBuildHoldCommandFromLatestResponse(response); + } + + if (command is null) + { + return; + } + + uint sequenceToSend; + lock (_commandLock) + { + sequenceToSend = response.Sequence + _sequenceBufferSize; + } + + FanucJ519Protocol.PackCommandPacket(command, sequenceToSend, commandBuffer); + socket.Send(commandBuffer); + TrackReceiveToSendLatency(receiveTicks); + // _logger?.LogDebug("J519 已回发命令包,seq={Seq}", sequence); + // _logger?.LogDebug( + // "J519 回发命令详情: joints={Joints}, ioMask={IoMask}, ioValue={IoValue}", + // command.TargetJoints, + // command.WriteIoMask, + // command.WriteIoValue); + lock (_commandLock) + { + _lastSentCommand = command; + if (willDrainQueue && _commandQueue is { Count: 0 }) + { + CompleteCommandQueueLocked(); + } + } + } + + /// + /// 当当前没有显式目标时,使用最近一帧状态反馈关节角构造保姿命令,维持机器人当前位置。 + /// + /// 当前收到的机器人状态包。 + /// 可用于保姿的临时 J519 命令;若反馈关节不足则返回 null。 + private static FanucJ519Command? TryBuildHoldCommandFromLatestResponse(FanucJ519Response response) + { + if (response.JointDegrees.Count < 6) + { + return null; + } + + // 无运动目标时,持续回发机器人当前反馈关节,保持伺服流与机器人当前位置一致。 + return new FanucJ519Command( + sequence: response.Sequence, + targetJoints: + [ + response.JointDegrees[0], + response.JointDegrees[1], + response.JointDegrees[2], + response.JointDegrees[3], + response.JointDegrees[4], + response.JointDegrees[5] + ]); + } + + /// + /// 记录状态包到命令包发出的最大耗时和慢发送次数,供低频诊断日志观察调度抖动。 + /// + /// 收到状态包后的时间戳。 + private void TrackReceiveToSendLatency(long receiveTicks) + { + var elapsedTicks = Stopwatch.GetTimestamp() - receiveTicks; + var currentMax = Interlocked.Read(ref _maxReceiveToSendTicks); + while (elapsedTicks > currentMax + && Interlocked.CompareExchange(ref _maxReceiveToSendTicks, elapsedTicks, currentMax) != currentMax) + { + currentMax = Interlocked.Read(ref _maxReceiveToSendTicks); + } + + if (Stopwatch.GetElapsedTime(0, elapsedTicks) > TimeSpan.FromMilliseconds(0.5)) + { + Interlocked.Increment(ref _slowSendCount); + } + } + + /// + /// 清空当前命令队列,并唤醒等待队列结束的运行时任务。 + /// + private void CompleteCommandQueueLocked() + { + _commandQueue?.Clear(); + _commandQueue = null; + _commandQueueDrainedCompletion?.TrySetResult(); + _commandQueueDrainedCompletion = null; + } + + /// + /// 读取最近一次已成功发送命令的目标关节轴文本,便于状态日志直接对照控制目标。 + /// + /// 格式化后的目标关节轴文本;如果尚未发送命令则返回占位符。 + private string GetLastSentTargetJointsLogText() + { + lock (_commandLock) + { + return _lastSentCommand is null + ? "n/a" + : string.Join(", ", _lastSentCommand.TargetJoints.Take(6).Select(v => v.ToString("F5"))); + } + } +} diff --git a/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Protocol.cs b/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Protocol.cs new file mode 100644 index 0000000..4d29a95 --- /dev/null +++ b/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Protocol.cs @@ -0,0 +1,450 @@ +using System.Buffers.Binary; + +namespace Flyshot.Runtime.Fanuc.Protocol; + +/// +/// 表示 FANUC UDP 60015 J519/ICSP 伺服流中的一帧命令数据。 +/// +public sealed class FanucJ519Command +{ + private readonly double[] _targetValues; + + /// + /// 初始化 J519 命令数据。 + /// + /// 命令序号。 + /// 目标关节或扩展轴数据,最多 9 个槽位。 + /// 是否为最后一帧数据。 + /// 读取 IO 类型。 + /// 读取 IO 起始索引。 + /// 读取 IO 掩码。 + /// 目标数据类型。 + /// 写入 IO 类型。 + /// 写入 IO 起始索引。 + /// 写入 IO 掩码。 + /// 写入 IO 数值。 + public FanucJ519Command( + uint sequence, + IReadOnlyList targetJoints, + byte lastData = 0, + byte readIoType = 2, + ushort readIoIndex = 1, + ushort readIoMask = 255, + byte dataStyle = 1, + byte writeIoType = 2, + ushort writeIoIndex = 1, + ushort writeIoMask = 0, + ushort writeIoValue = 0) + : this( + sequence, + targetValues: targetJoints, + lastData, + readIoType, + readIoIndex, + readIoMask, + dataStyle, + writeIoType, + writeIoIndex, + writeIoMask, + writeIoValue) + { + } + + /// + /// 初始化 J519 命令数据。 + /// + /// 命令序号。 + /// J519 目标槽位,直角模式为 X/Y/Z/W/P/R/E1/E2/E3,关节模式为 J1..J9。 + /// 是否为最后一帧数据。 + /// 读取 IO 类型。 + /// 读取 IO 起始索引。 + /// 读取 IO 掩码。 + /// 目标数据类型,0 为直角坐标,1 为关节坐标。 + /// 写入 IO 类型。 + /// 写入 IO 起始索引。 + /// 写入 IO 掩码。 + /// 写入 IO 数值。 + public FanucJ519Command( + uint sequence, + IEnumerable targetValues, + byte lastData = 0, + byte readIoType = 2, + ushort readIoIndex = 1, + ushort readIoMask = 255, + byte dataStyle = 1, + byte writeIoType = 2, + ushort writeIoIndex = 1, + ushort writeIoMask = 0, + ushort writeIoValue = 0) + { + ArgumentNullException.ThrowIfNull(targetValues); + var copiedTargetValues = targetValues.ToArray(); + if (copiedTargetValues.Length is <= 0 or > 9) + { + throw new ArgumentOutOfRangeException(nameof(targetValues), "J519 目标数据必须包含 1 到 9 个槽位。"); + } + + Sequence = sequence; + LastData = lastData; + ReadIoType = readIoType; + ReadIoIndex = readIoIndex; + ReadIoMask = readIoMask; + DataStyle = dataStyle; + WriteIoType = writeIoType; + WriteIoIndex = writeIoIndex; + WriteIoMask = writeIoMask; + WriteIoValue = writeIoValue; + _targetValues = copiedTargetValues; + } + + /// + /// 获取命令序号。 + /// + public uint Sequence { get; } + + /// + /// 获取是否为最后一帧数据。 + /// + public byte LastData { get; } + + /// + /// 获取读取 IO 类型。 + /// + public byte ReadIoType { get; } + + /// + /// 获取读取 IO 起始索引。 + /// + public ushort ReadIoIndex { get; } + + /// + /// 获取读取 IO 掩码。 + /// + public ushort ReadIoMask { get; } + + /// + /// 获取目标数据类型。 + /// + public byte DataStyle { get; } + + /// + /// 获取写入 IO 类型。 + /// + public byte WriteIoType { get; } + + /// + /// 获取写入 IO 起始索引。 + /// + public ushort WriteIoIndex { get; } + + /// + /// 获取写入 IO 掩码。 + /// + public ushort WriteIoMask { get; } + + /// + /// 获取写入 IO 数值。 + /// + public ushort WriteIoValue { get; } + + /// + /// 获取目标关节或扩展轴数据。 + /// + public IReadOnlyList TargetJoints => _targetValues; + + /// + /// 获取 J519 目标槽位,直角模式为 X/Y/Z/W/P/R/E1/E2/E3,关节模式为 J1..J9。 + /// + public IReadOnlyList TargetValues => _targetValues; +} + +/// +/// 表示 FANUC UDP 60015 J519/ICSP 伺服流中的一帧响应数据。 +/// +public sealed class FanucJ519Response +{ + private readonly double[] _pose; + private readonly double[] _externalAxes; + private readonly double[] _jointDegrees; + private readonly double[] _motorCurrents; + + /// + /// 初始化 J519 响应数据。 + /// + /// 响应类型。 + /// 协议版本。 + /// 响应序号。 + /// 状态位集合。 + /// 读取 IO 类型。 + /// 读取 IO 起始索引。 + /// 读取 IO 掩码。 + /// 读取 IO 数值。 + /// 控制器时间戳。 + /// TCP 笛卡尔位姿。 + /// 扩展轴反馈。 + /// 关节角度反馈。 + /// 电机电流反馈。 + public FanucJ519Response( + uint messageType, + uint version, + uint sequence, + byte status, + byte readIoType, + ushort readIoIndex, + ushort readIoMask, + ushort readIoValue, + uint timestamp, + IEnumerable pose, + IEnumerable externalAxes, + IEnumerable jointDegrees, + IEnumerable motorCurrents) + { + MessageType = messageType; + Version = version; + Sequence = sequence; + Status = status; + ReadIoType = readIoType; + ReadIoIndex = readIoIndex; + ReadIoMask = readIoMask; + ReadIoValue = readIoValue; + Timestamp = timestamp; + _pose = pose?.ToArray() ?? throw new ArgumentNullException(nameof(pose)); + _externalAxes = externalAxes?.ToArray() ?? throw new ArgumentNullException(nameof(externalAxes)); + _jointDegrees = jointDegrees?.ToArray() ?? throw new ArgumentNullException(nameof(jointDegrees)); + _motorCurrents = motorCurrents?.ToArray() ?? throw new ArgumentNullException(nameof(motorCurrents)); + } + + /// + /// 获取响应类型。 + /// + public uint MessageType { get; } + + /// + /// 获取协议版本。 + /// + public uint Version { get; } + + /// + /// 获取响应序号。 + /// + public uint Sequence { get; } + + /// + /// 获取状态位集合。 + /// + public byte Status { get; } + + /// + /// 获取读取 IO 类型。 + /// + public byte ReadIoType { get; } + + /// + /// 获取读取 IO 起始索引。 + /// + public ushort ReadIoIndex { get; } + + /// + /// 获取读取 IO 掩码。 + /// + public ushort ReadIoMask { get; } + + /// + /// 获取读取 IO 数值。 + /// + public ushort ReadIoValue { get; } + + /// + /// 获取控制器时间戳。 + /// + public uint Timestamp { get; } + + /// + /// 获取 TCP 笛卡尔位姿。 + /// + public IReadOnlyList Pose => _pose; + + /// + /// 获取扩展轴反馈。 + /// + public IReadOnlyList ExternalAxes => _externalAxes; + + /// + /// 获取关节角度反馈。 + /// + public IReadOnlyList JointDegrees => _jointDegrees; + + /// + /// 获取电机电流反馈。 + /// + public IReadOnlyList MotorCurrents => _motorCurrents; + + /// + /// 获取控制器是否接受命令。 + /// + public bool AcceptsCommand => (Status & 0b0001) != 0; + + /// + /// 获取控制器是否已收到命令。 + /// + public bool ReceivedCommand => (Status & 0b0010) != 0; + + /// + /// 获取控制器系统是否就绪。 + /// + public bool SystemReady => (Status & 0b0100) != 0; + + /// + /// 获取机器人是否处于运动中。 + /// + public bool RobotInMotion => (Status & 0b1000) != 0; +} + +/// +/// 提供 FANUC UDP 60015 J519/ICSP 伺服流的基础封包与响应解析能力。 +/// +public static class FanucJ519Protocol +{ + /// + /// J519 初始化和结束控制包长度。 + /// + public const int ControlPacketLength = 8; + + /// + /// J519 命令包长度。 + /// + public const int CommandPacketLength = 64; + + /// + /// J519 响应包长度。 + /// + public const int ResponsePacketLength = 132; + + /// + /// 封装 J519 初始化包。 + /// + /// 初始化包。 + public static byte[] PackInitPacket() + { + return PackControlPacket(0); + } + + /// + /// 封装 J519 结束包。 + /// + /// 结束包。 + public static byte[] PackEndPacket() + { + return PackControlPacket(2); + } + + /// + /// 封装 J519 64 字节命令包。 + /// + /// 命令数据。 + /// 命令包。 + public static byte[] PackCommandPacket(FanucJ519Command command) + { + ArgumentNullException.ThrowIfNull(command); + + var packet = new byte[CommandPacketLength]; + PackCommandPacket(command, command.Sequence, packet); + return packet; + } + + /// + /// 将 J519 64 字节命令包写入调用方提供的缓冲区。 + /// + /// 命令数据。 + /// 本次出包使用的机器人状态包序号。 + /// 长度至少为 64 字节的命令包缓冲区。 + public static void PackCommandPacket(FanucJ519Command command, uint sequence, Span packet) + { + ArgumentNullException.ThrowIfNull(command); + if (packet.Length < CommandPacketLength) + { + throw new ArgumentException("J519 命令包缓冲区长度不足。", nameof(packet)); + } + + packet.Slice(0, CommandPacketLength).Clear(); + BinaryPrimitives.WriteUInt32BigEndian(packet.Slice(0x00, sizeof(uint)), 1); + BinaryPrimitives.WriteUInt32BigEndian(packet.Slice(0x04, sizeof(uint)), 1); + BinaryPrimitives.WriteUInt32BigEndian(packet.Slice(0x08, sizeof(uint)), sequence); + packet[0x0c] = command.LastData; + packet[0x0d] = command.ReadIoType; + BinaryPrimitives.WriteUInt16BigEndian(packet.Slice(0x0e, sizeof(ushort)), command.ReadIoIndex); + BinaryPrimitives.WriteUInt16BigEndian(packet.Slice(0x10, sizeof(ushort)), command.ReadIoMask); + packet[0x12] = command.DataStyle; + packet[0x13] = command.WriteIoType; + BinaryPrimitives.WriteUInt16BigEndian(packet.Slice(0x14, sizeof(ushort)), command.WriteIoIndex); + BinaryPrimitives.WriteUInt16BigEndian(packet.Slice(0x16, sizeof(ushort)), command.WriteIoMask); + BinaryPrimitives.WriteUInt16BigEndian(packet.Slice(0x18, sizeof(ushort)), command.WriteIoValue); + BinaryPrimitives.WriteUInt16BigEndian(packet.Slice(0x1a, sizeof(ushort)), 0); + + // J519 命令包固定保留 9 个 f32 目标槽位,少于 9 个时剩余槽位补零。 + for (var index = 0; index < 9; index++) + { + var value = index < command.TargetJoints.Count ? command.TargetJoints[index] : 0.0; + BinaryPrimitives.WriteSingleBigEndian(packet.Slice(0x1c + (index * sizeof(float)), sizeof(float)), (float)value); + } + } + + /// + /// 解析 J519 132 字节响应包。 + /// + /// 响应包。 + /// 响应解析结果。 + public static FanucJ519Response ParseResponse(ReadOnlySpan packet) + { + if (packet.Length != ResponsePacketLength) + { + throw new InvalidDataException("FANUC J519 响应包长度不正确。"); + } + + return new FanucJ519Response( + BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0x00, sizeof(uint))), + BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0x04, sizeof(uint))), + BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0x08, sizeof(uint))), + packet[0x0c], + packet[0x0d], + BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(0x0e, sizeof(ushort))), + BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(0x10, sizeof(ushort))), + BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(0x12, sizeof(ushort))), + BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0x14, sizeof(uint))), + ReadFloatArray(packet, 0x18, 6), + ReadFloatArray(packet, 0x30, 3), + ReadFloatArray(packet, 0x3c, 9), + ReadFloatArray(packet, 0x60, 9)); + } + + /// + /// 封装 J519 控制包。 + /// + /// 控制包类型。 + /// 控制包。 + private static byte[] PackControlPacket(uint packetType) + { + var packet = new byte[ControlPacketLength]; + BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0, sizeof(uint)), packetType); + BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(sizeof(uint), sizeof(uint)), 1); + return packet; + } + + /// + /// 从响应包中读取固定长度 f32 数组。 + /// + /// 响应包。 + /// 数组起始偏移。 + /// 数组元素数量。 + /// 转换成 double 的数值数组。 + private static double[] ReadFloatArray(ReadOnlySpan packet, int offset, int count) + { + var values = new double[count]; + for (var index = 0; index < count; index++) + { + values[index] = BinaryPrimitives.ReadSingleBigEndian(packet.Slice(offset + (index * sizeof(float)), sizeof(float))); + } + + return values; + } +} diff --git a/src/Flyshot.Runtime.Fanuc/Protocol/FanucStateClient.cs b/src/Flyshot.Runtime.Fanuc/Protocol/FanucStateClient.cs new file mode 100644 index 0000000..5b522b5 --- /dev/null +++ b/src/Flyshot.Runtime.Fanuc/Protocol/FanucStateClient.cs @@ -0,0 +1,632 @@ +using System.Net.Sockets; +using Microsoft.Extensions.Logging; + +namespace Flyshot.Runtime.Fanuc.Protocol; + +/// +/// 表示 FANUC TCP 10010 状态通道客户端的连接阶段。 +/// +public enum FanucStateConnectionState +{ + /// + /// 状态通道未连接。 + /// + Disconnected, + + /// + /// 状态通道正在建立连接。 + /// + Connecting, + + /// + /// 状态通道已连接并由后台循环接收状态帧。 + /// + Connected, + + /// + /// 状态通道在限定时间内没有收到完整状态帧。 + /// + TimedOut, + + /// + /// 状态通道正在按退避策略重新连接。 + /// + Reconnecting, +} + +/// +/// 定义 FANUC TCP 10010 状态通道的超时和重连参数。 +/// +public sealed class FanucStateClientOptions +{ + /// + /// 获取或设置接收一帧完整 90B 状态帧允许的最长时间。 + /// + public TimeSpan FrameTimeout { get; init; } = TimeSpan.FromMilliseconds(250); + + /// + /// 获取或设置初始重连等待时间。 + /// + public TimeSpan ReconnectInitialDelay { get; init; } = TimeSpan.FromMilliseconds(100); + + /// + /// 获取或设置重连等待时间的上限。 + /// + public TimeSpan ReconnectMaxDelay { get; init; } = TimeSpan.FromSeconds(5); + + /// + /// 获取或设置单次 TCP 建连允许的最长时间。 + /// + public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(5); +} + +/// +/// 表示 FANUC TCP 10010 状态通道客户端的当前诊断状态。 +/// +public sealed class FanucStateClientStatus +{ + /// + /// 初始化状态通道诊断状态。 + /// + public FanucStateClientStatus( + FanucStateConnectionState state, + bool isFrameStale, + DateTimeOffset? lastFrameAt, + long reconnectAttemptCount, + string? lastErrorMessage) + { + State = state; + IsFrameStale = isFrameStale; + LastFrameAt = lastFrameAt; + ReconnectAttemptCount = reconnectAttemptCount; + LastErrorMessage = lastErrorMessage; + } + + /// + /// 获取状态通道当前连接阶段。 + /// + public FanucStateConnectionState State { get; } + + /// + /// 获取最近缓存状态帧是否已经超过状态帧超时窗口。 + /// + public bool IsFrameStale { get; } + + /// + /// 获取最近一次成功解析状态帧的 UTC 时间。 + /// + public DateTimeOffset? LastFrameAt { get; } + + /// + /// 获取后台循环发起重连的累计次数。 + /// + public long ReconnectAttemptCount { get; } + + /// + /// 获取最近一次状态通道异常的诊断文本。 + /// + public string? LastErrorMessage { get; } +} + +/// +/// FANUC TCP 10010 状态通道客户端,持续接收状态帧并缓存最新快照。 +/// +public sealed class FanucStateClient : IDisposable +{ + private readonly object _stateLock = new(); + private readonly FanucStateClientOptions _options; + private readonly ILogger? _logger; + private TcpClient? _tcpClient; + private NetworkStream? _stream; + private CancellationTokenSource? _receiveCts; + private Task? _receiveTask; + private FanucStateFrame? _latestFrame; + private FanucStateConnectionState _connectionState = FanucStateConnectionState.Disconnected; + private DateTimeOffset? _lastConnectedAt; + private DateTimeOffset? _lastFrameAt; + private long _reconnectAttemptCount; + private string? _lastErrorMessage; + private bool _disposed; + + /// + /// 使用默认状态通道参数初始化客户端。 + /// + public FanucStateClient() + : this(new FanucStateClientOptions(), null) + { + } + + /// + /// 使用指定状态通道参数初始化客户端。 + /// + /// 超时和重连参数。 + public FanucStateClient(FanucStateClientOptions options) + : this(options, null) + { + } + + /// + /// 使用指定状态通道参数和日志记录器初始化客户端。 + /// + /// 超时和重连参数。 + /// 日志记录器;允许 null。 + public FanucStateClient(FanucStateClientOptions options, ILogger? logger) + { + ArgumentNullException.ThrowIfNull(options); + ValidateOptions(options); + _options = options; + _logger = logger; + } + + /// + /// 获取当前是否已建立连接。 + /// + public bool IsConnected => GetStatus().State == FanucStateConnectionState.Connected; + + /// + /// 建立到 FANUC 控制柜 TCP 10010 状态通道的连接并启动后台接收循环。 + /// + /// 控制柜 IP 地址。 + /// 状态通道端口,默认 10010。 + /// 取消令牌。 + public async Task ConnectAsync(string ip, int port = 10010, CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (string.IsNullOrWhiteSpace(ip)) + { + throw new ArgumentException("IP 不能为空。", nameof(ip)); + } + + if (_receiveTask is not null) + { + throw new InvalidOperationException("状态通道已经连接,请先 Disconnect。"); + } + + _logger?.LogInformation("StateClient ConnectAsync: {Ip}:{Port}", ip, port); + + _receiveCts = new CancellationTokenSource(); + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _receiveCts.Token); + + lock (_stateLock) + { + _connectionState = FanucStateConnectionState.Connecting; + _latestFrame = null; + _lastConnectedAt = null; + _lastFrameAt = null; + _reconnectAttemptCount = 0; + _lastErrorMessage = null; + } + + try + { + await OpenConnectionAsync(ip, port, linkedCts.Token).ConfigureAwait(false); + } + catch (Exception exception) + { + _logger?.LogError(exception, "StateClient 连接失败: {Ip}:{Port}", ip, port); + CloseCurrentConnection(); + lock (_stateLock) + { + _connectionState = FanucStateConnectionState.Disconnected; + } + + _receiveCts.Dispose(); + _receiveCts = null; + throw; + } + + _receiveTask = Task.Run( + () => ReceiveAndReconnectLoopAsync(ip, port, _receiveCts.Token), + _receiveCts.Token); + + _logger?.LogInformation("StateClient 已连接并启动接收循环: {Ip}:{Port}", ip, port); + } + + /// + /// 断开状态通道并停止后台接收循环。 + /// + public void Disconnect() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + _logger?.LogInformation("StateClient Disconnect"); + Shutdown(clearLatestFrame: true); + } + + /// + /// 获取最近一次解析的状态帧;若尚未收到任何帧则返回 null。 + /// + /// 最新状态帧或 null。 + public FanucStateFrame? GetLatestFrame() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_stateLock) + { + return _latestFrame; + } + } + + /// + /// 获取状态通道当前诊断状态。 + /// + /// 状态通道诊断快照。 + public FanucStateClientStatus GetStatus() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_stateLock) + { + return new FanucStateClientStatus( + _connectionState, + IsFrameStaleLocked(DateTimeOffset.UtcNow), + _lastFrameAt, + _reconnectAttemptCount, + _lastErrorMessage); + } + } + + /// + /// 释放客户端资源。 + /// + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + Shutdown(clearLatestFrame: true); + } + + /// + /// 后台循环:持续接收状态帧;断线、超时或坏帧后进入退避重连。 + /// + private async Task ReceiveAndReconnectLoopAsync(string ip, int port, CancellationToken cancellationToken) + { + var reconnectDelay = _options.ReconnectInitialDelay; + _logger?.LogInformation("StateClient 接收循环启动: {Ip}:{Port}", ip, port); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + await ReceiveCurrentConnectionAsync(cancellationToken).ConfigureAwait(false); + reconnectDelay = _options.ReconnectInitialDelay; + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + _logger?.LogInformation("StateClient 接收循环正常取消"); + return; + } + catch (TimeoutException ex) + { + _logger?.LogWarning(ex, "StateClient 接收超时"); + MarkReceiveFailure(FanucStateConnectionState.TimedOut, ex.Message); + } + catch (Exception ex) when (ex is IOException or InvalidDataException or SocketException or ObjectDisposedException) + { + _logger?.LogWarning(ex, "StateClient 连接异常,准备重连"); + MarkReceiveFailure(FanucStateConnectionState.Reconnecting, ex.Message); + } + + CloseCurrentConnection(); + + if (cancellationToken.IsCancellationRequested) + { + return; + } + + reconnectDelay = await ReconnectWithBackoffAsync(ip, port, reconnectDelay, cancellationToken).ConfigureAwait(false); + } + } + + /// + /// 从当前连接中持续读取状态帧,直到连接异常或被取消。 + /// + private async Task ReceiveCurrentConnectionAsync(CancellationToken cancellationToken) + { + NetworkStream stream; + lock (_stateLock) + { + stream = _stream ?? throw new IOException("状态通道未连接。"); + } + + var buffer = new byte[FanucStateProtocol.StateFrameLength]; + long frameCount = 0; + FanucStateFrame? lastLoggedFrame = null; + + while (!cancellationToken.IsCancellationRequested) + { + await ReadExactAsync(stream, buffer, cancellationToken).ConfigureAwait(false); + + var frame = FanucStateProtocol.ParseFrame(buffer); + lock (_stateLock) + { + _latestFrame = frame; + _lastFrameAt = DateTimeOffset.UtcNow; + _connectionState = FanucStateConnectionState.Connected; + _lastErrorMessage = null; + } + + frameCount++; + + // 仅在状态变化或首次接收时记录 Info,避免高频日志。 + if (lastLoggedFrame is null + || lastLoggedFrame.CartesianPose[0] != frame.CartesianPose[0] + || !lastLoggedFrame.RawTailWords.SequenceEqual(frame.RawTailWords)) + { + _logger?.LogInformation( + "StateClient 收到状态帧: pose=[{X:F1}, {Y:F1}, {Z:F1}], tail=[{Tail}]", + frame.CartesianPose[0], + frame.CartesianPose[1], + frame.CartesianPose[2], + string.Join(", ", frame.RawTailWords)); + lastLoggedFrame = frame; + } + else if (frameCount % 1000 == 0) + { + _logger?.LogDebug("StateClient 已接收 {Count} 个状态帧", frameCount); + } + } + } + + /// + /// 从流中精确读取固定长度字节,超过帧超时窗口则抛出超时异常。 + /// + private async Task ReadExactAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken) + { + using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + timeoutCts.CancelAfter(_options.FrameTimeout); + + var totalRead = 0; + try + { + while (totalRead < buffer.Length) + { + var read = await stream.ReadAsync( + buffer.AsMemory(totalRead, buffer.Length - totalRead), + timeoutCts.Token).ConfigureAwait(false); + + if (read == 0) + { + throw new IOException("状态通道已断开,读取到 EOF。"); + } + + totalRead += read; + } + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + throw new TimeoutException("状态通道接收超时,未在限定时间内收到完整 90B 状态帧。"); + } + } + + /// + /// 打开 TCP 状态通道并更新连接状态。 + /// + private async Task OpenConnectionAsync(string ip, int port, CancellationToken cancellationToken) + { + var tcpClient = new TcpClient { NoDelay = true }; + try + { + _logger?.LogInformation("StateClient 正在连接 {Ip}:{Port}...", ip, port); + + using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + timeoutCts.CancelAfter(_options.ConnectTimeout); + await tcpClient.ConnectAsync(ip, port, timeoutCts.Token).ConfigureAwait(false); + + lock (_stateLock) + { + _tcpClient = tcpClient; + _stream = tcpClient.GetStream(); + _lastConnectedAt = DateTimeOffset.UtcNow; + _connectionState = FanucStateConnectionState.Connected; + } + + _logger?.LogInformation("StateClient 已连接到 {Ip}:{Port}", ip, port); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + _logger?.LogWarning("StateClient 连接 {Ip}:{Port} 超时", ip, port); + tcpClient.Dispose(); + throw new TimeoutException("状态通道建连超时。"); + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "StateClient 连接 {Ip}:{Port} 失败", ip, port); + tcpClient.Dispose(); + throw; + } + } + + /// + /// 按退避策略循环尝试重新连接,并返回下一次异常后的退避时间。 + /// + private async Task ReconnectWithBackoffAsync( + string ip, + int port, + TimeSpan reconnectDelay, + CancellationToken cancellationToken) + { + var nextDelay = reconnectDelay; + + while (!cancellationToken.IsCancellationRequested) + { + lock (_stateLock) + { + _connectionState = FanucStateConnectionState.Reconnecting; + } + + _logger?.LogInformation( + "StateClient 将在 {Delay}ms 后尝试重连 {Ip}:{Port}...", + nextDelay.TotalMilliseconds, + ip, + port); + + await Task.Delay(nextDelay, cancellationToken).ConfigureAwait(false); + + lock (_stateLock) + { + _reconnectAttemptCount++; + } + + try + { + await OpenConnectionAsync(ip, port, cancellationToken).ConfigureAwait(false); + _logger?.LogInformation( + "StateClient 重连成功: {Ip}:{Port}, 累计重连次数={Count}", + ip, + port, + _reconnectAttemptCount); + return _options.ReconnectInitialDelay; + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch (Exception ex) when (ex is SocketException or IOException or TimeoutException) + { + CloseCurrentConnection(); + lock (_stateLock) + { + _connectionState = FanucStateConnectionState.Reconnecting; + _lastErrorMessage = ex.Message; + } + + _logger?.LogWarning( + ex, + "StateClient 重连失败: {Ip}:{Port}, 下次等待={NextDelay}ms", + ip, + port, + nextDelay.TotalMilliseconds * 2); + + nextDelay = IncreaseReconnectDelay(nextDelay); + } + } + + return nextDelay; + } + + /// + /// 关闭当前 TCP 连接,不清除最新状态帧,供重连路径保留诊断数据。 + /// + private void CloseCurrentConnection() + { + NetworkStream? stream; + TcpClient? tcpClient; + lock (_stateLock) + { + stream = _stream; + tcpClient = _tcpClient; + _stream = null; + _tcpClient = null; + } + + stream?.Dispose(); + tcpClient?.Dispose(); + } + + /// + /// 记录接收异常并更新状态通道连接阶段。 + /// + private void MarkReceiveFailure(FanucStateConnectionState state, string message) + { + lock (_stateLock) + { + _connectionState = state; + _lastErrorMessage = message; + } + + _logger?.LogWarning("StateClient 接收失败: state={State}, message={Message}", state, message); + } + + /// + /// 关闭后台循环和 socket 资源。 + /// + private void Shutdown(bool clearLatestFrame) + { + _receiveCts?.Cancel(); + CloseCurrentConnection(); + + try + { + _receiveTask?.Wait(TimeSpan.FromSeconds(2)); + } + catch (AggregateException) + { + // 后台循环可能因取消而抛出 OperationCanceledException,忽略即可。 + } + + _receiveTask = null; + _receiveCts?.Dispose(); + _receiveCts = null; + + lock (_stateLock) + { + _connectionState = FanucStateConnectionState.Disconnected; + _lastConnectedAt = null; + _lastErrorMessage = null; + _reconnectAttemptCount = 0; + if (clearLatestFrame) + { + _latestFrame = null; + _lastFrameAt = null; + } + } + } + + /// + /// 判断缓存帧是否已经不能代表当前控制柜状态。 + /// + private bool IsFrameStaleLocked(DateTimeOffset now) + { + if (_latestFrame is null) + { + return _connectionState is FanucStateConnectionState.TimedOut or FanucStateConnectionState.Reconnecting + || _reconnectAttemptCount > 0 + || (_lastConnectedAt.HasValue && now - _lastConnectedAt.Value > _options.FrameTimeout); + } + + return _lastFrameAt.HasValue && now - _lastFrameAt.Value > _options.FrameTimeout; + } + + /// + /// 计算下一轮重连等待时间。 + /// + private TimeSpan IncreaseReconnectDelay(TimeSpan currentDelay) + { + var doubledMilliseconds = Math.Max(currentDelay.TotalMilliseconds * 2.0, _options.ReconnectInitialDelay.TotalMilliseconds); + var cappedMilliseconds = Math.Min(doubledMilliseconds, _options.ReconnectMaxDelay.TotalMilliseconds); + return TimeSpan.FromMilliseconds(cappedMilliseconds); + } + + /// + /// 校验状态通道参数,避免后台循环使用无效时间窗口。 + /// + private static void ValidateOptions(FanucStateClientOptions options) + { + ValidatePositive(options.FrameTimeout, nameof(options.FrameTimeout)); + ValidatePositive(options.ReconnectInitialDelay, nameof(options.ReconnectInitialDelay)); + ValidatePositive(options.ReconnectMaxDelay, nameof(options.ReconnectMaxDelay)); + ValidatePositive(options.ConnectTimeout, nameof(options.ConnectTimeout)); + + if (options.ReconnectMaxDelay < options.ReconnectInitialDelay) + { + throw new ArgumentOutOfRangeException(nameof(options), "最大重连等待时间不能小于初始重连等待时间。"); + } + } + + /// + /// 校验时间参数必须为正值。 + /// + private static void ValidatePositive(TimeSpan value, string parameterName) + { + if (value <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(parameterName, "时间参数必须大于 0。"); + } + } +} diff --git a/src/Flyshot.Runtime.Fanuc/Protocol/FanucStateProtocol.cs b/src/Flyshot.Runtime.Fanuc/Protocol/FanucStateProtocol.cs new file mode 100644 index 0000000..60883bf --- /dev/null +++ b/src/Flyshot.Runtime.Fanuc/Protocol/FanucStateProtocol.cs @@ -0,0 +1,187 @@ +using System.Buffers.Binary; + +namespace Flyshot.Runtime.Fanuc.Protocol; + +/// +/// 表示 FANUC TCP 10010 状态通道中的单个状态帧。 +/// +public sealed class FanucStateFrame +{ + private readonly double[] _pose; + private readonly double[] _jointOrExtensionValues; + private readonly double[] _jointRadians; + private readonly double[] _externalAxes; + private readonly uint[] _tailWords; + + /// + /// 初始化状态帧解析结果。 + /// + /// 状态帧消息号或序号。 + /// 控制器回传的笛卡尔位姿。 + /// 控制器回传的关节或扩展轴状态。 + /// 状态帧尾部状态槽位。 + public FanucStateFrame( + uint messageId, + IEnumerable pose, + IEnumerable jointOrExtensionValues, + IEnumerable tailWords) + { + MessageId = messageId; + _pose = pose?.ToArray() ?? throw new ArgumentNullException(nameof(pose)); + _jointOrExtensionValues = jointOrExtensionValues?.ToArray() ?? throw new ArgumentNullException(nameof(jointOrExtensionValues)); + _tailWords = tailWords?.ToArray() ?? throw new ArgumentNullException(nameof(tailWords)); + + if (_pose.Length != 6) + { + throw new ArgumentException("状态帧位姿必须包含 6 个 float。", nameof(pose)); + } + + if (_jointOrExtensionValues.Length != 9) + { + throw new ArgumentException("状态帧关节/扩展轴必须包含 9 个 float。", nameof(jointOrExtensionValues)); + } + + if (_tailWords.Length != 4) + { + throw new ArgumentException("状态帧尾部状态字必须包含 4 个 u32。", nameof(tailWords)); + } + + _jointRadians = _jointOrExtensionValues.Take(6).ToArray(); + _externalAxes = _jointOrExtensionValues.Skip(6).ToArray(); + } + + /// + /// 获取状态帧消息号或序号。 + /// + public uint MessageId { get; } + + /// + /// 获取控制器回传的笛卡尔位姿。 + /// + public IReadOnlyList Pose => _pose; + + /// + /// 获取控制器回传的笛卡尔位姿 X/Y/Z/W/P/R,单位来自 FANUC 状态服务器。 + /// + public IReadOnlyList CartesianPose => _pose; + + /// + /// 获取控制器回传的关节或扩展轴状态。 + /// + public IReadOnlyList JointOrExtensionValues => _jointOrExtensionValues; + + /// + /// 获取前 6 个机器人关节角度,当前现场抓包更支持按弧度制理解。 + /// + public IReadOnlyList JointRadians => _jointRadians; + + /// + /// 获取后 3 个扩展轴槽位。当前现场样本中这些值通常为 0。 + /// + public IReadOnlyList ExternalAxes => _externalAxes; + + /// + /// 获取状态帧尾部状态槽位。 + /// + public IReadOnlyList TailWords => _tailWords; + + /// + /// 获取原始尾部状态字。当前抓包中恒为 [2,0,0,1],语义暂不强行推断。 + /// + public IReadOnlyList RawTailWords => _tailWords; + + /// + /// 获取第 0 个原始尾部状态字。 + /// + public uint StatusWord0 => _tailWords[0]; + + /// + /// 获取第 1 个原始尾部状态字。 + /// + public uint StatusWord1 => _tailWords[1]; + + /// + /// 获取第 2 个原始尾部状态字。 + /// + public uint StatusWord2 => _tailWords[2]; + + /// + /// 获取第 3 个原始尾部状态字。 + /// + public uint StatusWord3 => _tailWords[3]; +} + +/// +/// 提供 FANUC TCP 10010 状态通道固定帧解析能力。 +/// +public static class FanucStateProtocol +{ + /// + /// FANUC 状态通道抓包确认的完整帧长度。 + /// + public const int StateFrameLength = 90; + + /// + /// 解析 TCP 10010 状态通道中的单个完整状态帧。 + /// + /// 完整状态帧。 + /// 状态帧解析结果。 + public static FanucStateFrame ParseFrame(ReadOnlySpan frame) + { + ValidateFrame(frame); + + var pose = new double[6]; + var jointOrExtensionValues = new double[9]; + var tailWords = new uint[4]; + + // 状态帧采用固定布局,偏移来自抓包与 StateServer 逆向结论。 + for (var index = 0; index < pose.Length; index++) + { + pose[index] = BinaryPrimitives.ReadSingleBigEndian(frame.Slice(11 + (index * sizeof(float)), sizeof(float))); + } + + for (var index = 0; index < jointOrExtensionValues.Length; index++) + { + jointOrExtensionValues[index] = BinaryPrimitives.ReadSingleBigEndian(frame.Slice(35 + (index * sizeof(float)), sizeof(float))); + } + + for (var index = 0; index < tailWords.Length; index++) + { + tailWords[index] = BinaryPrimitives.ReadUInt32BigEndian(frame.Slice(71 + (index * sizeof(uint)), sizeof(uint))); + } + + return new FanucStateFrame( + BinaryPrimitives.ReadUInt32BigEndian(frame.Slice(7, sizeof(uint))), + pose, + jointOrExtensionValues, + tailWords); + } + + /// + /// 校验状态帧的长度、magic 和长度字段。 + /// + /// 完整状态帧。 + private static void ValidateFrame(ReadOnlySpan frame) + { + if (frame.Length != StateFrameLength) + { + throw new InvalidDataException("FANUC 状态帧长度不符合 TCP 10010 固定帧布局。"); + } + + if (frame[0] != (byte)'d' || frame[1] != (byte)'o' || frame[2] != (byte)'z') + { + throw new InvalidDataException("FANUC 状态帧头 magic 不正确。"); + } + + if (frame[^3] != (byte)'z' || frame[^2] != (byte)'o' || frame[^1] != (byte)'d') + { + throw new InvalidDataException("FANUC 状态帧尾 magic 不正确。"); + } + + var declaredLength = BinaryPrimitives.ReadUInt32BigEndian(frame.Slice(3, sizeof(uint))); + if (declaredLength != frame.Length) + { + throw new InvalidDataException("FANUC 状态帧长度字段与实际长度不一致。"); + } + } +} diff --git a/src/Flyshot.Server.Host/.config/dotnet-tools.json b/src/Flyshot.Server.Host/.config/dotnet-tools.json new file mode 100644 index 0000000..4f7afb6 --- /dev/null +++ b/src/Flyshot.Server.Host/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "10.0.7", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/src/Flyshot.Server.Host/Controllers/DebugConsoleController.cs b/src/Flyshot.Server.Host/Controllers/DebugConsoleController.cs new file mode 100644 index 0000000..1edb439 --- /dev/null +++ b/src/Flyshot.Server.Host/Controllers/DebugConsoleController.cs @@ -0,0 +1,64 @@ +using Flyshot.Server.Host; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace Flyshot.Server.Host.Controllers; + +/// +/// 提供浏览器调试页所需的运行时配置 API。 +/// +/// +/// 本控制器自身不进入 Swagger 文档()。 +/// 调试页静态资源位于 wwwroot,Swagger 地址由配置 API 下发。 +/// +[ApiController] +[ApiExplorerSettings(IgnoreApi = true)] +[Tags("基础与状态")] +public sealed class DebugConsoleController : ControllerBase +{ + /// + /// Swagger 配置项,用于决定调试页是否对外暴露以及拼接 OpenAPI JSON 地址。 + /// + private readonly HostSwaggerOptions _swaggerOptions; + + /// + /// 初始化在线调试页控制器。 + /// + /// 来自 Swagger 配置节的标准选项。 + public DebugConsoleController(IOptions swaggerOptions) + { + ArgumentNullException.ThrowIfNull(swaggerOptions); + _swaggerOptions = swaggerOptions.Value ?? new HostSwaggerOptions(); + } + + /// + /// 返回静态调试页启动时所需的 Swagger 文档地址。 + /// + /// 当 Swagger 启用时返回配置;否则返回 404,与 Swagger UI 保持一致的可见性策略。 + [HttpGet("/api/debug/config")] + public IActionResult GetDebugConfig() + { + if (!_swaggerOptions.Enabled) + { + return NotFound(); + } + + return Ok(new + { + SwaggerJsonUrl = ResolveSwaggerJsonUrl(_swaggerOptions) + }); + } + + /// + /// 根据 与 解析出 Swagger JSON 实际地址。 + /// + /// Swagger 配置选项。 + /// 形如 /swagger/v1/swagger.json 的绝对路径。 + private static string ResolveSwaggerJsonUrl(HostSwaggerOptions options) + { + // Swashbuckle 的 RouteTemplate 不带前导斜杠,这里统一加上保证前端 fetch 走绝对路径。 + var template = options.JsonRouteTemplate ?? "swagger/{documentName}/swagger.json"; + var path = template.Replace("{documentName}", options.DocumentName ?? "v1", StringComparison.Ordinal); + return path.StartsWith('/') ? path : "/" + path; + } +} diff --git a/src/Flyshot.Server.Host/Controllers/HealthController.cs b/src/Flyshot.Server.Host/Controllers/HealthController.cs new file mode 100644 index 0000000..123d505 --- /dev/null +++ b/src/Flyshot.Server.Host/Controllers/HealthController.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Flyshot.Server.Host.Controllers; + +/// +/// 提供宿主基础探活与诊断接口。 +/// +[ApiController] +[Tags("基础与状态")] +public sealed class HealthController : ControllerBase +{ + /// + /// 返回宿主健康状态。 + /// + /// 固定的健康检查 JSON。 + [HttpGet("/healthz")] + public IActionResult GetHealth() + { + return Ok(new + { + status = "ok", + service = "flyshot-server-host" + }); + } +} diff --git a/src/Flyshot.Server.Host/Controllers/LegacyHttpApiController.cs b/src/Flyshot.Server.Host/Controllers/LegacyHttpApiController.cs new file mode 100644 index 0000000..38a60d2 --- /dev/null +++ b/src/Flyshot.Server.Host/Controllers/LegacyHttpApiController.cs @@ -0,0 +1,1204 @@ +using System.Text.Json; +using Flyshot.ControllerClientCompat; +using Microsoft.AspNetCore.Mvc; + +namespace Flyshot.Server.Host.Controllers; + +/// +/// 提供对 `flyshot-uaes-interface` 既有 FastAPI HTTP 路由层的一比一 MVC 兼容控制器。 +/// +[ApiController] +[Tags("ControllerClient 兼容")] +public sealed class LegacyHttpApiController : ControllerBase +{ + private readonly IControllerClientCompatService _compatService; + private readonly ILogger _logger; + + /// + /// 初始化旧 HTTP 兼容控制器。 + /// + /// ControllerClient 兼容服务。 + /// 日志记录器。 + public LegacyHttpApiController(IControllerClientCompatService compatService, ILogger logger) + { + _compatService = compatService ?? throw new ArgumentNullException(nameof(compatService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// 兼容旧根路径探活接口。 + /// + /// 旧 HTTP 服务约定的 Hello World 响应。 + [HttpGet("/")] + public IActionResult Root() + { + return Ok(new { message = "Hello World" }); + } + + /// + /// 兼容旧 `/connect_server/` 路由;在 replacement 宿主中仅记录调用方期望连接的地址。 + /// + /// 旧客户端传入的服务端 IP。 + /// 旧客户端传入的服务端端口。 + /// 与旧 FastAPI 层一致的状态响应。 + [HttpPost("/connect_server/")] + public IActionResult ConnectServer([FromQuery] string server_ip, [FromQuery] int port) + { + _logger.LogInformation("ConnectServer 调用: server_ip={ServerIp}, port={Port}", server_ip, port); + try + { + _compatService.ConnectServer(server_ip, port); + _logger.LogInformation("ConnectServer 成功: server_ip={ServerIp}, port={Port}", server_ip, port); + return Ok(new { status = "connected" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "ConnectServer 失败: server_ip={ServerIp}, port={Port}", server_ip, port); + return LegacyBadRequest("Connect Server failed"); + } + } + + /// + /// 兼容旧 `GetServerVersion` 版本查询语义。 + /// + /// 服务端版本号。 + [HttpGet("/get_server_version/")] + public IActionResult GetServerVersion() + { + return Ok(new { server_version = _compatService.GetServerVersion() }); + } + + /// + /// 兼容旧 `GetClientVersion` 版本查询语义。 + /// + /// 客户端版本号。 + [HttpGet("/get_client_version/")] + public IActionResult GetClientVersion() + { + return Ok(new { client_version = _compatService.GetClientVersion() }); + } + + /// + /// 兼容旧 `/setup_robot/` 路由。 + /// + /// 旧 HTTP 层使用的机器人名称。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/setup_robot/")] + public IActionResult SetupRobot([FromQuery] string robot_name) + { + _logger.LogInformation("SetupRobot 调用: robot_name={RobotName}", robot_name); + try + { + _compatService.SetUpRobot(robot_name); + _logger.LogInformation("SetupRobot 成功: robot_name={RobotName}", robot_name); + return Ok(new { status = "robot setup" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "SetupRobot 失败: robot_name={RobotName}", robot_name); + return LegacyBadRequest("SetUpRobot failed"); + } + } + + /// + /// 兼容旧 `SetUpRobotFromEnv(env_file)` 参数形状。 + /// + /// 环境文件路径。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/setup_robot_from_env/")] + public IActionResult SetupRobotFromEnv([FromQuery] string env_file) + { + try + { + _compatService.SetUpRobotFromEnv(env_file); + return Ok(new { status = "robot setup" }); + } + catch + { + return LegacyBadRequest("SetUpRobotFromEnv failed"); + } + } + + /// + /// 兼容旧 `SetShowTCP(is_show, axis_length, axis_size)` 参数形状。 + /// + /// 是否显示 TCP。 + /// 坐标轴长度。 + /// 坐标轴线宽。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/set_show_tcp/")] + public IActionResult SetShowTcp( + [FromQuery] bool is_show = true, + [FromQuery] double axis_length = 0.1, + [FromQuery] int axis_size = 2) + { + try + { + _compatService.SetShowTcp(is_show, axis_length, axis_size); + return Ok(new { status = "show TCP set" }); + } + catch + { + return LegacyBadRequest("SetShowTCP failed"); + } + } + + /// + /// 兼容旧 `/is_setup/` 路由。 + /// + /// 当前机器人是否完成初始化。 + [HttpGet("/is_setup/")] + public IActionResult IsSetup() + { + return Ok(new { is_setup = _compatService.IsSetUp }); + } + + /// + /// 兼容旧 `EnableRobot(buffer_size=4)` 参数形状。 + /// + /// 控制器执行缓冲区大小。 + /// 旧 FastAPI 层风格的布尔状态响应。 + [HttpGet("/enable_robot/")] + public IActionResult EnableRobot([FromQuery] int buffer_size = 4) + { + _logger.LogInformation("EnableRobot 调用: buffer_size={BufferSize}", buffer_size); + try + { + _compatService.EnableRobot(buffer_size); + _logger.LogInformation("EnableRobot 成功"); + return Ok(new { enable_robot = true }); + } + catch (Exception exception) + { + _logger.LogError(exception, "EnableRobot 失败"); + return LegacyBadRequest("EnableRobot failed"); + } + } + + /// + /// 兼容旧 `/disable_robot/` 路由。 + /// + /// 旧 FastAPI 层风格的布尔状态响应。 + [HttpGet("/disable_robot/")] + public IActionResult DisableRobot() + { + _logger.LogInformation("DisableRobot 调用"); + try + { + _compatService.DisableRobot(); + _logger.LogInformation("DisableRobot 成功"); + return Ok(new { disable_robot = true }); + } + catch (Exception exception) + { + _logger.LogError(exception, "DisableRobot 失败"); + return LegacyBadRequest("DisableRobot failed"); + } + } + + /// + /// 提供与旧客户端 StopMove 语义对应的 HTTP 端点。 + /// + /// 旧 FastAPI 层风格的状态响应。 + [HttpGet("/stop_move/")] + public IActionResult StopMove() + { + _logger.LogInformation("StopMove 调用"); + try + { + _compatService.StopMove(); + _logger.LogInformation("StopMove 成功"); + return Ok(new { status = "move stopped" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "StopMove 失败"); + return LegacyBadRequest("StopMove failed"); + } + } + + /// + /// 兼容旧 `/set_active_controller/` 路由。 + /// + /// 是否切到仿真控制器。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/set_active_controller/")] + public IActionResult SetActiveController([FromQuery] bool sim) + { + _logger.LogInformation("SetActiveController 调用: sim={Sim}", sim); + try + { + _compatService.SetActiveController(sim); + _logger.LogInformation("SetActiveController 成功: sim={Sim}", sim); + return Ok(new { status = "active controller set" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "SetActiveController 失败: sim={Sim}", sim); + return LegacyBadRequest("SetActiveController failed"); + } + } + + /// + /// 兼容旧 `/connect_robot/` 路由。 + /// + /// 控制器 IP。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/connect_robot/")] + public IActionResult ConnectRobot([FromQuery] string ip) + { + _logger.LogInformation("ConnectRobot 调用: ip={Ip}", ip); + try + { + _compatService.Connect(ip); + _logger.LogInformation("ConnectRobot 成功: ip={Ip}", ip); + return Ok(new { status = "robot connected" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "ConnectRobot 失败: ip={Ip}", ip); + return LegacyBadRequest("Connect failed"); + } + } + + /// + /// 提供与旧客户端 Disconnect 语义对应的 HTTP 端点。 + /// + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/disconnect_robot/")] + public IActionResult DisconnectRobot() + { + _logger.LogInformation("DisconnectRobot 调用"); + try + { + _compatService.Disconnect(); + _logger.LogInformation("DisconnectRobot 成功"); + return Ok(new { status = "robot disconnected" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "DisconnectRobot 失败"); + return LegacyBadRequest("Disconnect failed"); + } + } + + /// + /// 兼容旧 `/robot_info/` 路由。 + /// + /// 旧 HTTP 层聚合的机器人元信息。 + [HttpGet("/robot_info/")] + public IActionResult GetRobotInfo() + { + try + { + return Ok(new + { + name = _compatService.GetRobotName(), + server_version = _compatService.ServerVersion, + dof = _compatService.GetDegreesOfFreedom(), + speed_ratio = _compatService.GetSpeedRatio() + }); + } + catch + { + return LegacyBadRequest("GetRobotInfo failed"); + } + } + + /// + /// 兼容旧 `/set_tcp/` 路由。 + /// + /// 三维 TCP 请求体。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/set_tcp/")] + public IActionResult SetTcp([FromBody] LegacyTcpRequest tcp_data) + { + _logger.LogInformation("SetTcp 调用: x={X}, y={Y}, z={Z}", tcp_data.x, tcp_data.y, tcp_data.z); + try + { + _compatService.SetTcp(tcp_data.x, tcp_data.y, tcp_data.z); + _logger.LogInformation("SetTcp 成功"); + return Ok(new { status = "TCP set" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "SetTcp 失败"); + return LegacyBadRequest("SetTCP failed"); + } + } + + /// + /// 兼容旧 `/get_tcp/` 路由。 + /// + /// 当前 TCP 三维坐标。 + [HttpGet("/get_tcp/")] + public IActionResult GetTcp() + { + try + { + return Ok(new { tcp = _compatService.GetTcp() }); + } + catch + { + return LegacyBadRequest("GetTCP failed"); + } + } + + /// + /// 兼容旧 `/set_io/` 路由。 + /// + /// IO 端口号。 + /// IO 值。 + /// IO 类型字符串。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/set_io/")] + public IActionResult SetIo([FromQuery] int port, [FromQuery] bool value, [FromQuery] string io_type) + { + _logger.LogInformation("SetIo 调用: port={Port}, value={Value}, io_type={IoType}", port, value, io_type); + try + { + _compatService.SetIo(port, value, io_type); + _logger.LogInformation("SetIo 成功: port={Port}, value={Value}", port, value); + return Ok(new { status = "IO set" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "SetIo 失败: port={Port}, value={Value}", port, value); + return LegacyBadRequest("SetDigitalOutput failed"); + } + } + + /// + /// 兼容旧 `/get_io/` 路由。 + /// + /// IO 端口号。 + /// IO 类型字符串。 + /// 当前 IO 值。 + [HttpGet("/get_io/")] + public IActionResult GetIo([FromQuery] int port, [FromQuery] string io_type) + { + _logger.LogInformation("GetIo 调用: port={Port}, io_type={IoType}", port, io_type); + try + { + var value = _compatService.GetIo(port, io_type); + _logger.LogInformation("GetIo 成功: port={Port}, value={Value}", port, value); + return Ok(new { value }); + } + catch (Exception exception) + { + _logger.LogError(exception, "GetIo 失败: port={Port}", port); + return LegacyBadRequest("GetDigitalOutput failed"); + } + } + + /// + /// 兼容旧 `/get_joint_position/` 路由。 + /// + /// 旧 HTTP 层定义的关节位置 JSON 外形。 + [HttpGet("/get_joint_position/")] + public IActionResult GetJointPosition() + { + try + { + return Ok(new { success = true, points = _compatService.GetJointPositions() }); + } + catch + { + return LegacyBadRequest("GetJointPosition failed"); + } + } + + /// + /// 兼容旧 `/move_joint/` 路由。 + /// + /// 关节位置请求体。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/move_joint/")] + public IActionResult MoveJoint([FromBody] LegacyJointPositionRequest joint_data) + { + _logger.LogInformation("MoveJoint 调用: 关节数={JointCount}", joint_data.joints.Count); + _logger.LogDebug("MoveJoint 路点: {Joints}", string.Join(", ", joint_data.joints.Select(j => j.ToString("F4")))); + try + { + _compatService.MoveJoint(joint_data.joints); + _logger.LogInformation("MoveJoint 成功"); + return Ok(new { status = "robot moved" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "MoveJoint 失败"); + return LegacyBadRequest("MoveJoint failed"); + } + } + + /// + /// 以直角坐标 `[x,y,z,w,p,r]` 执行点到点移动。 + /// + /// 直角位姿请求体。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/move_pose/")] + public IActionResult MovePose([FromBody] JsonElement pose_data) + { + try + { + var poseRequest = LegacyCartesianPoseRequest.FromJson(pose_data); + var pose = poseRequest.ToPoseArray(); + _logger.LogInformation("MovePose 调用: x={X}, y={Y}, z={Z}, w={W}, p={P}, r={R}", + poseRequest.x, poseRequest.y, poseRequest.z, poseRequest.w, poseRequest.p, poseRequest.r); + + _compatService.MovePose(pose); + _logger.LogInformation("MovePose 成功"); + return Ok(new { status = "robot moved" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "MovePose 失败"); + return LegacyBadRequest("MovePose failed"); + } + } + + /// + /// 兼容旧 `GetNearestIK(pose, seed, ik)` 参数形状。 + /// + /// IK 请求体。 + /// IK 结果。 + [HttpPost("/get_nearest_ik/")] + public IActionResult GetNearestIk([FromBody] LegacyNearestIkRequest request) + { + try + { + return Ok(new { success = true, ik = _compatService.GetNearestIk(request.pose, request.seed) }); + } + catch (NotSupportedException exception) + { + return StatusCode(StatusCodes.Status501NotImplemented, new { detail = exception.Message }); + } + catch + { + return LegacyBadRequest("GetNearestIK failed"); + } + } + + /// + /// 兼容旧 `/list_flyShotTraj/` 路由。 + /// + /// 已上传飞拍轨迹名称列表。 + [HttpGet("/list_flyShotTraj/")] + public IActionResult ListFlyshotTrajectories() + { + var names = _compatService.ListTrajectoryNames(); + if (names.Count == 0) + { + return LegacyBadRequest("ListFlyShotTraj failed"); + } + + return Ok(new { flyshot_trajs = names }); + } + + /// + /// 兼容旧 `/execute_trajectory/` 路由,并接受两种历史请求体形状。 + /// + /// 轨迹请求体。 + /// 查询字符串中的 method 覆盖值(兼容历史调用方式)。 + /// 查询字符串中的 save_traj 覆盖值(兼容历史调用方式)。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/execute_trajectory/")] + public IActionResult ExecuteTrajectory( + [FromBody] JsonElement waypoints, + [FromQuery] string? method = null, + [FromQuery] bool? save_traj = null) + { + _logger.LogInformation("ExecuteTrajectory 调用: method={Method}, save_traj={SaveTraj}", method ?? "icsp", save_traj ?? false); + try + { + var request = ParseExecuteTrajectoryRequest(waypoints, method, save_traj); + _logger.LogDebug("ExecuteTrajectory 路点数={WaypointCount}, method={Method}", request.Waypoints.Count, request.Method); + _compatService.ExecuteTrajectory( + request.Waypoints, + new TrajectoryExecutionOptions(request.Method, request.SaveTrajectory)); + _logger.LogInformation("ExecuteTrajectory 成功: method={Method}", request.Method); + return Ok(new { status = "trajectory executed" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "ExecuteTrajectory 失败"); + return LegacyBadRequest("ExecuteTrajectory failed"); + } + } + + /// + /// 兼容旧 `/upload_flyshot/` 路由。 + /// + /// 飞拍上传请求体。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/upload_flyshot/")] + public IActionResult UploadFlyshot([FromBody] LegacyFlightTrajectoryRequest trajectory_data) + { + _logger.LogInformation( + "UploadFlyshot 调用: name={Name}, waypoints={WaypointCount}, shot_flags={ShotCount}", + trajectory_data.name, + trajectory_data.waypoints.Count, + trajectory_data.shot_flags.Count(static f => f)); + + if (trajectory_data.shot_flags.Count != trajectory_data.waypoints.Count) + { + _logger.LogWarning("UploadFlyshot 校验失败: shot_flags长度({ShotFlagsCount}) != 路点数({WaypointCount})", + trajectory_data.shot_flags.Count, trajectory_data.waypoints.Count); + return LegacyValidationError("shot_flags长度必须与路点数量相同"); + } + + if (trajectory_data.offset_values.Count != trajectory_data.waypoints.Count) + { + _logger.LogWarning("UploadFlyshot 校验失败: offset_values长度({OffsetCount}) != 路点数({WaypointCount})", + trajectory_data.offset_values.Count, trajectory_data.waypoints.Count); + return LegacyValidationError("offset_values长度必须与路点数量相同"); + } + + if (trajectory_data.addrs.Count != trajectory_data.waypoints.Count) + { + _logger.LogWarning("UploadFlyshot 校验失败: addrs长度({AddrCount}) != 路点数({WaypointCount})", + trajectory_data.addrs.Count, trajectory_data.waypoints.Count); + return LegacyValidationError("addrs长度必须与路点数量相同"); + } + + try + { + var trajectory = new ControllerClientCompatUploadedTrajectory( + name: trajectory_data.name, + waypoints: trajectory_data.waypoints, + shotFlags: trajectory_data.shot_flags, + offsetValues: trajectory_data.offset_values.Select(static value => (int)value), + addressGroups: trajectory_data.addrs); + + _compatService.UploadTrajectory(trajectory); + _logger.LogInformation("UploadFlyshot 成功: name={Name}", trajectory_data.name); + return Ok(new { status = "FlyShot uploaded" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "UploadFlyshot 失败: name={Name}", trajectory_data.name); + return LegacyBadRequest("UploadFlyShotTraj failed"); + } + } + + /// + /// 兼容旧 `/execute_flyshot/` 路由。 + /// + /// 包含轨迹名称和执行参数的请求体。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/execute_flyshot/")] + public IActionResult ExecuteFlyshot([FromBody] LegacyExecuteFlyshotRequest data) + { + _logger.LogInformation( + "ExecuteFlyshot 调用: name={Name}, method={Method}, move_to_start={MoveToStart}, use_cache={UseCache}, wait={Wait}", + data.name, data.method, data.move_to_start, data.use_cache, data.wait); + try + { + _compatService.ExecuteTrajectoryByName( + data.name, + new FlyshotExecutionOptions( + moveToStart: data.move_to_start, + method: data.method, + saveTrajectory: data.save_traj, + useCache: data.use_cache, + wait: data.wait)); + _logger.LogInformation("ExecuteFlyshot 成功: name={Name}", data.name); + return Ok(new { status = "FlyShot executed", success = true }); + } + catch (Exception exception) + { + _logger.LogError(exception, "ExecuteFlyshot 失败: name={Name}", data.name); + return StatusCode(StatusCodes.Status500InternalServerError, new { detail = exception.Message }); + } + } + + /// + /// 兼容旧 `SaveTrajInfo(name, method)` 参数形状。 + /// + /// 轨迹保存请求体。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/save_traj_info/")] + public IActionResult SaveTrajectoryInfo([FromBody] LegacyTrajectoryInfoRequest request) + { + _logger.LogInformation("SaveTrajectoryInfo 调用: name={Name}, method={Method}", request.name, request.method); + try + { + _compatService.SaveTrajectoryInfo(request.name, request.method); + _logger.LogInformation("SaveTrajectoryInfo 成功: name={Name}", request.name); + return Ok(new { status = "trajectory info saved", success = true }); + } + catch (NotSupportedException exception) + { + _logger.LogWarning(exception, "SaveTrajectoryInfo 不支持: name={Name}", request.name); + return StatusCode(StatusCodes.Status501NotImplemented, new { detail = exception.Message }); + } + catch (Exception exception) + { + _logger.LogError(exception, "SaveTrajectoryInfo 失败: name={Name}", request.name); + return LegacyBadRequest("SaveTrajInfo failed"); + } + } + + /// + /// 兼容旧 `IsFlyShotTrajValid(time, name, method, save_traj)` 参数形状。 + /// + /// 轨迹有效性检查请求体。 + /// 有效性和轨迹时长。 + [HttpPost("/is_flyShotTrajValid/")] + public IActionResult IsFlyshotTrajectoryValid([FromBody] LegacyFlyshotValidationRequest request) + { + _logger.LogInformation("IsFlyshotTrajectoryValid 调用: name={Name}, method={Method}", request.name, request.method); + try + { + var isValid = _compatService.IsFlyshotTrajectoryValid( + out var duration, + request.name, + request.method, + request.save_traj); + + _logger.LogInformation("IsFlyshotTrajectoryValid 结果: name={Name}, valid={Valid}, duration={Duration}s", request.name, isValid, duration.TotalSeconds); + return Ok(new { success = isValid, valid = isValid, time = duration.TotalSeconds }); + } + catch (NotSupportedException exception) + { + _logger.LogWarning(exception, "IsFlyshotTrajectoryValid 不支持: name={Name}", request.name); + return StatusCode(StatusCodes.Status501NotImplemented, new { detail = exception.Message }); + } + catch (Exception exception) + { + _logger.LogError(exception, "IsFlyshotTrajectoryValid 失败: name={Name}", request.name); + return LegacyBadRequest("IsFlyShotTrajValid failed"); + } + } + + /// + /// 兼容旧 `/set_speedRatio/` 路由。 + /// + /// 速度倍率请求体。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/set_speedRatio/")] + public IActionResult SetSpeedRatio([FromBody] LegacySpeedRatioRequest data) + { + _logger.LogInformation("SetSpeedRatio 调用: speed={Speed}", data.speed); + try + { + // 验证数值 范围符合预期(例如 0.01到 1.0),以避免对控制器造成潜在风险 + if (data.speed < 0.01 || data.speed > 1.0) + { + _logger.LogWarning("SetSpeedRatio 参数无效: speed={Speed}", data.speed); + return BadRequest(new { detail = "Speed ratio must be between 0.01 and 1.0." }); + } + + _compatService.SetSpeedRatio(data.speed); + _logger.LogInformation("SetSpeedRatio 成功: speed={Speed}", data.speed); + return Ok(new { message = "set_speedRatio executed", returnCode = 0 }); + } + catch (Exception exception) + { + _logger.LogError(exception, "SetSpeedRatio 失败: speed={Speed}", data.speed); + return LegacyBadRequest("set_speedRatio failed"); + } + } + + /// + /// 兼容旧 `/delete_flyshot/` 路由。 + /// + /// 包含轨迹名称的请求体。 + /// 旧 FastAPI 层风格的状态响应。 + [HttpPost("/delete_flyshot/")] + public IActionResult DeleteFlyshot([FromBody] LegacyNameRequest request) + { + _logger.LogInformation("DeleteFlyshot 调用: name={Name}", request.name); + try + { + _compatService.DeleteTrajectory(request.name); + _logger.LogInformation("DeleteFlyshot 成功: name={Name}", request.name); + return Ok(new { status = "FlyShot deleted" }); + } + catch (Exception exception) + { + _logger.LogError(exception, "DeleteFlyshot 失败: name={Name}", request.name); + return LegacyBadRequest("DeleteFlyShotTraj failed"); + } + } + + /// + /// 兼容旧 `/init_mpc_robt` 路由,保留历史拼写。 + /// + /// 初始化请求体。 + /// 旧 FastAPI 层风格的初始化结果。 + [HttpPost("/init_mpc_robt")] + public IActionResult InitMpcRobot([FromBody] LegacyInitMpcRobotRequest data) + { + _logger.LogInformation( + "InitMpcRobot 调用: robot_name={RobotName}, robot_ip={RobotIp}, sim={Sim}, server={ServerIp}:{Port}", + data.robot_name, data.robot_ip, data.sim, data.server_ip, data.port); + try + { + + _compatService.SetUpRobot(data.robot_name); + if (!_compatService.IsSetUp) + { + _logger.LogWarning("InitMpcRobot 失败: Robot not setup"); + return LegacyBadRequest("Robot not setup"); + } + + _compatService.SetActiveController(data.sim); + _compatService.Connect(data.robot_ip); + _compatService.EnableRobot(8); + _logger.LogInformation("InitMpcRobot 成功: robot_name={RobotName}", data.robot_name); + return Ok(new { message = "init_Success", returnCode = 0 }); + } + catch (Exception exception) + { + _logger.LogError(exception, "InitMpcRobot 失败"); + return LegacyBadRequest("Connect Server failed"); + } + } + + /// + /// 兼容旧 `/get_pose` 路由。 + /// + /// 当前末端位姿数组。 + [HttpGet("/get_pose")] + public IActionResult GetPose() + { + try + { + return Ok(new { pose = _compatService.GetPose() }); + } + catch + { + return LegacyBadRequest("GetPose failed"); + } + } + + /// + /// 解析旧 `/execute_trajectory/` 的完整参数形状。 + /// + /// 原始 JSON 请求体。 + /// 查询字符串中的 method 覆盖值。 + /// 查询字符串中的 save_traj 覆盖值。 + /// 统一后的路点和执行参数。 + private static ( + IReadOnlyList> Waypoints, + string Method, + bool SaveTrajectory) ParseExecuteTrajectoryRequest( + JsonElement payload, + string? queryMethod, + bool? querySaveTrajectory) + { + string method = queryMethod ?? "icsp"; + bool saveTrajectory = querySaveTrajectory ?? false; + + if (payload.ValueKind == JsonValueKind.Object) + { + if (payload.TryGetProperty("method", out var methodElement) && methodElement.ValueKind == JsonValueKind.String) + { + method = methodElement.GetString() ?? method; + } + + if (payload.TryGetProperty("save_traj", out var saveTrajectoryElement)) + { + saveTrajectory = saveTrajectoryElement.GetBoolean(); + } + + if (!payload.TryGetProperty("waypoints", out var waypointElement)) + { + throw new InvalidOperationException("ExecuteTrajectory request body must include waypoints."); + } + + return (ParseLegacyTrajectoryWaypoints(waypointElement), method, saveTrajectory); + } + + return (ParseLegacyTrajectoryWaypoints(payload), method, saveTrajectory); + } + + /// + /// 解析旧 `/execute_trajectory/` 可能出现的两种历史请求体形状。 + /// + /// 原始 JSON 请求体。 + /// 统一后的关节路点集合。 + private static IReadOnlyList> ParseLegacyTrajectoryWaypoints(JsonElement waypoints) + { + if (waypoints.ValueKind != JsonValueKind.Array) + { + throw new InvalidOperationException("ExecuteTrajectory request body must be an array."); + } + + var parsedWaypoints = new List>(); + foreach (var waypointElement in waypoints.EnumerateArray()) + { + if (waypointElement.ValueKind == JsonValueKind.Array) + { + parsedWaypoints.Add(waypointElement.EnumerateArray().Select(static value => value.GetDouble()).ToArray()); + continue; + } + + if (waypointElement.ValueKind == JsonValueKind.Object && waypointElement.TryGetProperty("joints", out var jointElement)) + { + parsedWaypoints.Add(jointElement.EnumerateArray().Select(static value => value.GetDouble()).ToArray()); + continue; + } + + throw new InvalidOperationException("Unsupported waypoint payload shape."); + } + + return parsedWaypoints; + } + + /// + /// 构造与旧 FastAPI `HTTPException(status_code=400, detail=...)` 等价的响应。 + /// + /// 错误详情文本。 + /// 400 JSON 响应。 + private BadRequestObjectResult LegacyBadRequest(string detail) + { + return BadRequest(new { detail }); + } + + /// + /// 构造与旧 FastAPI `422` 输入校验失败等价的响应。 + /// + /// 错误详情文本。 + /// 422 JSON 响应。 + private ObjectResult LegacyValidationError(string detail) + { + return StatusCode(StatusCodes.Status422UnprocessableEntity, new { detail }); + } +} + +/// +/// 表示旧 `/set_tcp/` 路由使用的三维 TCP 请求体。 +/// +public sealed class LegacyTcpRequest +{ + /// + /// 获取或设置 TCP X。 + /// + public double x { get; init; } + + /// + /// 获取或设置 TCP Y。 + /// + public double y { get; init; } + + /// + /// 获取或设置 TCP Z。 + /// + public double z { get; init; } +} + +/// +/// 表示旧 `/move_joint/` 路由使用的关节请求体。 +/// +public sealed class LegacyJointPositionRequest +{ + /// + /// 获取或设置目标关节数组。 + /// + public List joints { get; init; } = []; +} + +/// +/// 表示 `/move_pose/` 路由使用的直角位姿请求体。 +/// +public sealed class LegacyCartesianPoseRequest +{ + /// + /// MovePose 第一版入口硬限制:TCP X/Y 最大绝对值,单位 mm。 + /// + private const double MaxHorizontalMillimeters = 1000.0; + + /// + /// MovePose 第一版入口硬限制:TCP Z 最小值,单位 mm。 + /// + private const double MinZMillimeters = 0.0; + + /// + /// MovePose 第一版入口硬限制:TCP Z 最大值,单位 mm。 + /// + private const double MaxZMillimeters = 1200.0; + + /// + /// MovePose 第一版入口硬限制:W/R 姿态角最大绝对值,单位 deg。 + /// + private const double MaxRollYawDegrees = 180.0; + + /// + /// MovePose 第一版入口硬限制:P 姿态角最大绝对值,单位 deg。 + /// + private const double MaxPitchDegrees = 90.0; + + /// + /// 获取或设置 TCP X,单位为 mm。 + /// + public double x { get; init; } + + /// + /// 获取或设置 TCP Y,单位为 mm。 + /// + public double y { get; init; } + + /// + /// 获取或设置 TCP Z,单位为 mm。 + /// + public double z { get; init; } + + /// + /// 获取或设置姿态 W,单位为 deg。 + /// + public double w { get; init; } + + /// + /// 获取或设置姿态 P,单位为 deg。 + /// + public double p { get; init; } + + /// + /// 获取或设置姿态 R,单位为 deg。 + /// + public double r { get; init; } + + /// + /// 从原始 JSON 请求体解析直角位姿,并显式拒绝缺字段、null 和非有限数。 + /// + /// 原始 JSON 请求体。 + /// 解析后的直角位姿请求。 + public static LegacyCartesianPoseRequest FromJson(JsonElement json) + { + if (json.ValueKind != JsonValueKind.Object) + { + throw new ArgumentException("MovePose request body must be an object."); + } + + // 旧接口要求请求体必须完整提供 x/y/z/w/p/r,不能让模型绑定把缺字段静默补 0。 + return new LegacyCartesianPoseRequest + { + x = ReadRequiredFiniteDouble(json, "x", -MaxHorizontalMillimeters, MaxHorizontalMillimeters), + y = ReadRequiredFiniteDouble(json, "y", -MaxHorizontalMillimeters, MaxHorizontalMillimeters), + z = ReadRequiredFiniteDouble(json, "z", MinZMillimeters, MaxZMillimeters), + w = ReadRequiredFiniteDouble(json, "w", -MaxRollYawDegrees, MaxRollYawDegrees), + p = ReadRequiredFiniteDouble(json, "p", -MaxPitchDegrees, MaxPitchDegrees), + r = ReadRequiredFiniteDouble(json, "r", -MaxRollYawDegrees, MaxRollYawDegrees) + }; + } + + /// + /// 转换为兼容层使用的六维位姿数组。 + /// + /// [x,y,z,w,p,r] 数组。 + public IReadOnlyList ToPoseArray() + { + return [x, y, z, w, p, r]; + } + + /// + /// 读取必填有限数值字段。 + /// + /// 请求体 JSON 对象。 + /// 字段名。 + /// 允许的最小值。 + /// 允许的最大值。 + /// 字段对应的有限 double 数值。 + private static double ReadRequiredFiniteDouble( + JsonElement json, + string propertyName, + double minInclusive, + double maxInclusive) + { + if (!json.TryGetProperty(propertyName, out var property) || property.ValueKind != JsonValueKind.Number) + { + throw new ArgumentException($"MovePose request field '{propertyName}' is required and must be a number."); + } + + var value = property.GetDouble(); + if (double.IsNaN(value) || double.IsInfinity(value)) + { + throw new ArgumentOutOfRangeException(propertyName, "MovePose request values must be finite."); + } + + if (value < minInclusive || value > maxInclusive) + { + throw new ArgumentOutOfRangeException( + propertyName, + value, + $"MovePose request field '{propertyName}' must be between {minInclusive} and {maxInclusive}."); + } + + return value; + } +} + +/// +/// 表示旧 `/upload_flyshot/` 路由使用的飞拍上传请求体。 +/// +public sealed class LegacyFlightTrajectoryRequest +{ + /// + /// 获取或设置地址组集合。 + /// + public List> addrs { get; init; } = []; + + /// + /// 获取或设置飞拍轨迹名称。 + /// + public string name { get; init; } = string.Empty; + + /// + /// 获取或设置偏移周期集合。 + /// + public List offset_values { get; init; } = []; + + /// + /// 获取或设置拍照标志集合。 + /// + public List shot_flags { get; init; } = []; + + /// + /// 获取或设置关节路点集合。 + /// + public List> waypoints { get; init; } = []; +} + +/// +/// 表示旧 `/execute_flyshot/` 与 `/delete_flyshot/` 路由使用的名称请求体。 +/// +public sealed class LegacyNameRequest +{ + /// + /// 获取或设置轨迹名称。 + /// + public string name { get; init; } = string.Empty; +} + +/// +/// 表示旧 `/execute_flyshot/` 路由使用的完整执行请求体。 +/// +public sealed class LegacyExecuteFlyshotRequest +{ + /// + /// 获取或设置轨迹名称。 + /// + public string name { get; init; } = string.Empty; + + /// + /// 获取或设置是否先移动到轨迹起点。 + /// + public bool move_to_start { get; init; } = true; + + /// + /// 获取或设置轨迹生成方法。 + /// + public string method { get; init; } = "icsp"; + + /// + /// 获取或设置是否保存轨迹信息。 + /// + public bool save_traj { get; init; } = true; + + /// + /// 获取或设置是否复用轨迹缓存。 + /// + public bool use_cache { get; init; } = true; + + /// + /// 获取或设置是否等待机器人执行完整条飞拍轨迹后再返回。 + /// + public bool wait { get; init; } = true; +} + +/// +/// 表示旧 `SaveTrajInfo` 参数形状。 +/// +public sealed class LegacyTrajectoryInfoRequest +{ + /// + /// 获取或设置轨迹名称。 + /// + public string name { get; init; } = string.Empty; + + /// + /// 获取或设置轨迹生成方法。 + /// + public string method { get; init; } = "icsp"; +} + +/// +/// 表示旧 `IsFlyShotTrajValid` 参数形状。 +/// +public sealed class LegacyFlyshotValidationRequest +{ + /// + /// 获取或设置轨迹名称。 + /// + public string name { get; init; } = string.Empty; + + /// + /// 获取或设置轨迹生成方法。 + /// + public string method { get; init; } = "icsp"; + + /// + /// 获取或设置是否保存轨迹信息。 + /// + public bool save_traj { get; init; } = true; +} + +/// +/// 表示旧 `GetNearestIK` 参数形状。 +/// +public sealed class LegacyNearestIkRequest +{ + /// + /// 获取或设置目标位姿 `[x,y,z,qx,qy,qz,qw]`。 + /// + public List pose { get; init; } = []; + + /// + /// 获取或设置 IK seed 关节数组。 + /// + public List seed { get; init; } = []; +} + +/// +/// 表示旧 `/set_speedRatio/` 路由使用的速度倍率请求体。 +/// +public sealed class LegacySpeedRatioRequest +{ + /// + /// 获取或设置目标速度倍率。 + /// + public double speed { get; init; } +} + +/// +/// 表示旧 `/init_mpc_robt` 路由使用的初始化请求体。 +/// +public sealed class LegacyInitMpcRobotRequest +{ + /// + /// 获取或设置目标服务端 IP。 + /// + public string server_ip { get; init; } = string.Empty; + + /// + /// 获取或设置目标服务端端口。 + /// + public int port { get; init; } + + /// + /// 获取或设置机器人名称。 + /// + public string robot_name { get; init; } = string.Empty; + + /// + /// 获取或设置机器人控制器 IP。 + /// + public string robot_ip { get; init; } = string.Empty; + + /// + /// 获取或设置是否使用仿真控制器;默认 false 连接真机。 + /// + public bool sim { get; init; } +} diff --git a/src/Flyshot.Server.Host/Controllers/StatusController.cs b/src/Flyshot.Server.Host/Controllers/StatusController.cs new file mode 100644 index 0000000..7e466b3 --- /dev/null +++ b/src/Flyshot.Server.Host/Controllers/StatusController.cs @@ -0,0 +1,67 @@ +using Flyshot.ControllerClientCompat; +using Microsoft.AspNetCore.Mvc; + +namespace Flyshot.Server.Host.Controllers; + +/// +/// 提供控制器状态快照 API,状态监控页面由 wwwroot 静态资源承载。 +/// +[ApiController] +[Tags("基础与状态")] +public sealed class StatusController : ControllerBase +{ + private readonly IControllerClientCompatService _compatService; + + /// + /// 初始化状态监控控制器。 + /// + /// ControllerClient 兼容层服务。 + public StatusController(IControllerClientCompatService compatService) + { + _compatService = compatService ?? throw new ArgumentNullException(nameof(compatService)); + } + + /// + /// 提供短路由 `/status`,跳转到静态状态页。 + /// + /// 重定向到 /status.html。 + [HttpGet("/status")] + public IActionResult StatusPage() + { + return Redirect("/status.html"); + } + + /// + /// 提供短路由 `/debug`,跳转到静态调试页。 + /// + /// 重定向到 /debug.html。 + [HttpGet("/debug")] + public IActionResult DebugPage() + { + return Redirect("/debug.html"); + } + + /// + /// 返回当前 ControllerClient 兼容层与控制器运行时状态快照。 + /// + /// 面向状态页和外部诊断的 JSON 快照。 + [HttpGet("/api/status/snapshot")] + public IActionResult GetSnapshot() + { + var snapshot = _compatService.GetControllerSnapshot(); + var metadata = _compatService.GetStatusSnapshotMetadata(); + + return Ok(new + { + Status = "ok", + Service = "flyshot-server-host", + ServerVersion = metadata.ServerVersion, + ClientVersion = metadata.ClientVersion, + IsSetup = metadata.IsSetup, + RobotName = metadata.RobotName, + DegreesOfFreedom = metadata.DegreesOfFreedom, + UploadedTrajectories = metadata.UploadedTrajectories, + Snapshot = snapshot + }); + } +} diff --git a/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj b/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj index 21720d3..3dbd0b7 100644 --- a/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj +++ b/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj @@ -1,6 +1,31 @@ + + + true + $(NoWarn);1591 + + + + + + + + + + + + + + + + + + + + diff --git a/src/Flyshot.Server.Host/HostCorsOptions.cs b/src/Flyshot.Server.Host/HostCorsOptions.cs new file mode 100644 index 0000000..dbdfcf3 --- /dev/null +++ b/src/Flyshot.Server.Host/HostCorsOptions.cs @@ -0,0 +1,27 @@ +namespace Flyshot.Server.Host; + +/// +/// 表示宿主 CORS 策略的标准配置项。 +/// +public sealed class HostCorsOptions +{ + /// + /// 获取或设置 CORS 策略名称。 + /// + public string PolicyName { get; set; } = "LegacyHttpApi"; + + /// + /// 获取或设置允许的源列表。 + /// + public string[] AllowedOrigins { get; set; } = ["*"]; + + /// + /// 获取或设置允许的 HTTP 方法列表。 + /// + public string[] AllowedMethods { get; set; } = ["GET", "POST", "OPTIONS"]; + + /// + /// 获取或设置允许的请求头列表。 + /// + public string[] AllowedHeaders { get; set; } = ["*"]; +} diff --git a/src/Flyshot.Server.Host/HostSwaggerOptions.cs b/src/Flyshot.Server.Host/HostSwaggerOptions.cs new file mode 100644 index 0000000..3145773 --- /dev/null +++ b/src/Flyshot.Server.Host/HostSwaggerOptions.cs @@ -0,0 +1,37 @@ +namespace Flyshot.Server.Host; + +/// +/// 表示宿主 Swagger/OpenAPI 文档的标准配置项。 +/// +public sealed class HostSwaggerOptions +{ + /// + /// 获取或设置是否启用 Swagger。 + /// + public bool Enabled { get; set; } = true; + + /// + /// 获取或设置文档名称。 + /// + public string DocumentName { get; set; } = "v1"; + + /// + /// 获取或设置 Swagger 文档标题。 + /// + public string Title { get; set; } = "Flyshot Replacement HTTP API"; + + /// + /// 获取或设置 Swagger 文档版本文本。 + /// + public string Version { get; set; } = "v1"; + + /// + /// 获取或设置 Swagger JSON 路由模板。 + /// + public string JsonRouteTemplate { get; set; } = "swagger/{documentName}/swagger.json"; + + /// + /// 获取或设置 Swagger UI 路由前缀。 + /// + public string RoutePrefix { get; set; } = "swagger"; +} diff --git a/src/Flyshot.Server.Host/Middleware/RequestResponseLoggingMiddleware.cs b/src/Flyshot.Server.Host/Middleware/RequestResponseLoggingMiddleware.cs new file mode 100644 index 0000000..e8a6794 --- /dev/null +++ b/src/Flyshot.Server.Host/Middleware/RequestResponseLoggingMiddleware.cs @@ -0,0 +1,172 @@ +using System.Diagnostics; +using System.Text; + +namespace Flyshot.Server.Host.Middleware; + +/// +/// HTTP 请求与响应日志中间件。 +/// 记录每个 HTTP 请求的进入时间、方法、路径、查询串、请求体, +/// 以及响应的状态码、耗时和响应体(调试级别)。 +/// +public sealed class RequestResponseLoggingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// 请求体最大日志长度,超出则截断并附加省略标记。 + /// + private const int MaxBodyLogLength = 4096; + + /// + /// 请求/响应日志忽略路径前缀列表,用于跳过高频轮询接口的常规日志。 + /// + private static readonly string[] IgnoredLogPathPrefixes = + [ + "/api/status/snapshot" + ]; + + /// + /// 初始化请求响应日志中间件。 + /// + /// 下一个中间件委托。 + /// 日志记录器。 + public RequestResponseLoggingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// 处理 HTTP 请求并记录输入输出。 + /// + /// HTTP 上下文。 + public async Task InvokeAsync(HttpContext context) + { + // 高频状态轮询接口只转发请求,不记录请求/响应日志,避免控制台和文件日志被刷屏。 + if (ShouldSkipRequestResponseLog(context.Request.Path)) + { + await _next(context).ConfigureAwait(false); + return; + } + + var stopwatch = Stopwatch.StartNew(); + var request = context.Request; + var requestId = Activity.Current?.Id ?? context.TraceIdentifier; + + // 记录请求进入信息(Info 级别:方法、路径、查询参数)。 + _logger.LogInformation( + "[HTTP-REQ] [{RequestId}] {Method} {Path}{QueryString} — 客户端 {RemoteIp}", + requestId, + request.Method, + request.Path, + request.QueryString.HasValue ? request.QueryString.Value : string.Empty, + context.Connection.RemoteIpAddress); + + // 读取并记录请求体(Debug 级别)。 + string? requestBody = null; + if (request.ContentLength > 0 && request.Body.CanRead) + { + request.EnableBuffering(); + requestBody = await ReadBodyAsync(request.Body, context.RequestAborted).ConfigureAwait(false); + request.Body.Position = 0; + + if (!string.IsNullOrEmpty(requestBody)) + { + _logger.LogDebug( + "[HTTP-REQ-BODY] [{RequestId}] {Body}", + requestId, + TruncateBody(requestBody)); + } + } + + // 拦截响应流以便读取响应体。 + var originalResponseBody = context.Response.Body; + using var responseBodyStream = new MemoryStream(); + context.Response.Body = responseBodyStream; + + try + { + await _next(context).ConfigureAwait(false); + } + catch (Exception exception) + { + _logger.LogError( + exception, + "[HTTP-ERR] [{RequestId}] {Method} {Path} 处理过程中发生未捕获异常", + requestId, + request.Method, + request.Path); + throw; + } + finally + { + stopwatch.Stop(); + responseBodyStream.Position = 0; + var responseBody = await ReadBodyAsync(responseBodyStream, context.RequestAborted).ConfigureAwait(false); + responseBodyStream.Position = 0; + await responseBodyStream.CopyToAsync(originalResponseBody, context.RequestAborted).ConfigureAwait(false); + context.Response.Body = originalResponseBody; + + var statusCode = context.Response.StatusCode; + var level = statusCode >= 500 ? LogLevel.Error : statusCode >= 400 ? LogLevel.Warning : LogLevel.Information; + + // 记录响应概要(Info/Warning/Error 级别)。 + _logger.Log( + level, + "[HTTP-RES] [{RequestId}] {Method} {Path} => {StatusCode} ({ElapsedMs}ms)", + requestId, + request.Method, + request.Path, + statusCode, + stopwatch.ElapsedMilliseconds); + + // 记录响应体(Debug 级别)。 + if (!string.IsNullOrEmpty(responseBody)) + { + _logger.LogDebug( + "[HTTP-RES-BODY] [{RequestId}] {Body}", + requestId, + TruncateBody(responseBody)); + } + } + } + + /// + /// 从流中读取文本内容。 + /// + private static async Task ReadBodyAsync(Stream stream, CancellationToken cancellationToken) + { + if (!stream.CanRead) + { + return string.Empty; + } + + using var reader = new StreamReader(stream, Encoding.UTF8, leaveOpen: true); + var body = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + return body; + } + + /// + /// 截断过长内容,避免日志膨胀。 + /// + private static string TruncateBody(string body) + { + if (body.Length <= MaxBodyLogLength) + { + return body; + } + + return body[..MaxBodyLogLength] + " ... [截断,总长度=" + body.Length + "]"; + } + + /// + /// 判断当前请求路径是否命中请求/响应日志忽略前缀。 + /// + private static bool ShouldSkipRequestResponseLog(PathString path) + { + var pathValue = path.Value; + return !string.IsNullOrEmpty(pathValue) + && IgnoredLogPathPrefixes.Any(prefix => pathValue.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); + } +} diff --git a/src/Flyshot.Server.Host/Program.cs b/src/Flyshot.Server.Host/Program.cs index 38ded6e..6e313ff 100644 --- a/src/Flyshot.Server.Host/Program.cs +++ b/src/Flyshot.Server.Host/Program.cs @@ -1,13 +1,124 @@ -var builder = WebApplication.CreateBuilder(args); -var app = builder.Build(); +using Flyshot.ControllerClientCompat; +using Flyshot.Server.Host; +using Flyshot.Server.Host.Middleware; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using NLog.Web; +using Swashbuckle.AspNetCore.SwaggerGen; -app.MapGet("/", () => Results.Redirect("/healthz")); -app.MapGet("/healthz", () => Results.Ok(new +// NLog: 在 ASP.NET Core 启动前完成配置加载,确保最早期的日志也能被捕获。 +NLog.LogManager.Setup().LoadConfigurationFromAppSettings(); +var logger = NLog.LogManager.GetCurrentClassLogger(); + +try { - status = "ok", - service = "flyshot-server-host" -})); + logger.Info("Flyshot Server Host 启动中..."); -app.Run(); + var builder = WebApplication.CreateBuilder(args); + + // NLog: 替换默认日志提供者为 NLog,清除其他 Provider 避免重复输出。 + builder.Logging.ClearProviders(); + builder.Host.UseNLog(); + + builder.Services.Configure(builder.Configuration.GetSection("Swagger")); + builder.Services.Configure(builder.Configuration.GetSection("Cors")); + builder.Services.AddControllerClientCompat(builder.Configuration); + builder.Services.AddControllers(); + builder.Services.AddEndpointsApiExplorer(); + + var swaggerOptions = builder.Configuration.GetSection("Swagger").Get() ?? new HostSwaggerOptions(); + builder.Services.AddSwaggerGen(options => + { + options.SwaggerDoc(swaggerOptions.DocumentName, new OpenApiInfo + { + Title = swaggerOptions.Title, + Version = swaggerOptions.Version + }); + + // 把控制器与 DTO 上的 /// summary 注释纳入 OpenAPI 文档;调试页据此渲染端点标题。 + var xmlDocumentationPath = Path.Combine(AppContext.BaseDirectory, $"{typeof(Program).Assembly.GetName().Name}.xml"); + if (File.Exists(xmlDocumentationPath)) + { + options.IncludeXmlComments(xmlDocumentationPath, includeControllerXmlComments: true); + } + }); + + var corsOptions = builder.Configuration.GetSection("Cors").Get() ?? new HostCorsOptions(); + builder.Services.AddCors(options => + { + options.AddPolicy(corsOptions.PolicyName, policyBuilder => + { + // 兼容本地调试时最常见的任意源配置,同时保留显式白名单模式。 + if (corsOptions.AllowedOrigins.Length == 1 && string.Equals(corsOptions.AllowedOrigins[0], "*", StringComparison.Ordinal)) + { + policyBuilder.AllowAnyOrigin(); + } + else + { + policyBuilder.WithOrigins(corsOptions.AllowedOrigins); + } + + if (corsOptions.AllowedMethods.Length == 1 && string.Equals(corsOptions.AllowedMethods[0], "*", StringComparison.Ordinal)) + { + policyBuilder.AllowAnyMethod(); + } + else + { + policyBuilder.WithMethods(corsOptions.AllowedMethods); + } + + if (corsOptions.AllowedHeaders.Length == 1 && string.Equals(corsOptions.AllowedHeaders[0], "*", StringComparison.Ordinal)) + { + policyBuilder.AllowAnyHeader(); + } + else + { + policyBuilder.WithHeaders(corsOptions.AllowedHeaders); + } + }); + }); + + var app = builder.Build(); + + var resolvedSwaggerOptions = app.Services.GetRequiredService>().Value; + var resolvedCorsOptions = app.Services.GetRequiredService>().Value; + + if (resolvedSwaggerOptions.Enabled) + { + app.UseSwagger(options => + { + options.RouteTemplate = resolvedSwaggerOptions.JsonRouteTemplate; + }); + + app.UseSwaggerUI(options => + { + options.RoutePrefix = resolvedSwaggerOptions.RoutePrefix; + options.SwaggerEndpoint( + $"/swagger/{resolvedSwaggerOptions.DocumentName}/swagger.json", + $"{resolvedSwaggerOptions.Title} {resolvedSwaggerOptions.Version}"); + options.DocumentTitle = resolvedSwaggerOptions.Title; + }); + } + + app.UseCors(resolvedCorsOptions.PolicyName); + app.UseStaticFiles(); + + // 注册 HTTP 请求/响应日志中间件,记录所有 API 调用的输入输出。 + app.UseMiddleware(); + + app.MapControllers(); + + logger.Info("Flyshot Server Host 已就绪,开始监听请求。"); + app.Run(); +} +catch (Exception exception) +{ + logger.Error(exception, "Flyshot Server Host 启动失败。"); + throw; +} +finally +{ + NLog.LogManager.Shutdown(); +} public partial class Program; diff --git a/src/Flyshot.Server.Host/appsettings.Development.json b/src/Flyshot.Server.Host/appsettings.Development.json index 0c208ae..99b4ec1 100644 --- a/src/Flyshot.Server.Host/appsettings.Development.json +++ b/src/Flyshot.Server.Host/appsettings.Development.json @@ -4,5 +4,33 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "ControllerClientCompat": { + "ServerVersion": "flyshot-replacement-controller-client-compat/0.1.0" + }, + "Swagger": { + "Enabled": true + }, + "Cors": { + "PolicyName": "LegacyHttpApi", + "AllowedOrigins": [ + "http://localhost:3000", + "http://127.0.0.1:3000" + ], + "AllowedMethods": [ + "GET", + "POST", + "OPTIONS" + ], + "AllowedHeaders": [ + "*" + ] + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://0.0.0.0:5190" + } + } } } diff --git a/src/Flyshot.Server.Host/appsettings.json b/src/Flyshot.Server.Host/appsettings.json index 10f68b8..e8d68f9 100644 --- a/src/Flyshot.Server.Host/appsettings.json +++ b/src/Flyshot.Server.Host/appsettings.json @@ -5,5 +5,37 @@ "Microsoft.AspNetCore": "Warning" } }, + "ControllerClientCompat": { + "ServerVersion": "flyshot-replacement-controller-client-compat/0.1.0" + }, + "Swagger": { + "Enabled": true, + "DocumentName": "v1", + "Title": "Flyshot Replacement HTTP API", + "Version": "v1", + "JsonRouteTemplate": "swagger/{documentName}/swagger.json", + "RoutePrefix": "swagger" + }, + "Cors": { + "PolicyName": "LegacyHttpApi", + "AllowedOrigins": [ + "*" + ], + "AllowedMethods": [ + "GET", + "POST", + "OPTIONS" + ], + "AllowedHeaders": [ + "*" + ] + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://0.0.0.0:5000" + } + } + }, "AllowedHosts": "*" } diff --git a/src/Flyshot.Server.Host/wwwroot/assets/debug.css b/src/Flyshot.Server.Host/wwwroot/assets/debug.css new file mode 100644 index 0000000..03f6c3d --- /dev/null +++ b/src/Flyshot.Server.Host/wwwroot/assets/debug.css @@ -0,0 +1,424 @@ +:root { + color-scheme: light; + --bg: #f5f7fb; + --surface: #ffffff; + --line: #d8dee9; + --text: #172033; + --muted: #5b667a; + --accent: #007c89; + --good: #12805c; + --warn: #b7791f; + --bad: #b42318; + --get: #1f6feb; + --post: #2da44e; + --put: #9a6700; + --delete: #cf222e; + --code-bg: #f4f6fa; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + min-height: 100vh; + background: var(--bg); + color: var(--text); + font-family: "Segoe UI", "Microsoft YaHei", Arial, sans-serif; + font-size: 14px; +} + +header { + border-bottom: 1px solid var(--line); + background: var(--surface); + position: sticky; + top: 0; + z-index: 10; +} + +.topbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + width: min(1280px, calc(100% - 32px)); + margin: 0 auto; + padding: 18px 0; +} + +h1 { + margin: 0; + font-size: 22px; + font-weight: 650; +} + +.actions { + display: flex; + align-items: center; + gap: 10px; +} + +button { + min-height: 34px; + padding: 0 14px; + border: 1px solid var(--accent); + border-radius: 6px; + background: var(--accent); + color: #ffffff; + font: inherit; + cursor: pointer; +} + +button.secondary { + background: transparent; + color: var(--accent); +} + +button:disabled { + opacity: 0.55; + cursor: default; +} + +.link-button { + display: inline-flex; + align-items: center; + min-height: 34px; + padding: 0 14px; + border: 1px solid var(--accent); + border-radius: 6px; + background: transparent; + color: var(--accent); + font: inherit; + text-decoration: none; +} + +.link-button:hover { + background: rgba(0, 124, 137, 0.08); +} + +main { + width: min(1280px, calc(100% - 32px)); + margin: 22px auto 60px; +} + +.meta { + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); + margin-bottom: 18px; +} + +.meta dl { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0; + margin: 0; + padding: 12px 16px; +} + +.meta dt { + color: var(--muted); + font-size: 12px; +} + +.meta dd { + margin: 4px 0 0; + font-family: Consolas, "Cascadia Mono", monospace; + overflow-wrap: anywhere; +} + +.meta dd.bad { color: var(--bad); } +.meta dd.good { color: var(--good); } + +.group { + margin-top: 22px; +} + +.group h2 { + margin: 0 0 10px 4px; + font-size: 16px; + font-weight: 650; + color: var(--muted); +} + +.card { + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); + margin-bottom: 12px; + overflow: hidden; +} + +.card-head { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + cursor: pointer; + user-select: none; +} + +.card-head:hover { + background: #fafbfd; +} + +.badge { + flex: 0 0 auto; + min-width: 60px; + padding: 3px 10px; + border-radius: 999px; + text-align: center; + color: #ffffff; + font-weight: 650; + font-size: 12px; + letter-spacing: 0.5px; +} + +.badge.GET { background: var(--get); } +.badge.POST { background: var(--post); } +.badge.PUT { background: var(--put); } +.badge.DELETE { background: var(--delete); } +.badge.OTHER { background: var(--muted); } + +.card-path { + flex: 1 1 auto; + font-family: Consolas, "Cascadia Mono", monospace; + font-size: 14px; + overflow-wrap: anywhere; +} + +.card-summary { + flex: 0 1 auto; + max-width: 50%; + color: var(--muted); + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.card-toggle { + flex: 0 0 auto; + color: var(--muted); + font-size: 12px; +} + +.card-body { + padding: 12px 16px 16px; + border-top: 1px solid var(--line); +} + +.card.collapsed .card-body { + display: none; +} + +.form-row { + display: grid; + grid-template-columns: 180px minmax(0, 1fr) 90px; + gap: 8px 12px; + align-items: center; + margin-bottom: 8px; +} + +.form-row .name { + font-family: Consolas, "Cascadia Mono", monospace; + color: var(--text); + overflow-wrap: anywhere; +} + +.form-row .name .required { + color: var(--bad); + margin-left: 4px; +} + +.form-row input[type="text"], +.form-row input[type="number"] { + width: 100%; + min-height: 32px; + padding: 4px 10px; + border: 1px solid var(--line); + border-radius: 4px; + font: inherit; +} + +.form-row .type { + color: var(--muted); + font-size: 12px; + font-family: Consolas, "Cascadia Mono", monospace; +} + +.body-block { + margin-top: 6px; +} + +.body-label { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 6px; +} + +.body-label .left { + color: var(--muted); + font-size: 12px; +} + +textarea.body-editor { + width: 100%; + min-height: 140px; + padding: 10px 12px; + border: 1px solid var(--line); + border-radius: 4px; + background: var(--code-bg); + font-family: Consolas, "Cascadia Mono", monospace; + font-size: 13px; + resize: vertical; +} + +.button-row { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; +} + +.response-block { + margin-top: 14px; + padding-top: 12px; + border-top: 1px dashed var(--line); +} + +.response-summary { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; + margin-bottom: 8px; + font-size: 13px; +} + +.status-badge { + padding: 2px 8px; + border-radius: 4px; + color: #ffffff; + font-weight: 650; +} + +.status-badge.s2xx { background: var(--good); } +.status-badge.s3xx { background: var(--get); } +.status-badge.s4xx { background: var(--warn); } +.status-badge.s5xx { background: var(--bad); } +.status-badge.error { background: var(--bad); } + +pre.response-body, +pre.response-headers { + margin: 6px 0 0; + padding: 10px 12px; + background: var(--code-bg); + border: 1px solid var(--line); + border-radius: 4px; + max-height: 360px; + overflow: auto; + white-space: pre-wrap; + overflow-wrap: anywhere; + font-family: Consolas, "Cascadia Mono", monospace; + font-size: 12.5px; +} + +pre.response-headers { + max-height: 160px; +} + +details > summary { + cursor: pointer; + color: var(--muted); + font-size: 12px; + margin: 6px 0 0; +} + +.empty-hint { + padding: 12px 0; + color: var(--muted); + font-style: italic; +} + +.history { + position: fixed; + right: 16px; + bottom: 16px; + width: 360px; + max-width: calc(100vw - 32px); + max-height: 50vh; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); + box-shadow: 0 8px 24px rgba(23, 32, 51, 0.12); + display: flex; + flex-direction: column; + z-index: 20; +} + +.history h3 { + margin: 0; + padding: 10px 14px; + border-bottom: 1px solid var(--line); + font-size: 13px; + font-weight: 650; + display: flex; + justify-content: space-between; + align-items: center; +} + +.history h3 button { + min-height: 24px; + padding: 0 8px; + font-size: 12px; +} + +.history ul { + list-style: none; + margin: 0; + padding: 6px 0; + overflow: auto; +} + +.history li { + padding: 6px 14px; + font-size: 12px; + border-bottom: 1px solid #edf1f7; + display: grid; + grid-template-columns: 50px 1fr auto; + gap: 8px; + align-items: center; +} + +.history li:last-child { border-bottom: 0; } + +.history li .h-method { + font-weight: 650; + font-family: Consolas, "Cascadia Mono", monospace; +} + +.history li .h-path { + font-family: Consolas, "Cascadia Mono", monospace; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +@media (max-width: 920px) { + .form-row { + grid-template-columns: 1fr; + } + + .meta dl { + grid-template-columns: 1fr; + } + + .history { + position: static; + width: auto; + margin-top: 18px; + max-height: none; + } +} diff --git a/src/Flyshot.Server.Host/wwwroot/assets/debug.js b/src/Flyshot.Server.Host/wwwroot/assets/debug.js new file mode 100644 index 0000000..c1175ad --- /dev/null +++ b/src/Flyshot.Server.Host/wwwroot/assets/debug.js @@ -0,0 +1,686 @@ +// 静态调试页通过配置 API 获取实际 Swagger JSON 地址,避免硬编码路由前缀。 +const DEBUG_CONFIG_URL = "/api/debug/config"; +const STORAGE_PREFIX = "flyshot.debug."; +const HISTORY_LIMIT = 10; + +const groupTitleByPrefix = [ + // 基础与状态分组:探活和状态快照两个固定 API 路径 + { match: function (op) { return op.path === "/healthz" || op.path === "/api/status/snapshot"; }, title: "基础与状态" }, + // 默认兜底:剩余全部走 ControllerClient 兼容分组 + { match: function () { return true; }, title: "ControllerClient 兼容" } +]; + +const state = { + spec: null, + operations: [], + history: [] +}; + +const requestBodySamples = { + "POST /move_pose/": { + x: 100.0, + y: 200.0, + z: 300.0, + w: 0.0, + p: 45.0, + r: 0.0 + } +}; + +/** 简单的 escape:把任意字符串安全嵌入 textContent 之外的位置时使用。 */ +function escapeHtml(value) { + return String(value).replace(/[&<>"']/g, function (ch) { + return { "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }[ch]; + }); +} + +/** 解析 OpenAPI 中的 $ref 引用,仅支持本地 components.schemas 形式。 */ +function resolveRef(ref) { + if (!ref || !state.spec) return null; + const parts = ref.replace(/^#\//, "").split("/"); + let cursor = state.spec; + for (const part of parts) { + if (cursor && Object.prototype.hasOwnProperty.call(cursor, part)) { + cursor = cursor[part]; + } else { + return null; + } + } + return cursor; +} + +/** 根据 schema 生成默认 JSON 模板,用于自动填充请求体编辑器。 */ +function buildSampleFromSchema(schema, depth) { + depth = depth || 0; + // 防御递归:复杂自引用 schema 在 4 层后停下,避免栈爆。 + if (!schema || depth > 4) return null; + + if (schema.$ref) { + const resolved = resolveRef(schema.$ref); + return resolved ? buildSampleFromSchema(resolved, depth + 1) : null; + } + + // 部分 schema 只标 oneOf/anyOf/allOf,挑第一个分支即可,调试场景够用。 + if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return buildSampleFromSchema(schema.oneOf[0], depth + 1); + if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return buildSampleFromSchema(schema.anyOf[0], depth + 1); + if (Array.isArray(schema.allOf) && schema.allOf.length > 0) { + const merged = {}; + schema.allOf.forEach(function (sub) { + const value = buildSampleFromSchema(sub, depth + 1); + if (value && typeof value === "object" && !Array.isArray(value)) Object.assign(merged, value); + }); + return merged; + } + + const type = schema.type || (schema.properties ? "object" : "string"); + switch (type) { + case "object": { + const result = {}; + const props = schema.properties || {}; + Object.keys(props).forEach(function (key) { + result[key] = buildSampleFromSchema(props[key], depth + 1); + }); + return result; + } + case "array": + return []; + case "integer": + case "number": + return 0; + case "boolean": + return false; + case "string": + default: + if (schema.enum && schema.enum.length > 0) return schema.enum[0]; + return ""; + } +} + +/** 把 schema.type 翻译成 input type 与展示文本。 */ +function inputKindForType(schema) { + if (!schema) return { kind: "text", label: "string" }; + const type = schema.type || "string"; + if (type === "boolean") return { kind: "checkbox", label: "boolean" }; + if (type === "integer" || type === "number") return { kind: "number", label: type }; + return { kind: "text", label: type }; +} + +/** 把 OpenAPI 的 paths 节点展开成扁平的 operation 列表。 */ +function extractOperations(spec) { + const operations = []; + const paths = spec.paths || {}; + Object.keys(paths).forEach(function (path) { + const pathItem = paths[path] || {}; + ["get", "post", "put", "delete", "patch", "options", "head"].forEach(function (method) { + const op = pathItem[method]; + if (!op) return; + const parameters = (op.parameters || []).filter(function (p) { return p.in === "query" || p.in === "path"; }); + let bodySchema = null; + if (op.requestBody && op.requestBody.content) { + const json = op.requestBody.content["application/json"]; + if (json && json.schema) bodySchema = json.schema; + } + operations.push({ + method: method.toUpperCase(), + path: path, + summary: op.summary || "", + description: op.description || "", + tags: op.tags || [], + parameters: parameters, + bodySchema: bodySchema + }); + }); + }); + return operations; +} + +/** 选择分组:优先用第一条匹配的 groupTitleByPrefix 规则,OpenAPI tag 留作兜底。 */ +function pickGroup(op) { + for (const rule of groupTitleByPrefix) { + if (rule.match(op)) return rule.title; + } + if (op.tags && op.tags.length > 0) return op.tags[0]; + return "其它"; +} + +/** localStorage key 必须避免冲突,使用 method:path 复合键。 */ +function storageKey(op) { + return STORAGE_PREFIX + op.method + ":" + op.path; +} + +/** 返回指定端点的手工调试样例,处理 JsonElement 等 OpenAPI 无法自动推断字段的接口。 */ +function getRequestBodySample(op) { + return requestBodySamples[op.method + " " + op.path] || null; +} + +/** 读取本端点最近一次输入;解析失败则当作空。 */ +function loadInputs(op) { + try { + const raw = window.localStorage.getItem(storageKey(op)); + return raw ? JSON.parse(raw) : null; + } catch (e) { + return null; + } +} + +/** 保存本端点最近一次输入;写入失败时静默忽略,避免影响调试体验。 */ +function saveInputs(op, payload) { + try { + window.localStorage.setItem(storageKey(op), JSON.stringify(payload)); + } catch (e) { + // localStorage 可能被禁用或满载,忽略写入失败。 + } +} + +/** 拼接最终请求 URL(含 query 串与 path 参数替换)。 */ +function buildRequestUrl(op, paramValues) { + let path = op.path; + const queryPairs = []; + op.parameters.forEach(function (param) { + const raw = paramValues[param.name]; + if (raw === undefined || raw === null || raw === "") return; + if (param.in === "path") { + path = path.replace("{" + param.name + "}", encodeURIComponent(raw)); + } else if (param.in === "query") { + queryPairs.push(encodeURIComponent(param.name) + "=" + encodeURIComponent(raw)); + } + }); + return path + (queryPairs.length > 0 ? "?" + queryPairs.join("&") : ""); +} + +/** 生成与浏览器请求等价的 curl 命令,便于复制到终端复现。 */ +function buildCurlCommand(op, requestUrl, body) { + const parts = ["curl", "-X", op.method, JSON.stringify(window.location.origin + requestUrl)]; + if (body !== null && body !== undefined && body !== "") { + parts.push("-H", "\"Content-Type: application/json\""); + parts.push("--data-raw", JSON.stringify(body)); + } + return parts.join(" "); +} + +/** 渲染参数输入表单,返回收集函数。 */ +function renderParameterRows(container, op, savedValues) { + if (op.parameters.length === 0) return function () { return {}; }; + + const inputs = {}; + op.parameters.forEach(function (param) { + const row = document.createElement("div"); + row.className = "form-row"; + + const nameNode = document.createElement("div"); + nameNode.className = "name"; + nameNode.textContent = param.name + " (" + param.in + ")"; + if (param.required) { + const requiredMark = document.createElement("span"); + requiredMark.className = "required"; + requiredMark.textContent = "*"; + nameNode.appendChild(requiredMark); + } + row.appendChild(nameNode); + + const kind = inputKindForType(param.schema); + const inputNode = document.createElement("input"); + inputNode.type = kind.kind; + if (kind.kind === "checkbox") { + inputNode.checked = savedValues && Object.prototype.hasOwnProperty.call(savedValues, param.name) + ? Boolean(savedValues[param.name]) + : Boolean(param.schema && param.schema.default); + } else { + let initial = ""; + if (savedValues && Object.prototype.hasOwnProperty.call(savedValues, param.name)) { + initial = String(savedValues[param.name]); + } else if (param.schema && param.schema.default !== undefined) { + initial = String(param.schema.default); + } + inputNode.value = initial; + if (kind.kind === "number") inputNode.step = "any"; + } + row.appendChild(inputNode); + + const typeNode = document.createElement("div"); + typeNode.className = "type"; + typeNode.textContent = kind.label; + row.appendChild(typeNode); + + container.appendChild(row); + inputs[param.name] = { node: inputNode, kind: kind.kind, schema: param.schema }; + }); + + return function collect() { + const collected = {}; + Object.keys(inputs).forEach(function (key) { + const item = inputs[key]; + if (item.kind === "checkbox") { + collected[key] = item.node.checked; + } else { + const raw = item.node.value; + if (raw === "") { + collected[key] = ""; + } else if (item.kind === "number") { + const num = Number(raw); + collected[key] = Number.isNaN(num) ? raw : num; + } else { + collected[key] = raw; + } + } + }); + return collected; + }; +} + +/** 渲染请求体编辑器,返回收集函数。 */ +function renderBodyEditor(container, op, savedBody) { + if (!op.bodySchema) return function () { return null; }; + + const block = document.createElement("div"); + block.className = "body-block"; + + const labelRow = document.createElement("div"); + labelRow.className = "body-label"; + const left = document.createElement("div"); + left.className = "left"; + left.textContent = "请求体 (application/json)"; + labelRow.appendChild(left); + + const formatBtn = document.createElement("button"); + formatBtn.type = "button"; + formatBtn.className = "secondary"; + formatBtn.textContent = "格式化 JSON"; + labelRow.appendChild(formatBtn); + + block.appendChild(labelRow); + + const textarea = document.createElement("textarea"); + textarea.className = "body-editor"; + textarea.spellcheck = false; + let initialText; + if (savedBody !== undefined && savedBody !== null) { + initialText = typeof savedBody === "string" ? savedBody : JSON.stringify(savedBody, null, 2); + } else { + const sample = getRequestBodySample(op) || buildSampleFromSchema(op.bodySchema, 0); + initialText = sample === null ? "" : JSON.stringify(sample, null, 2); + } + textarea.value = initialText; + block.appendChild(textarea); + + formatBtn.addEventListener("click", function () { + try { + const parsed = JSON.parse(textarea.value || "null"); + textarea.value = parsed === null ? "" : JSON.stringify(parsed, null, 2); + } catch (e) { + window.alert("JSON 解析失败: " + e.message); + } + }); + + container.appendChild(block); + + return function collect() { + return textarea.value; + }; +} + +/** 把 HTTP 状态码翻译成颜色徽标 class。 */ +function statusBadgeClass(status) { + if (status >= 200 && status < 300) return "s2xx"; + if (status >= 300 && status < 400) return "s3xx"; + if (status >= 400 && status < 500) return "s4xx"; + if (status >= 500) return "s5xx"; + return "error"; +} + +/** 把响应头展开成可读字符串。 */ +function formatHeaders(headers) { + const lines = []; + headers.forEach(function (value, key) { lines.push(key + ": " + value); }); + return lines.join("\n"); +} + +/** 在历史面板顶部追加一条记录,超过上限则丢弃尾部。 */ +function pushHistory(entry) { + state.history.unshift(entry); + if (state.history.length > HISTORY_LIMIT) state.history.length = HISTORY_LIMIT; + renderHistory(); +} + +function renderHistory() { + const list = document.getElementById("history-list"); + list.innerHTML = ""; + if (state.history.length === 0) { + const empty = document.createElement("li"); + empty.textContent = "暂无调用记录"; + empty.style.color = "var(--muted)"; + empty.style.gridTemplateColumns = "1fr"; + list.appendChild(empty); + return; + } + state.history.forEach(function (entry) { + const li = document.createElement("li"); + const method = document.createElement("span"); + method.className = "h-method"; + method.textContent = entry.method; + method.style.color = entry.method === "GET" ? "var(--get)" : entry.method === "POST" ? "var(--post)" : "var(--muted)"; + const path = document.createElement("span"); + path.className = "h-path"; + path.title = entry.url; + path.textContent = entry.url; + const meta = document.createElement("span"); + meta.style.color = "var(--muted)"; + meta.textContent = (entry.status || "ERR") + " · " + entry.elapsedMs + "ms"; + li.appendChild(method); + li.appendChild(path); + li.appendChild(meta); + list.appendChild(li); + }); +} + +/** 渲染单个端点的卡片。 */ +function renderOperationCard(op) { + const card = document.createElement("section"); + card.className = "card collapsed"; + + const head = document.createElement("div"); + head.className = "card-head"; + + const badge = document.createElement("span"); + badge.className = "badge " + (["GET", "POST", "PUT", "DELETE"].indexOf(op.method) >= 0 ? op.method : "OTHER"); + badge.textContent = op.method; + head.appendChild(badge); + + const path = document.createElement("span"); + path.className = "card-path"; + path.textContent = op.path; + head.appendChild(path); + + const summary = document.createElement("span"); + summary.className = "card-summary"; + summary.textContent = op.summary; + summary.title = op.summary; + head.appendChild(summary); + + const toggle = document.createElement("span"); + toggle.className = "card-toggle"; + toggle.textContent = "展开 ▾"; + head.appendChild(toggle); + + head.addEventListener("click", function () { + const collapsed = card.classList.toggle("collapsed"); + toggle.textContent = collapsed ? "展开 ▾" : "收起 ▴"; + }); + card.appendChild(head); + + const body = document.createElement("div"); + body.className = "card-body"; + + // 描述(来自 XML summary)独立成一段 + if (op.summary) { + const desc = document.createElement("div"); + desc.style.color = "var(--muted)"; + desc.style.marginBottom = "10px"; + desc.style.fontSize = "13px"; + desc.textContent = op.summary; + body.appendChild(desc); + } + + const saved = loadInputs(op) || {}; + + // 参数区 + let collectParams = function () { return {}; }; + if (op.parameters.length > 0) { + const paramsContainer = document.createElement("div"); + paramsContainer.className = "params"; + body.appendChild(paramsContainer); + collectParams = renderParameterRows(paramsContainer, op, saved.params); + } + + // 请求体区 + const collectBody = renderBodyEditor(body, op, saved.body); + + // 操作按钮 + const buttonRow = document.createElement("div"); + buttonRow.className = "button-row"; + + const sendBtn = document.createElement("button"); + sendBtn.type = "button"; + sendBtn.textContent = "发送"; + + const resetBtn = document.createElement("button"); + resetBtn.type = "button"; + resetBtn.className = "secondary"; + resetBtn.textContent = "重置"; + + const curlBtn = document.createElement("button"); + curlBtn.type = "button"; + curlBtn.className = "secondary"; + curlBtn.textContent = "复制 curl"; + + buttonRow.appendChild(sendBtn); + buttonRow.appendChild(resetBtn); + buttonRow.appendChild(curlBtn); + body.appendChild(buttonRow); + + // 响应面板 + const responseBlock = document.createElement("div"); + responseBlock.className = "response-block"; + responseBlock.style.display = "none"; + body.appendChild(responseBlock); + + function renderResponse(payload) { + responseBlock.style.display = "block"; + responseBlock.innerHTML = ""; + + const summaryRow = document.createElement("div"); + summaryRow.className = "response-summary"; + + const statusBadge = document.createElement("span"); + statusBadge.className = "status-badge " + statusBadgeClass(payload.status || 0); + statusBadge.textContent = payload.status ? payload.status + " " + (payload.statusText || "") : "请求失败"; + summaryRow.appendChild(statusBadge); + + const elapsed = document.createElement("span"); + elapsed.style.color = "var(--muted)"; + elapsed.textContent = payload.elapsedMs + " ms · " + payload.url; + summaryRow.appendChild(elapsed); + + responseBlock.appendChild(summaryRow); + + if (payload.error) { + const pre = document.createElement("pre"); + pre.className = "response-body"; + pre.textContent = payload.error; + responseBlock.appendChild(pre); + return; + } + + const headersDetails = document.createElement("details"); + const headersSummary = document.createElement("summary"); + headersSummary.textContent = "响应头"; + headersDetails.appendChild(headersSummary); + const headersPre = document.createElement("pre"); + headersPre.className = "response-headers"; + headersPre.textContent = payload.headers; + headersDetails.appendChild(headersPre); + responseBlock.appendChild(headersDetails); + + const bodyPre = document.createElement("pre"); + bodyPre.className = "response-body"; + bodyPre.textContent = payload.bodyText; + responseBlock.appendChild(bodyPre); + } + + sendBtn.addEventListener("click", async function () { + sendBtn.disabled = true; + const params = collectParams(); + const rawBody = collectBody(); + saveInputs(op, { params: params, body: rawBody }); + + const requestUrl = buildRequestUrl(op, params); + const init = { method: op.method, headers: {} }; + + // 仅 POST/PUT/PATCH/DELETE 才认为可能携带 body;对没有 bodySchema 的方法直接跳过。 + const methodAllowsBody = ["POST", "PUT", "PATCH", "DELETE"].indexOf(op.method) >= 0; + if (methodAllowsBody && op.bodySchema && rawBody !== null && rawBody !== undefined && rawBody !== "") { + init.headers["Content-Type"] = "application/json"; + init.body = rawBody; + } + + const startedAt = performance.now(); + try { + const response = await fetch(requestUrl, init); + const elapsedMs = Math.round(performance.now() - startedAt); + const text = await response.text(); + const contentType = response.headers.get("content-type") || ""; + let bodyText = text; + if (contentType.indexOf("application/json") >= 0) { + try { + bodyText = JSON.stringify(JSON.parse(text), null, 2); + } catch (e) { + bodyText = text; + } + } + renderResponse({ + status: response.status, + statusText: response.statusText, + headers: formatHeaders(response.headers), + bodyText: bodyText, + url: requestUrl, + elapsedMs: elapsedMs + }); + pushHistory({ method: op.method, url: requestUrl, status: response.status, elapsedMs: elapsedMs }); + } catch (err) { + const elapsedMs = Math.round(performance.now() - startedAt); + renderResponse({ + error: String(err && err.message ? err.message : err), + url: requestUrl, + elapsedMs: elapsedMs + }); + pushHistory({ method: op.method, url: requestUrl, status: 0, elapsedMs: elapsedMs }); + } finally { + sendBtn.disabled = false; + } + }); + + resetBtn.addEventListener("click", function () { + try { window.localStorage.removeItem(storageKey(op)); } catch (e) { /* 忽略 */ } + // 直接重新渲染当前卡片:替换原 DOM 节点。 + const refreshed = renderOperationCard(op); + refreshed.classList.remove("collapsed"); + refreshed.querySelector(".card-toggle").textContent = "收起 ▴"; + card.parentNode.replaceChild(refreshed, card); + }); + + curlBtn.addEventListener("click", function () { + const params = collectParams(); + const rawBody = collectBody(); + const requestUrl = buildRequestUrl(op, params); + const methodAllowsBody = ["POST", "PUT", "PATCH", "DELETE"].indexOf(op.method) >= 0; + const bodyForCurl = methodAllowsBody && op.bodySchema ? rawBody : null; + const command = buildCurlCommand(op, requestUrl, bodyForCurl); + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(command).then(function () { + curlBtn.textContent = "已复制 ✓"; + window.setTimeout(function () { curlBtn.textContent = "复制 curl"; }, 1500); + }).catch(function () { + window.prompt("复制失败,手动复制:", command); + }); + } else { + window.prompt("复制失败,手动复制:", command); + } + }); + + card.appendChild(body); + return card; +} + +/** 把 operation 列表按分组渲染到主区域。 */ +function renderGroups(operations) { + const root = document.getElementById("debug-console-app"); + root.innerHTML = ""; + if (operations.length === 0) { + const empty = document.createElement("div"); + empty.className = "empty-hint"; + empty.textContent = "OpenAPI 文档中没有任何端点。"; + root.appendChild(empty); + return; + } + + const grouped = new Map(); + operations.forEach(function (op) { + const groupTitle = pickGroup(op); + if (!grouped.has(groupTitle)) grouped.set(groupTitle, []); + grouped.get(groupTitle).push(op); + }); + + // 固定输出顺序:基础与状态在前,ControllerClient 兼容在后,其余按字典序。 + const orderedTitles = []; + ["基础与状态", "ControllerClient 兼容"].forEach(function (title) { + if (grouped.has(title)) orderedTitles.push(title); + }); + Array.from(grouped.keys()).sort().forEach(function (title) { + if (orderedTitles.indexOf(title) < 0) orderedTitles.push(title); + }); + + orderedTitles.forEach(function (title) { + const ops = grouped.get(title); + ops.sort(function (a, b) { + if (a.path === b.path) return a.method.localeCompare(b.method); + return a.path.localeCompare(b.path); + }); + + const section = document.createElement("section"); + section.className = "group"; + const heading = document.createElement("h2"); + heading.textContent = title + " (" + ops.length + ")"; + section.appendChild(heading); + ops.forEach(function (op) { section.appendChild(renderOperationCard(op)); }); + root.appendChild(section); + }); +} + +/** 加载 OpenAPI 文档并渲染。 */ +async function loadSpecAndRender() { + const metaSpec = document.getElementById("meta-spec-url"); + const metaCount = document.getElementById("meta-operation-count"); + const metaStatus = document.getElementById("meta-status"); + + metaSpec.textContent = "正在读取调试配置..."; + metaStatus.textContent = "正在拉取 OpenAPI 文档..."; + metaStatus.className = ""; + + try { + const configResponse = await fetch(DEBUG_CONFIG_URL, { cache: "no-store" }); + if (!configResponse.ok) throw new Error("调试配置 HTTP " + configResponse.status + " " + configResponse.statusText); + const config = await configResponse.json(); + const swaggerJsonUrl = config.swaggerJsonUrl; + if (!swaggerJsonUrl) throw new Error("调试配置缺少 swaggerJsonUrl"); + metaSpec.textContent = swaggerJsonUrl; + + const response = await fetch(swaggerJsonUrl, { cache: "no-store" }); + if (!response.ok) throw new Error("HTTP " + response.status + " " + response.statusText); + const spec = await response.json(); + state.spec = spec; + state.operations = extractOperations(spec); + metaCount.textContent = state.operations.length; + metaStatus.textContent = "已加载"; + metaStatus.className = "good"; + renderGroups(state.operations); + } catch (err) { + metaStatus.textContent = "加载失败: " + (err && err.message ? err.message : err); + metaStatus.className = "bad"; + metaCount.textContent = "0"; + const root = document.getElementById("debug-console-app"); + root.innerHTML = ""; + const errBlock = document.createElement("div"); + errBlock.className = "empty-hint"; + errBlock.textContent = "无法加载 OpenAPI 文档,请确认 Swagger:Enabled = true 且 " + DEBUG_CONFIG_URL + " 可访问。"; + root.appendChild(errBlock); + } +} + +document.getElementById("reload-spec").addEventListener("click", loadSpecAndRender); +document.getElementById("history-clear").addEventListener("click", function () { + state.history.length = 0; + renderHistory(); +}); + +renderHistory(); +loadSpecAndRender(); diff --git a/src/Flyshot.Server.Host/wwwroot/assets/status.css b/src/Flyshot.Server.Host/wwwroot/assets/status.css new file mode 100644 index 0000000..e17f75c --- /dev/null +++ b/src/Flyshot.Server.Host/wwwroot/assets/status.css @@ -0,0 +1,526 @@ +:root { + color-scheme: light; + --bg: #f5f7fb; + --surface: #ffffff; + --line: #d8dee9; + --text: #172033; + --muted: #5b667a; + --accent: #007c89; + --accent-dark: #005d67; + --steel: #2f3b4f; + --steel-soft: #e7ebf1; + --good: #12805c; + --warn: #b7791f; + --bad: #b42318; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background: var(--bg); + color: var(--text); + font-family: "Segoe UI", "Microsoft YaHei", Arial, sans-serif; + font-size: 15px; + letter-spacing: 0; +} + +header { + border-bottom: 1px solid var(--line); + background: var(--surface); +} + +.topbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + width: min(1180px, calc(100% - 32px)); + margin: 0 auto; + padding: 18px 0; +} + +h1 { + margin: 0; + font-size: 22px; + font-weight: 650; +} + +button { + min-height: 36px; + padding: 0 14px; + border: 1px solid var(--accent); + border-radius: 6px; + background: var(--accent); + color: #ffffff; + font: inherit; + cursor: pointer; +} + +button:disabled { + opacity: 0.6; + cursor: default; +} + +/* 顶部操作区按钮和外链按钮共用同一组视觉样式,便于现场顺手跳转。 */ +.actions { + display: flex; + align-items: center; + gap: 10px; +} + +.link-button { + display: inline-flex; + align-items: center; + min-height: 36px; + padding: 0 14px; + border: 1px solid var(--accent); + border-radius: 6px; + background: transparent; + color: var(--accent); + font: inherit; + text-decoration: none; +} + +.link-button:hover { + background: rgba(0, 124, 137, 0.08); +} + +main { + width: min(1180px, calc(100% - 32px)); + margin: 22px auto; +} + +.summary { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + margin-bottom: 16px; +} + +.metric, +section { + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); +} + +.metric { + min-height: 86px; + padding: 14px; +} + +.label { + color: var(--muted); + font-size: 13px; +} + +.value { + margin-top: 8px; + overflow-wrap: anywhere; + font-size: 24px; + font-weight: 650; +} + +.status-row { + display: flex; + align-items: center; + gap: 8px; +} + +.dot { + width: 12px; + height: 12px; + flex: 0 0 12px; + border-radius: 999px; + background: var(--warn); +} + +.dot.good { + background: var(--good); +} + +.dot.bad { + background: var(--bad); +} + +.grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +section h2 { + margin: 0; + padding: 14px 16px; + border-bottom: 1px solid var(--line); + font-size: 16px; + font-weight: 650; +} + +dl { + display: grid; + grid-template-columns: 160px minmax(0, 1fr); + gap: 0; + margin: 0; + padding: 4px 16px 12px; +} + +dt, +dd { + min-height: 36px; + margin: 0; + padding: 9px 0; + border-bottom: 1px solid #edf1f7; +} + +dt { + color: var(--muted); +} + +dd { + overflow-wrap: anywhere; + font-family: Consolas, "Cascadia Mono", monospace; +} + +.empty { + color: var(--muted); + font-family: inherit; +} + +.jog-panel { + grid-column: 1 / -1; + overflow: hidden; +} + +.jog-toolbar { + display: grid; + grid-template-columns: repeat(2, minmax(180px, 1fr)) minmax(220px, 1fr); + gap: 12px; + padding: 16px; + border-bottom: 1px solid var(--line); + background: #fbfcfe; +} + +.step-field { + display: grid; + gap: 6px; + color: var(--muted); + font-size: 13px; +} + +.step-field input { + min-height: 38px; + width: 100%; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 6px; + background: #ffffff; + color: var(--text); + font: inherit; +} + +.jog-body { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 18px; + padding: 16px; +} + +.stick-card { + min-width: 0; + border: 1px solid #ccd4df; + border-radius: 8px; + background: linear-gradient(180deg, #ffffff 0%, #f3f6fa 100%); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9); +} + +.stick-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 14px 16px 0; +} + +.stick-head h3 { + margin: 0; + font-size: 16px; + font-weight: 650; +} + +.stick-head span { + color: var(--muted); + font-family: Consolas, "Cascadia Mono", monospace; + font-size: 13px; +} + +.stick-console { + display: grid; + grid-template-columns: 72px 1fr 72px; + grid-template-rows: 72px 72px 72px; + gap: 8px; + width: min(100%, 344px); + margin: 18px auto 16px; + padding: 14px; + border: 1px solid #c2cad7; + border-radius: 999px; + background: + radial-gradient(circle at 50% 50%, #ffffff 0 26%, #e6ebf2 27% 48%, #cdd5e0 49% 50%, #edf1f6 51% 100%); +} + +.jog-button { + min-width: 44px; + min-height: 44px; + color: var(--steel); + font-weight: 650; + touch-action: none; + user-select: none; +} + +.axis-button, +.mini-button { + border: 1px solid #99a8ba; + border-radius: 999px; + background: linear-gradient(180deg, #ffffff 0%, #e5ebf2 100%); + box-shadow: 0 2px 0 #b7c1ce; +} + +.axis-button { + min-width: 0; + min-height: 64px; + padding: 0 8px; +} + +.mini-button { + padding: 0 10px; + background: var(--surface); +} + +.axis-button:hover, +.axis-button.active { + border-color: var(--accent); + background: linear-gradient(180deg, #10a4b3 0%, var(--accent) 100%); + color: #ffffff; + box-shadow: 0 2px 0 var(--accent-dark); +} + +.mini-button:hover, +.mini-button.active { + border-color: var(--warn); + background: var(--warn); + color: #ffffff; + box-shadow: 0 2px 0 #8a5a13; +} + +.jog-button:focus-visible { + outline: 3px solid rgba(0, 124, 137, 0.3); + outline-offset: 2px; +} + +.jog-button.active { + transform: translateY(1px); +} + +.axis-button.up { + grid-column: 2; + grid-row: 1; +} + +.axis-button.left { + grid-column: 1; + grid-row: 2; +} + +.axis-button.right { + grid-column: 3; + grid-row: 2; +} + +.axis-button.down { + grid-column: 2; + grid-row: 3; +} + +.center-stack { + display: grid; + grid-column: 2; + grid-row: 2; + grid-template-columns: 1fr 1fr; + gap: 6px; + place-items: center; + padding: 8px; + border-radius: 999px; + background: rgba(47, 59, 79, 0.08); +} + +.hint-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + padding: 0 16px 16px; + color: var(--muted); + font-size: 13px; + text-align: center; +} + +.hint-row span { + min-height: 28px; + padding: 5px 8px; + border-radius: 999px; + background: var(--steel-soft); + white-space: nowrap; +} + +.jog-status { + display: flex; + align-items: center; + justify-content: flex-end; + min-height: 38px; + color: var(--muted); + font-family: Consolas, "Cascadia Mono", monospace; + text-align: right; +} + +.jog-status.error { + color: var(--bad); +} + +.log-panel { + margin: 0 16px 16px; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: #fbfcfe; +} + +.log-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 14px; + border-bottom: 1px solid var(--line); + background: var(--surface); +} + +.log-head h3 { + margin: 0; + font-size: 15px; + font-weight: 650; +} + +.clear-button { + min-height: 34px; + padding: 0 12px; + border-color: var(--line); + background: #ffffff; + color: var(--muted); +} + +.clear-button:hover { + border-color: var(--accent); + color: var(--accent); +} + +.request-log { + display: grid; + gap: 8px; + max-height: 300px; + min-height: 54px; + margin: 0; + padding: 12px 14px 14px; + overflow: auto; + font-family: Consolas, "Cascadia Mono", monospace; + font-size: 13px; +} + +.log-entry { + display: grid; + grid-template-columns: 88px 96px minmax(0, 1fr); + gap: 10px; + align-items: start; + padding: 9px 10px; + border: 1px solid #e1e7ef; + border-radius: 6px; + background: #ffffff; +} + +.log-entry:first-child { + border-color: rgba(0, 124, 137, 0.35); + background: rgba(0, 124, 137, 0.04); +} + +.log-entry.error { + border-color: rgba(180, 35, 24, 0.35); + background: rgba(180, 35, 24, 0.06); +} + +.log-time, +.log-axis { + color: var(--muted); + white-space: nowrap; +} + +.log-payload { + overflow-wrap: anywhere; + color: var(--text); +} + +.log-empty { + color: var(--muted); +} + +@media (max-width: 820px) { + .topbar { + align-items: flex-start; + flex-direction: column; + } + + .summary, + .grid { + grid-template-columns: 1fr; + } + + .jog-toolbar, + .jog-body { + grid-template-columns: 1fr; + } + + .jog-status { + justify-content: flex-start; + text-align: left; + } + + dl { + grid-template-columns: 1fr; + } + + dt { + border-bottom: 0; + padding-bottom: 2px; + } + + dd { + padding-top: 2px; + } +} + +@media (max-width: 520px) { + .stick-console { + grid-template-columns: 60px 1fr 60px; + grid-template-rows: 60px 60px 60px; + padding: 10px; + } + + .axis-button { + min-height: 56px; + } + + .log-entry { + grid-template-columns: 1fr; + } +} diff --git a/src/Flyshot.Server.Host/wwwroot/assets/status.js b/src/Flyshot.Server.Host/wwwroot/assets/status.js new file mode 100644 index 0000000..5c97ffa --- /dev/null +++ b/src/Flyshot.Server.Host/wwwroot/assets/status.js @@ -0,0 +1,333 @@ +const fields = { + connectionState: document.getElementById("connection-state"), + stateDot: document.getElementById("state-dot"), + robotName: document.getElementById("robot-name"), + speedRatio: document.getElementById("speed-ratio"), + motionState: document.getElementById("motion-state"), + serverVersion: document.getElementById("server-version"), + clientVersion: document.getElementById("client-version"), + setupState: document.getElementById("setup-state"), + enabledState: document.getElementById("enabled-state"), + j519Status: document.getElementById("j519-status"), + j519Sequence: document.getElementById("j519-sequence"), + capturedAt: document.getElementById("captured-at"), + dof: document.getElementById("dof"), + joints: document.getElementById("joints"), + pose: document.getElementById("pose"), + trajectories: document.getElementById("trajectories"), + refresh: document.getElementById("refresh"), + linearStep: document.getElementById("linear-step"), + angularStep: document.getElementById("angular-step"), + jogStatus: document.getElementById("jog-status"), + requestLog: document.getElementById("request-log"), + clearLog: document.getElementById("clear-log"), + jogButtons: Array.from(document.querySelectorAll(".jog-button")) +}; + +const axisIndexes = { + x: 0, + y: 1, + z: 2, + w: 3, + p: 4, + r: 5 +}; + +// 点动状态集中保存,确保按住按钮时不会并发发送多条 MovePose 请求。 +const jogState = { + timer: null, + activeButton: null, + inFlight: false, + lastSnapshot: null, + sequence: 0, + logs: [] +}; + +const maxJogLogs = 80; +const jogIntervalMs = 250; + +function formatArray(values) { + if (!Array.isArray(values) || values.length === 0) { + return "--"; + } + + return values.map(value => Number(value).toFixed(4)).join(", "); +} + +function formatNullableBool(value) { + if (value === true) { + return "是"; + } + + if (value === false) { + return "否"; + } + + return "--"; +} + +function formatJ519Status(snapshot) { + if (snapshot.j519Status === null || snapshot.j519Status === undefined) { + return "--"; + } + + const status = Number(snapshot.j519Status).toString(16).padStart(2, "0").toUpperCase(); + return `0x${status} accept=${formatNullableBool(snapshot.j519AcceptsCommand)} received=${formatNullableBool(snapshot.j519ReceivedCommand)} sysrdy=${formatNullableBool(snapshot.j519SystemReady)} motion=${formatNullableBool(snapshot.j519RobotInMotion)}`; +} + +function setDot(connectionState) { + fields.stateDot.className = "dot"; + if (connectionState === "Connected") { + fields.stateDot.classList.add("good"); + } else if (connectionState === "NotConfigured") { + fields.stateDot.classList.add("bad"); + } +} + +function clampStep(input, min, max) { + const value = Number(input.value); + if (!Number.isFinite(value)) { + input.value = String(min); + return min; + } + + const clamped = Math.min(Math.max(value, min), max); + input.value = String(clamped); + return clamped; +} + +function getStepForAxis(axis) { + // 平移和姿态使用不同单位,但共享 0.1 到 20 的现场可调范围。 + return axis === "x" || axis === "y" || axis === "z" + ? clampStep(fields.linearStep, 0.1, 20) + : clampStep(fields.angularStep, 0.1, 20); +} + +function setJogStatus(message, isError) { + fields.jogStatus.textContent = message; + fields.jogStatus.classList.toggle("error", Boolean(isError)); +} + +function formatDirection(axis, direction) { + return `${axis.toUpperCase()}${direction > 0 ? "+" : "-"}`; +} + +function renderJogLogs() { + fields.requestLog.replaceChildren(); + + if (jogState.logs.length === 0) { + const empty = document.createElement("div"); + empty.className = "log-empty"; + empty.textContent = "暂无请求记录"; + fields.requestLog.appendChild(empty); + return; + } + + jogState.logs.forEach(function (entry) { + const row = document.createElement("div"); + row.className = `log-entry${entry.ok ? "" : " error"}`; + + const time = document.createElement("span"); + time.className = "log-time"; + time.textContent = entry.time; + + const axis = document.createElement("span"); + axis.className = "log-axis"; + axis.textContent = `#${entry.sequence} ${entry.direction}`; + + const payload = document.createElement("span"); + payload.className = "log-payload"; + payload.textContent = `step=${entry.step} ${entry.unit}; body=${entry.body}; ${entry.result}`; + + row.append(time, axis, payload); + fields.requestLog.appendChild(row); + }); +} + +function addJogLog(entry) { + jogState.logs.unshift(entry); + if (jogState.logs.length > maxJogLogs) { + jogState.logs.length = maxJogLogs; + } + + renderJogLogs(); +} + +async function loadSnapshotForJog() { + const response = await fetch("/api/status/snapshot", { cache: "no-store" }); + if (!response.ok) { + throw new Error(`状态快照读取失败: HTTP ${response.status}`); + } + + const payload = await response.json(); + jogState.lastSnapshot = payload.snapshot; + return payload.snapshot; +} + +function buildJogPose(snapshot, axis, direction) { + // 每次点动都从最新 TCP 位姿出发,只修改一个轴,避免连续按压时累积本地误差。 + const sourcePose = Array.isArray(snapshot.cartesianPose) ? snapshot.cartesianPose : []; + if (sourcePose.length < 6) { + throw new Error("当前 TCP 位姿不足 6 维,无法点动。"); + } + + const pose = sourcePose.slice(0, 6).map(Number); + if (pose.some(value => !Number.isFinite(value))) { + throw new Error("当前 TCP 位姿包含非数值,无法点动。"); + } + + const axisIndex = axisIndexes[axis]; + pose[axisIndex] = Number((pose[axisIndex] + direction * getStepForAxis(axis)).toFixed(6)); + return { + x: pose[0], + y: pose[1], + z: pose[2], + w: pose[3], + p: pose[4], + r: pose[5] + }; +} + +async function sendJog(button) { + // MovePose 底层会生成完整直角轨迹;前端这里只负责构造增量目标位姿。 + if (jogState.inFlight) { + return; + } + + const axis = button.dataset.axis; + const direction = Number(button.dataset.direction); + if (!Object.prototype.hasOwnProperty.call(axisIndexes, axis) || !Number.isFinite(direction)) { + return; + } + + jogState.inFlight = true; + const step = getStepForAxis(axis); + const directionLabel = formatDirection(axis, direction); + const sequence = ++jogState.sequence; + let bodyText = "{}"; + let resultText = ""; + let ok = false; + + try { + const snapshot = await loadSnapshotForJog(); + const pose = buildJogPose(snapshot, axis, direction); + bodyText = JSON.stringify(pose); + const response = await fetch("/move_pose/", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: bodyText + }); + + if (!response.ok) { + throw new Error(`MovePose 调用失败: HTTP ${response.status}`); + } + + ok = true; + resultText = `HTTP ${response.status}`; + setJogStatus(`${directionLabel} 已发送`, false); + await refreshStatus(); + } catch (error) { + resultText = error instanceof Error ? error.message : "点动失败"; + setJogStatus(resultText, true); + stopJog(); + } finally { + addJogLog({ + time: new Date().toLocaleTimeString(), + sequence, + direction: directionLabel, + step, + unit: axis === "x" || axis === "y" || axis === "z" ? "mm" : "deg", + body: bodyText, + result: resultText, + ok + }); + jogState.inFlight = false; + } +} + +function startJog(event) { + const button = event.currentTarget; + if (!button || button.disabled) { + return; + } + + event.preventDefault(); + stopJog(); + jogState.activeButton = button; + button.classList.add("active"); + sendJog(button); + jogState.timer = window.setInterval(function () { + sendJog(button); + }, jogIntervalMs); +} + +function stopJog() { + if (jogState.timer !== null) { + window.clearInterval(jogState.timer); + jogState.timer = null; + } + + if (jogState.activeButton) { + jogState.activeButton.classList.remove("active"); + jogState.activeButton = null; + } +} + +async function refreshStatus() { + fields.refresh.disabled = true; + try { + const response = await fetch("/api/status/snapshot", { cache: "no-store" }); + const payload = await response.json(); + const snapshot = payload.snapshot; + jogState.lastSnapshot = snapshot; + + fields.connectionState.textContent = snapshot.connectionState; + fields.robotName.textContent = payload.robotName || "--"; + fields.speedRatio.textContent = Number(snapshot.speedRatio).toFixed(2); + fields.motionState.textContent = snapshot.isInMotion ? "是" : "否"; + fields.serverVersion.textContent = payload.serverVersion; + fields.clientVersion.textContent = payload.clientVersion; + fields.setupState.textContent = payload.isSetup ? "是" : "否"; + fields.enabledState.textContent = snapshot.isEnabled ? "是" : "否"; + fields.j519Status.textContent = formatJ519Status(snapshot); + fields.j519Sequence.textContent = snapshot.j519Sequence ?? "--"; + fields.capturedAt.textContent = new Date(snapshot.capturedAt).toLocaleString(); + fields.dof.textContent = payload.degreesOfFreedom; + fields.joints.textContent = formatArray(snapshot.jointPositions); + fields.pose.textContent = formatArray(snapshot.cartesianPose); + fields.trajectories.textContent = payload.uploadedTrajectories.length > 0 + ? payload.uploadedTrajectories.join(", ") + : "--"; + fields.trajectories.classList.toggle("empty", payload.uploadedTrajectories.length === 0); + setDot(snapshot.connectionState); + } finally { + fields.refresh.disabled = false; + } +} + +fields.refresh.addEventListener("click", refreshStatus); +fields.linearStep.addEventListener("change", function () { + clampStep(fields.linearStep, 0.1, 20); +}); +fields.angularStep.addEventListener("change", function () { + clampStep(fields.angularStep, 0.1, 20); +}); +fields.clearLog.addEventListener("click", function () { + jogState.logs = []; + renderJogLogs(); +}); +fields.jogButtons.forEach(function (button) { + button.addEventListener("pointerdown", startJog); + button.addEventListener("pointerup", stopJog); + button.addEventListener("pointercancel", stopJog); + button.addEventListener("pointerleave", stopJog); +}); +window.addEventListener("blur", stopJog); +window.addEventListener("keyup", function (event) { + if (event.key === "Escape") { + stopJog(); + } +}); +renderJogLogs(); +refreshStatus(); +window.setInterval(refreshStatus, 2000); diff --git a/src/Flyshot.Server.Host/wwwroot/debug.html b/src/Flyshot.Server.Host/wwwroot/debug.html new file mode 100644 index 0000000..3b85ee4 --- /dev/null +++ b/src/Flyshot.Server.Host/wwwroot/debug.html @@ -0,0 +1,44 @@ + + + + + + Flyshot Replacement 接口调试 + + + +
+
+

Flyshot Replacement 接口调试

+
+ 回到状态页 + Swagger UI + +
+
+
+
+
+
+
OpenAPI 文档
+
--
+
API 数量
+
--
+
加载状态
+
初始化中...
+
+
+
+
正在加载接口列表...
+
+
+ + + + diff --git a/src/Flyshot.Server.Host/wwwroot/status.html b/src/Flyshot.Server.Host/wwwroot/status.html new file mode 100644 index 0000000..7239670 --- /dev/null +++ b/src/Flyshot.Server.Host/wwwroot/status.html @@ -0,0 +1,131 @@ + + + + + + Flyshot Replacement 状态监控 + + + +
+
+

Flyshot Replacement 状态监控

+
+ 调试接口 + +
+
+
+
+
+
+
连接状态
+
--
+
+
+
机器人
+
--
+
+
+
速度倍率
+
--
+
+
+
运动中
+
--
+
+
+
+
+

控制器

+
+
服务端版本
--
+
客户端版本
--
+
已初始化
--
+
已使能
--
+
J519 状态
--
+
J519 序号
--
+
采样时间
--
+
+
+
+

机器人

+
+
自由度
--
+
关节位置
--
+
TCP 位姿
--
+
已上传轨迹
--
+
+
+
+

直角坐标点动

+
+ + +
等待点动输入
+
+
+
+
+

XYZ 平移摇杆

+ mm +
+
+ + +
+ + +
+ + +
+
+ X 左右 + Y 上下 + Z 中心 +
+
+
+
+

WPR 姿态摇杆

+ deg +
+
+ + +
+ + +
+ + +
+
+ W 左右 + P 上下 + R 中心 +
+
+
+
+
+

请求日志

+ +
+
+
暂无请求记录
+
+
+
+
+
+ + + diff --git a/tests/Flyshot.Core.Tests/ConfigCompatibilityTests.cs b/tests/Flyshot.Core.Tests/ConfigCompatibilityTests.cs index ac3b989..be32aa1 100644 --- a/tests/Flyshot.Core.Tests/ConfigCompatibilityTests.cs +++ b/tests/Flyshot.Core.Tests/ConfigCompatibilityTests.cs @@ -1,37 +1,39 @@ using Flyshot.Core.Config; -using Flyshot.Core.Domain; namespace Flyshot.Core.Tests; /// -/// 锁定 Task 3 的兼容输入行为,确保旧配置、.robot 元数据和路径策略都能被稳定加载。 +/// 锁定 Task 3 的兼容输入行为,确保旧配置、JSON 模型元数据和路径策略都能被稳定加载。 /// public sealed class ConfigCompatibilityTests { /// - /// 验证现有 RobotConfig.json 能被加载,并保持关键机器人参数与飞拍程序内容不变。 + /// 验证现有旧样本配置能被加载,并保持关键机器人参数与飞拍程序内容不变。 /// [Fact] public void RobotConfigLoader_LoadsLegacyRobotConfig_AndPreservesPrograms() { var workspaceRoot = GetWorkspaceRoot(); - var configPath = Path.Combine(workspaceRoot, "Rvbust", "EOL10_EAU_0", "RobotConfig.json"); + var configPath = Path.Combine(workspaceRoot, "Rvbust", "EOL10_EAU_0", "EOL10_EAU_0.json"); var loaded = new RobotConfigLoader().Load(configPath); Assert.True(loaded.Robot.UseDo); Assert.Equal([7, 8], loaded.Robot.IoAddresses); Assert.Equal(2, loaded.Robot.IoKeepCycles); + Assert.Equal(7, loaded.Robot.TriggerSampleIndexOffsetCycles); Assert.Equal(1.0, loaded.Robot.AccLimitScale); Assert.Equal(1.0, loaded.Robot.JerkLimitScale); + Assert.Equal(1.0, loaded.Robot.PlanningSpeedScale); + Assert.True(loaded.Robot.SmoothStartStopTiming); Assert.Equal(5, loaded.Robot.AdaptIcspTryNum); - var program = Assert.Contains("001", loaded.Programs); - Assert.Equal("001", program.Name); - Assert.Equal(5, program.Waypoints.Count); - Assert.Equal(3, program.ShotWaypointCount); + var program = Assert.Contains("EOL10_EAU_0", loaded.Programs); + Assert.Equal("EOL10_EAU_0", program.Name); + Assert.Equal(45, program.Waypoints.Count); + Assert.Equal(42, program.ShotWaypointCount); Assert.Empty(program.AddressGroups[0].Addresses); - Assert.Equal([8, 7], program.AddressGroups[1].Addresses); + Assert.Equal([4, 3], program.AddressGroups[1].Addresses); } /// @@ -69,8 +71,12 @@ public sealed class ConfigCompatibilityTests Assert.False(loaded.Robot.UseDo); Assert.Empty(loaded.Robot.IoAddresses); Assert.Equal(3, loaded.Robot.IoKeepCycles); + Assert.Equal(7, loaded.Robot.TriggerSampleIndexOffsetCycles); Assert.Equal(0.5, loaded.Robot.AccLimitScale); Assert.Equal(0.25, loaded.Robot.JerkLimitScale); + Assert.Equal(1.0, loaded.Robot.PlanningSpeedScale); + Assert.Equal(1.0, loaded.Robot.SpeedRatio); + Assert.True(loaded.Robot.SmoothStartStopTiming); Assert.Equal([0, 0, 0], program.OffsetValues); Assert.All(program.AddressGroups, group => Assert.Empty(group.Addresses)); } @@ -81,13 +87,173 @@ public sealed class ConfigCompatibilityTests } /// - /// 验证 .robot 解析会保留 Joint3 对 Joint2 的 couple 元数据,并构造规划侧可直接消费的 RobotProfile。 + /// 验证 RobotConfig.json 可以显式配置运行时默认速度倍率,供 MoveJoint 和后续执行链路初始化使用。 + /// + [Fact] + public void RobotConfigLoader_LoadsRuntimeSpeedRatio() + { + var tempRoot = CreateTempDirectory(); + try + { + var configPath = Path.Combine(tempRoot, "legacy.json"); + File.WriteAllText( + configPath, + """ + { + "robot": { + "use_do": true, + "io_keep_cycles": 2, + "acc_limit": 1.0, + "jerk_limit": 1.0, + "speed_ratio": 0.65 + }, + "flying_shots": { + "demo": { + "traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]], + "shot_flags": [false, false, false, false] + } + } + } + """); + + var loaded = new RobotConfigLoader().Load(configPath); + + Assert.Equal(0.65, loaded.Robot.SpeedRatio, precision: 6); + } + finally + { + Directory.Delete(tempRoot, recursive: true); + } + } + + /// + /// 验证 RobotConfig.json 可以显式配置规划限速倍率,且该倍率独立于运行时 J519 速度倍率。 + /// + [Fact] + public void RobotConfigLoader_LoadsPlanningSpeedScale() + { + var tempRoot = CreateTempDirectory(); + try + { + var configPath = Path.Combine(tempRoot, "legacy.json"); + File.WriteAllText( + configPath, + """ + { + "robot": { + "use_do": true, + "io_keep_cycles": 2, + "acc_limit": 1.0, + "jerk_limit": 1.0, + "planning_speed_scale": 0.742277 + }, + "flying_shots": { + "demo": { + "traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]], + "shot_flags": [false, false, false, false] + } + } + } + """); + + var loaded = new RobotConfigLoader().Load(configPath); + + Assert.Equal(0.742277, loaded.Robot.PlanningSpeedScale, precision: 6); + } + finally + { + Directory.Delete(tempRoot, recursive: true); + } + } + + /// + /// 验证 RobotConfig.json 可以显式配置触发绑定后的命令 sample 后移周期数。 + /// + [Fact] + public void RobotConfigLoader_LoadsTriggerSampleIndexOffsetCycles() + { + var tempRoot = CreateTempDirectory(); + try + { + var configPath = Path.Combine(tempRoot, "legacy.json"); + File.WriteAllText( + configPath, + """ + { + "robot": { + "use_do": true, + "io_keep_cycles": 2, + "trigger_sample_index_offset_cycles": 8, + "acc_limit": 1.0, + "jerk_limit": 1.0 + }, + "flying_shots": { + "demo": { + "traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]], + "shot_flags": [false, false, false, false] + } + } + } + """); + + var loaded = new RobotConfigLoader().Load(configPath); + + Assert.Equal(8, loaded.Robot.TriggerSampleIndexOffsetCycles); + } + finally + { + Directory.Delete(tempRoot, recursive: true); + } + } + + /// + /// 验证 RobotConfig.json 可以关闭飞拍执行前的二次平滑起停时间重映射。 + /// + [Fact] + public void RobotConfigLoader_LoadsSmoothStartStopTimingSwitch() + { + var tempRoot = CreateTempDirectory(); + try + { + var configPath = Path.Combine(tempRoot, "legacy.json"); + File.WriteAllText( + configPath, + """ + { + "robot": { + "use_do": true, + "io_keep_cycles": 2, + "acc_limit": 1.0, + "jerk_limit": 1.0, + "smooth_start_stop_timing": false + }, + "flying_shots": { + "demo": { + "traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]], + "shot_flags": [false, false, false, false] + } + } + } + """); + + var loaded = new RobotConfigLoader().Load(configPath); + + Assert.False(loaded.Robot.SmoothStartStopTiming); + } + finally + { + Directory.Delete(tempRoot, recursive: true); + } + } + + /// + /// 验证 JSON 模型解析会保留 Joint3 对 Joint2 的 couple 元数据,并构造规划侧可直接消费的 RobotProfile。 /// [Fact] public void RobotModelLoader_LoadsRobotProfile_WithJointLimitsAndCoupling() { - var workspaceRoot = GetWorkspaceRoot(); - var modelPath = Path.Combine(workspaceRoot, "FlyingShot", "FlyingShot", "Models", "LR_Mate_200iD_7L.robot"); + var replacementRoot = GetReplacementRoot(); + var modelPath = Path.Combine(replacementRoot, "Config", "Models", "LR_Mate_200iD_7L.json"); var profile = new RobotModelLoader().LoadProfile(modelPath); @@ -105,13 +271,13 @@ public sealed class ConfigCompatibilityTests } /// - /// 验证 RobotConfig 中的 acc_limit 和 jerk_limit 乘子会正确叠加到模型关节限制上。 + /// 验证 RobotConfig 中的 acc_limit 和 jerk_limit 乘子会正确叠加到 JSON 模型关节限制上。 /// [Fact] public void RobotModelLoader_AppliesAccelerationAndJerkScales() { - var workspaceRoot = GetWorkspaceRoot(); - var modelPath = Path.Combine(workspaceRoot, "FlyingShot", "FlyingShot", "Models", "LR_Mate_200iD_7L.robot"); + var replacementRoot = GetReplacementRoot(); + var modelPath = Path.Combine(replacementRoot, "Config", "Models", "LR_Mate_200iD_7L.json"); var profile = new RobotModelLoader().LoadProfile(modelPath, accLimitScale: 0.5, jerkLimitScale: 0.25); @@ -120,10 +286,57 @@ public sealed class ConfigCompatibilityTests } /// - /// 验证路径兼容层既能补旧目录候选,也能按平台策略生成默认用户数据目录。 + /// 验证 JSON 模型可一次解析后同时生成规划约束视图和运动学几何视图。 /// [Fact] - public void PathCompatibility_ResolvesLegacyCandidates_AndBuildsUserDataRoots() + public void RobotModelLoader_LoadsProfileAndKinematics_FromSingleParse() + { + var replacementRoot = GetReplacementRoot(); + var modelPath = Path.Combine(replacementRoot, "Config", "Models", "LR_Mate_200iD_7L.json"); + + var loaded = new RobotModelLoader().LoadProfileAndKinematics(modelPath, accLimitScale: 0.5, jerkLimitScale: 0.25); + + Assert.Equal("FANUC_LR_Mate_200iD_7L", loaded.Profile.Name); + Assert.Equal(modelPath, loaded.Profile.ModelPath); + Assert.Equal(6, loaded.Profile.DegreesOfFreedom); + Assert.Equal(14.905, loaded.Profile.JointLimits[2].AccelerationLimit, precision: 3); + Assert.Equal(62.115, loaded.Profile.JointLimits[2].JerkLimit, precision: 3); + + Assert.Equal("FANUC_LR_Mate_200iD_7L", loaded.KinematicsModel.Name); + Assert.True(loaded.KinematicsModel.Joints.Count >= loaded.Profile.DegreesOfFreedom); + Assert.Contains(loaded.KinematicsModel.Joints, static joint => joint.Name == "Joint3" && joint.CoupleMaster == "Joint2"); + } + + /// + /// 验证路径兼容层只从当前服务配置目录解析相对配置,并按平台策略生成默认用户数据目录。 + /// + [Fact] + public void PathCompatibility_ResolvesConfigDirectoryOnly_AndBuildsUserDataRoots() + { + var tempRoot = CreateTempDirectory(); + try + { + var configPath = Path.Combine(tempRoot, "Config", "sample.json"); + Directory.CreateDirectory(Path.GetDirectoryName(configPath)!); + File.WriteAllText(configPath, "{}"); + + var resolved = PathCompatibility.ResolveConfigPath("sample.json", tempRoot); + + Assert.Equal(configPath, resolved); + Assert.Equal("/home/tester/.Rvbust/Data", PathCompatibility.BuildUserDataRoot("/home/tester", CompatibilityPathStyle.Posix)); + Assert.Equal(@"C:\Users\tester\.Rvbust\Data", PathCompatibility.BuildUserDataRoot(@"C:\Users\tester", CompatibilityPathStyle.Windows)); + } + finally + { + Directory.Delete(tempRoot, recursive: true); + } + } + + /// + /// 验证旧父工作区候选路径存在时也不会被相对配置解析隐式命中。 + /// + [Fact] + public void PathCompatibility_DoesNotResolveLegacyWorkspaceFallbacks() { var tempRoot = CreateTempDirectory(); try @@ -132,11 +345,9 @@ public sealed class ConfigCompatibilityTests Directory.CreateDirectory(Path.GetDirectoryName(legacyConfigPath)!); File.WriteAllText(legacyConfigPath, "{}"); - var resolved = PathCompatibility.ResolveConfigPath("sample.json", tempRoot); + var exception = Assert.Throws(() => PathCompatibility.ResolveConfigPath("sample.json", tempRoot)); - Assert.Equal(legacyConfigPath, resolved); - Assert.Equal("/home/tester/.Rvbust/Data", PathCompatibility.BuildUserDataRoot("/home/tester", CompatibilityPathStyle.Posix)); - Assert.Equal(@"C:\Users\tester\.Rvbust\Data", PathCompatibility.BuildUserDataRoot(@"C:\Users\tester", CompatibilityPathStyle.Windows)); + Assert.Equal("sample.json", exception.FileName); } finally { @@ -144,6 +355,19 @@ public sealed class ConfigCompatibilityTests } } + /// + /// 验证默认加载配置时使用当前 replacement 仓库内的 Config/RobotConfig.json。 + /// + [Fact] + public void RobotConfigLoader_LoadsRepositoryConfigFromReplacementConfigDirectory() + { + var replacementRoot = GetReplacementRoot(); + + var loaded = new RobotConfigLoader().Load("RobotConfig.json"); + + Assert.Equal(Path.Combine(replacementRoot, "Config", "RobotConfig.json"), loaded.SourcePath); + } + /// /// 定位当前工作区根目录,便于复用父仓库中的真实样本。 /// @@ -164,6 +388,25 @@ public sealed class ConfigCompatibilityTests throw new DirectoryNotFoundException("Unable to locate the flyshot workspace root."); } + /// + /// 定位 replacement 仓库根目录,供测试读取仓库内固化配置。 + /// + private static string GetReplacementRoot() + { + var current = new DirectoryInfo(AppContext.BaseDirectory); + while (current is not null) + { + if (File.Exists(Path.Combine(current.FullName, "FlyshotReplacement.sln"))) + { + return current.FullName; + } + + current = current.Parent; + } + + throw new DirectoryNotFoundException("Unable to locate the flyshot replacement root."); + } + /// /// 创建当前测试专用的临时目录,避免不同测试之间相互污染。 /// diff --git a/tests/Flyshot.Core.Tests/ControllerClientCompatConfigRootTests.cs b/tests/Flyshot.Core.Tests/ControllerClientCompatConfigRootTests.cs new file mode 100644 index 0000000..167811a --- /dev/null +++ b/tests/Flyshot.Core.Tests/ControllerClientCompatConfigRootTests.cs @@ -0,0 +1,153 @@ +using Flyshot.ControllerClientCompat; +using Flyshot.Core.Config; + +namespace Flyshot.Core.Tests; + +/// +/// 验证 ControllerClient 兼容层默认围绕运行目录 Config 读写配置和轨迹文件。 +/// +public sealed class ControllerClientCompatConfigRootTests +{ + /// + /// 验证路径兼容层优先命中运行目录 Config 下的 RobotConfig.json,而不是旧仓库根目录候选。 + /// + [Fact] + public void PathCompatibility_ResolvesRuntimeConfigBeforeLegacyCandidates() + { + var runtimeRoot = CreateTempDirectory(); + try + { + var configPath = Path.Combine(runtimeRoot, "Config", "RobotConfig.json"); + var legacyPath = Path.Combine(runtimeRoot, "RobotConfig.json"); + Directory.CreateDirectory(Path.GetDirectoryName(configPath)!); + File.WriteAllText(configPath, "{}"); + File.WriteAllText(legacyPath, "{}"); + + var resolved = PathCompatibility.ResolveConfigPath("RobotConfig.json", runtimeRoot); + + Assert.Equal(configPath, resolved); + } + finally + { + Directory.Delete(runtimeRoot, recursive: true); + } + } + + /// + /// 验证机器人目录优先从显式 ConfigRoot/Models 加载现场 JSON 模型文件。 + /// + [Fact] + public void ControllerClientCompatRobotCatalog_LoadsModelFromConfigRootModels() + { + var configRoot = CreateTempConfigRoot(); + try + { + CopySampleRobotModel(configRoot); + var options = new ControllerClientCompatOptions { ConfigRoot = configRoot }; + var catalog = new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()); + + var profile = catalog.LoadProfile("FANUC_LR_Mate_200iD"); + + Assert.Equal(Path.Combine(configRoot, "Models", "LR_Mate_200iD_7L.json"), profile.ModelPath); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证 JSON 轨迹存储保存、加载和删除都落在 ConfigRoot/RobotConfig.json。 + /// + [Fact] + public void JsonFlyshotTrajectoryStore_PersistsTrajectoriesInRobotConfigJson() + { + var configRoot = CreateTempConfigRoot(); + try + { + var options = new ControllerClientCompatOptions { ConfigRoot = configRoot }; + var store = new JsonFlyshotTrajectoryStore(options, new RobotConfigLoader()); + var settings = new CompatibilityRobotSettings( + useDo: true, + ioAddresses: [7, 8], + ioKeepCycles: 2, + triggerSampleIndexOffsetCycles: 7, + accLimitScale: 1.0, + jerkLimitScale: 1.0, + adaptIcspTryNum: 5, + speedRatio: 0.65); + var trajectory = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + + store.Save("FANUC_LR_Mate_200iD", settings, trajectory); + var expectedPath = Path.Combine(configRoot, "RobotConfig.json"); + + Assert.True(File.Exists(expectedPath), $"应在运行目录 Config 下创建统一配置文件: {expectedPath}"); + Assert.False(Directory.Exists(Path.Combine(configRoot, "TrajectoryStore")), "不应再创建独立轨迹存储目录。"); + var loaded = store.LoadAll("FANUC_LR_Mate_200iD", out var loadedSettings); + Assert.NotNull(loadedSettings); + Assert.Equal(7, loadedSettings.TriggerSampleIndexOffsetCycles); + Assert.Equal(0.65, loadedSettings.SpeedRatio, precision: 6); + Assert.Contains(trajectory.Name, loaded); + + store.Delete("FANUC_LR_Mate_200iD", trajectory.Name); + + var afterDelete = store.LoadAll("FANUC_LR_Mate_200iD", out _); + Assert.Empty(afterDelete); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 创建测试专用的运行目录 Config 根,避免污染真实输出目录。 + /// + private static string CreateTempConfigRoot() + { + var root = Path.Combine(Path.GetTempPath(), "flyshot-config-root-tests", Guid.NewGuid().ToString("N"), "Config"); + Directory.CreateDirectory(root); + return root; + } + + /// + /// 创建测试专用的临时目录。 + /// + private static string CreateTempDirectory() + { + var root = Path.Combine(Path.GetTempPath(), "flyshot-config-root-tests", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + return root; + } + + /// + /// 复制仓库内已固化的现场机器人 JSON 模型到临时 Config/Models 目录。 + /// + private static void CopySampleRobotModel(string configRoot) + { + var modelDir = Path.Combine(configRoot, "Models"); + Directory.CreateDirectory(modelDir); + File.Copy( + Path.Combine(GetReplacementRoot(), "Config", "Models", "LR_Mate_200iD_7L.json"), + Path.Combine(modelDir, "LR_Mate_200iD_7L.json")); + } + + /// + /// 定位 replacement 仓库根目录,供测试读取仓库内固化样本。 + /// + private static string GetReplacementRoot() + { + var current = new DirectoryInfo(AppContext.BaseDirectory); + while (current is not null) + { + if (File.Exists(Path.Combine(current.FullName, "FlyshotReplacement.sln"))) + { + return current.FullName; + } + + current = current.Parent; + } + + throw new DirectoryNotFoundException("Unable to locate the flyshot replacement root."); + } +} diff --git a/tests/Flyshot.Core.Tests/DomainModelTests.cs b/tests/Flyshot.Core.Tests/DomainModelTests.cs index f094479..6192dae 100644 --- a/tests/Flyshot.Core.Tests/DomainModelTests.cs +++ b/tests/Flyshot.Core.Tests/DomainModelTests.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Flyshot.Core.Domain; +using Flyshot.Core.Planning.Sampling; namespace Flyshot.Core.Tests; @@ -114,6 +115,27 @@ public sealed class DomainModelTests Assert.Empty(snapshot.JointPositions); Assert.Empty(snapshot.CartesianPose); Assert.Empty(snapshot.ActiveAlarms); + Assert.Empty(snapshot.StateTailWords); + } + + /// + /// 验证控制器快照会保留 TCP 10010 尾部状态字作为诊断字段。 + /// + [Fact] + public void ControllerStateSnapshot_CopiesStateTailWordsForDiagnostics() + { + var snapshot = new ControllerStateSnapshot( + capturedAt: DateTimeOffset.Parse("2026-04-23T10:00:00+08:00"), + connectionState: "Connected", + isEnabled: true, + isInMotion: false, + speedRatio: 1.0, + stateTailWords: [2u, 0u, 0u, 1u]); + + var json = JsonSerializer.Serialize(snapshot); + + Assert.Equal([2u, 0u, 0u, 1u], snapshot.StateTailWords); + Assert.Contains("\"stateTailWords\":[2,0,0,1]", json); } /// @@ -143,7 +165,8 @@ public sealed class DomainModelTests triggerTime: 0.5, offsetCycles: 0, holdCycles: 1, - addressGroup: new IoAddressGroup(new[] { 100 })) + addressGroup: new IoAddressGroup(new[] { 100 }), + referenceJointsDegrees: new[] { 12.5, -3.0 }) }, artifacts: new[] { @@ -155,7 +178,8 @@ public sealed class DomainModelTests failureReason: null, usedCache: true, originalWaypointCount: 4, - plannedWaypointCount: 5); + plannedWaypointCount: 5, + triggerSampleIndexOffsetCycles: 7); var json = JsonSerializer.Serialize(result); @@ -163,6 +187,36 @@ public sealed class DomainModelTests Assert.Contains("\"method\":\"SelfAdaptIcsp\"", json); Assert.Contains("\"kind\":\"JointDenseTrajectory\"", json); Assert.Contains("\"usedCache\":true", json); + Assert.Contains("\"triggerSampleIndexOffsetCycles\":7", json); + Assert.Contains("\"referenceJointsDegrees\":[12.5,-3]", json); + } + + /// + /// 验证触发绑定允许在最佳 sample 基础上继续向后偏移固定命令周期。 + /// + [Fact] + public void TriggerSampleBinder_Bind_AppliesConfiguredSampleIndexOffset() + { + var trigger = new TrajectoryDoEvent( + waypointIndex: 1, + triggerTime: 0.008, + offsetCycles: 0, + holdCycles: 2, + addressGroup: new IoAddressGroup([2, 4]), + referenceJointsDegrees: [10.0, 20.0]); + var samples = new[] + { + new J519SendSample(sampleIndex: 0, sendTime: 0.0, trajectoryTime: 0.0, speedRatio: 1.0, jointsDegrees: [0.0, 0.0]), + new J519SendSample(sampleIndex: 1, sendTime: 0.008, trajectoryTime: 0.008, speedRatio: 1.0, jointsDegrees: [10.0, 20.0]), + new J519SendSample(sampleIndex: 2, sendTime: 0.016, trajectoryTime: 0.016, speedRatio: 1.0, jointsDegrees: [11.0, 21.0]), + new J519SendSample(sampleIndex: 3, sendTime: 0.024, trajectoryTime: 0.024, speedRatio: 1.0, jointsDegrees: [12.0, 22.0]) + }; + + var binding = Assert.Single(Flyshot.Core.Planning.Sampling.TriggerSampleBinder.Bind([trigger], samples, sampleIndexOffsetCycles: 2)); + + Assert.True(binding.FoundInWindow); + Assert.Equal(3, binding.SampleIndex); + Assert.Equal(0.024, binding.Sample.SendTime, precision: 6); } /// diff --git a/tests/Flyshot.Core.Tests/FanucCommandClientTests.cs b/tests/Flyshot.Core.Tests/FanucCommandClientTests.cs new file mode 100644 index 0000000..60dfcc1 --- /dev/null +++ b/tests/Flyshot.Core.Tests/FanucCommandClientTests.cs @@ -0,0 +1,319 @@ +using System.Net; +using System.Net.Sockets; +using Flyshot.Runtime.Fanuc.Protocol; + +namespace Flyshot.Core.Tests; + +/// +/// 验证 FANUC TCP 10012 命令客户端的帧收发与响应解析。 +/// +public sealed class FanucCommandClientTests : IDisposable +{ + private readonly TcpListener _listener; + private readonly CancellationTokenSource _cts = new(); + + /// + /// 在随机可用端口启动本地模拟控制器。 + /// + public FanucCommandClientTests() + { + _listener = new TcpListener(IPAddress.Loopback, 0); + _listener.Start(); + } + + /// + /// 获取分配给本地模拟控制器的端口。 + /// + private int Port => ((IPEndPoint)_listener.LocalEndpoint).Port; + + /// + /// 清理模拟控制器和取消源。 + /// + public void Dispose() + { + _cts.Cancel(); + _listener.Stop(); + _cts.Dispose(); + } + + /// + /// 验证命令客户端可以连接本地模拟控制器。 + /// + [Fact] + public async Task ConnectAsync_ConnectsToLocalListener() + { + using var client = new FanucCommandClient(); + var acceptTask = _listener.AcceptTcpClientAsync(); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + + Assert.True(client.IsConnected); + // 确保模拟侧也完成握手 + await acceptTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 StopProgram 命令帧与抓包样本一致,并能解析成功响应。 + /// + [Fact] + public async Task StopProgramAsync_SendsCorrectFrameAndParsesSuccess() + { + using var client = new FanucCommandClient(); + var handlerTask = RunSingleResponseControllerAsync( + Convert.FromHexString("646f7a0000001a0000210300000008525642555354534d7a6f64"), + Convert.FromHexString("646f7a0000001200002103000000007a6f64"), + _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.StopProgramAsync("RVBUSTSM", _cts.Token); + + Assert.True(response.IsSuccess); + Assert.Equal(FanucCommandMessageIds.StopProgram, response.MessageId); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 ResetRobot 空命令帧能正确发送并解析结果响应。 + /// + [Fact] + public async Task ResetRobotAsync_SendsEmptyCommandAndParsesResponse() + { + using var client = new FanucCommandClient(); + var expectedFrame = FanucCommandProtocol.PackEmptyCommand(FanucCommandMessageIds.ResetRobot); + var responseFrame = Convert.FromHexString("646f7a0000001200002100000000007a6f64"); + var handlerTask = RunSingleResponseControllerAsync(expectedFrame, responseFrame, _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.ResetRobotAsync(_cts.Token); + + Assert.True(response.IsSuccess); + Assert.Equal(FanucCommandMessageIds.ResetRobot, response.MessageId); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 GetProgramStatus 命令帧能正确发送并解析程序状态响应。 + /// + [Fact] + public async Task GetProgramStatusAsync_SendsFrameAndParsesStatusResponse() + { + using var client = new FanucCommandClient(); + var expectedFrame = FanucCommandProtocol.PackProgramCommand(FanucCommandMessageIds.GetProgramStatus, "RVBUSTSM"); + var responseFrame = Convert.FromHexString("646f7a000000160000200300000001000000007a6f64"); + var handlerTask = RunSingleResponseControllerAsync(expectedFrame, responseFrame, _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.GetProgramStatusAsync("RVBUSTSM", _cts.Token); + + Assert.True(response.IsSuccess); + Assert.Equal(FanucCommandMessageIds.GetProgramStatus, response.MessageId); + Assert.Equal(1u, response.ProgramStatus); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 StartProgram 命令帧能正确发送并解析成功响应。 + /// + [Fact] + public async Task StartProgramAsync_SendsCorrectFrameAndParsesSuccess() + { + using var client = new FanucCommandClient(); + var expectedFrame = FanucCommandProtocol.PackProgramCommand(FanucCommandMessageIds.StartProgram, "RVBUSTSM"); + var responseFrame = Convert.FromHexString("646f7a0000001200002102000000007a6f64"); + var handlerTask = RunSingleResponseControllerAsync(expectedFrame, responseFrame, _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.StartProgramAsync("RVBUSTSM", _cts.Token); + + Assert.True(response.IsSuccess); + Assert.Equal(FanucCommandMessageIds.StartProgram, response.MessageId); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 GetSpeedRatio 发送空业务体命令,并按 ratio_int / 100.0 解析倍率。 + /// + [Fact] + public async Task GetSpeedRatioAsync_SendsFrameAndParsesRatio() + { + using var client = new FanucCommandClient(); + var handlerTask = RunSingleResponseControllerAsync( + FanucCommandProtocol.PackGetSpeedRatioCommand(), + FanucCommandProtocol.PackFrame(FanucCommandMessageIds.GetSpeedRatio, Convert.FromHexString("0000005a00000000")), + _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.GetSpeedRatioAsync(_cts.Token); + + Assert.True(response.IsSuccess); + Assert.Equal(0.9, response.Ratio, precision: 6); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 SetSpeedRatio 会把 double 倍率夹到 0..100 的整数百分比后下发。 + /// + [Fact] + public async Task SetSpeedRatioAsync_SendsClampedPercentAndParsesSuccess() + { + using var client = new FanucCommandClient(); + var handlerTask = RunSingleResponseControllerAsync( + FanucCommandProtocol.PackSetSpeedRatioCommand(2.0), + FanucCommandProtocol.PackFrame(FanucCommandMessageIds.SetSpeedRatio, Convert.FromHexString("00000000")), + _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.SetSpeedRatioAsync(2.0, _cts.Token); + + Assert.True(response.IsSuccess); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 GetTcp 会发送 tcp_id 请求,并解析 result_code + tcp_id + 7 个 float 位姿。 + /// + [Fact] + public async Task GetTcpAsync_SendsFrameAndParsesPose() + { + using var client = new FanucCommandClient(); + var handlerTask = RunSingleResponseControllerAsync( + FanucCommandProtocol.PackGetTcpCommand(1), + FanucCommandProtocol.PackFrame( + FanucCommandMessageIds.GetTcp, + Convert.FromHexString("00000000000000013f80000040000000404000000000000000000000000000003f800000")), + _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.GetTcpAsync(1, _cts.Token); + + Assert.True(response.IsSuccess); + Assert.Equal([1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 1.0], response.Pose); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 SetTcp 会按 tcp_id + 7 个 float 位姿下发并解析结果码。 + /// + [Fact] + public async Task SetTcpAsync_SendsFrameAndParsesSuccess() + { + using var client = new FanucCommandClient(); + var handlerTask = RunSingleResponseControllerAsync( + FanucCommandProtocol.PackSetTcpCommand(1, [1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 1.0]), + FanucCommandProtocol.PackFrame(FanucCommandMessageIds.SetTcp, Convert.FromHexString("00000000")), + _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.SetTcpAsync(1, [1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 1.0], _cts.Token); + + Assert.True(response.IsSuccess); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 GetIo 会按 io_type、io_index 顺序请求,并解析 float IO 值。 + /// + [Fact] + public async Task GetIoAsync_SendsFrameAndParsesValue() + { + using var client = new FanucCommandClient(); + var handlerTask = RunSingleResponseControllerAsync( + FanucCommandProtocol.PackGetIoCommand(FanucIoTypes.DigitalOutput, 7), + FanucCommandProtocol.PackFrame(FanucCommandMessageIds.GetIo, Convert.FromHexString("000000003f800000")), + _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.GetIoAsync(7, "DO", _cts.Token); + + Assert.True(response.IsSuccess); + Assert.True(response.Value); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证 SetIo 会按 io_type、io_index、float value 顺序下发并解析结果码。 + /// + [Fact] + public async Task SetIoAsync_SendsFrameAndParsesSuccess() + { + using var client = new FanucCommandClient(); + var handlerTask = RunSingleResponseControllerAsync( + FanucCommandProtocol.PackSetIoCommand(FanucIoTypes.DigitalOutput, 7, true), + FanucCommandProtocol.PackFrame(FanucCommandMessageIds.SetIo, Convert.FromHexString("00000000")), + _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var response = await client.SetIoAsync(7, true, "DO", _cts.Token); + + Assert.True(response.IsSuccess); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证命令响应 result_code 非零时,客户端会抛出可诊断异常而不是让上层误判成功。 + /// + [Fact] + public async Task StopProgramAsync_NonZeroResultCode_ThrowsDiagnosticException() + { + using var client = new FanucCommandClient(); + var handlerTask = RunSingleResponseControllerAsync( + FanucCommandProtocol.PackProgramCommand(FanucCommandMessageIds.StopProgram, "RVBUSTSM"), + Convert.FromHexString("646f7a00000012000021030000002a7a6f64"), + _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var exception = await Assert.ThrowsAsync( + () => client.StopProgramAsync("RVBUSTSM", _cts.Token)); + + Assert.Contains("0x2103", exception.Message); + Assert.Contains("42", exception.Message); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证在连接前调用命令会抛出 InvalidOperationException。 + /// + [Fact] + public async Task SendProgramCommandAsync_BeforeConnect_Throws() + { + using var client = new FanucCommandClient(); + await Assert.ThrowsAsync( + () => client.StopProgramAsync("RVBUSTSM", _cts.Token)); + } + + /// + /// 启动模拟控制器,接收一条请求帧并比对期望内容,然后返回预设响应。 + /// + private async Task RunSingleResponseControllerAsync( + byte[] expectedFrame, + byte[] responseFrame, + CancellationToken cancellationToken) + { + using var controller = await _listener.AcceptTcpClientAsync(cancellationToken); + await using var stream = controller.GetStream(); + + var buffer = new byte[expectedFrame.Length]; + await ReadExactAsync(stream, buffer, cancellationToken); + Assert.Equal(expectedFrame, buffer); + + await stream.WriteAsync(responseFrame, cancellationToken); + } + + /// + /// 从流中精确读取指定长度的字节。 + /// + private static async Task ReadExactAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken) + { + var totalRead = 0; + while (totalRead < buffer.Length) + { + var read = await stream.ReadAsync(buffer.AsMemory(totalRead), cancellationToken); + if (read == 0) + { + throw new IOException("模拟控制器读取到 EOF。"); + } + + totalRead += read; + } + } +} diff --git a/tests/Flyshot.Core.Tests/FanucControllerRuntimeDenseTests.cs b/tests/Flyshot.Core.Tests/FanucControllerRuntimeDenseTests.cs new file mode 100644 index 0000000..02d35a0 --- /dev/null +++ b/tests/Flyshot.Core.Tests/FanucControllerRuntimeDenseTests.cs @@ -0,0 +1,1543 @@ +using Flyshot.Core.Domain; +using Flyshot.ControllerClientCompat; +using Flyshot.Core.Config; +using Flyshot.Core.Planning.Sampling; +using Flyshot.Runtime.Fanuc; +using Flyshot.Runtime.Fanuc.Protocol; +using System.Buffers.Binary; +using System.Net; +using System.Net.Sockets; +using System.Reflection; + +namespace Flyshot.Core.Tests; + +/// +/// 验证 FANUC 控制器运行在稠密轨迹流式执行与 IO 触发上的行为。 +/// +public sealed class FanucControllerRuntimeDenseTests +{ + private const double SmoothPtpVelocityShapeCoefficient = 2.1875; + private const double SmoothPtpAccelerationShapeCoefficient = 7.513188404399293; + private const double SmoothPtpJerkShapeCoefficient = 52.5; + + /// + /// 验证真机 J519 会直接消费已经按 8ms 生成好的稠密轨迹,不再按运行时 speedRatio 临场重采样。 + /// + [Fact] + public void ExecuteTrajectory_WithDenseWaypoints_RealMode_UsesPlannedDenseRowsAndConvertsRadiansToDegrees() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio: 0.5); + + var denseTrajectory = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.008, DegreesToRadians(0.08), 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.016, DegreesToRadians(0.16), 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + + var result = new TrajectoryResult( + programName: "demo", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.016), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4, + denseJointTrajectory: denseTrajectory); + + runtime.ExecuteTrajectory(result, [DegreesToRadians(0.16), 0.0, 0.0, 0.0, 0.0, 0.0]); + WaitUntilIdle(runtime); + + var commands = j519Client.GetCommandHistoryForTests(); + Assert.Equal(3, commands.Count); + Assert.All(commands, static command => Assert.Equal(0u, command.Sequence)); + var expectedJ1Targets = new[] { 0.0, 0.08, 0.16 }; + var actualJ1Targets = commands.Select(static command => command.TargetJoints[0]).ToArray(); + Assert.Equal(expectedJ1Targets.Length, actualJ1Targets.Length); + for (var index = 0; index < expectedJ1Targets.Length; index++) + { + Assert.Equal(expectedJ1Targets[index], actualJ1Targets[index], precision: 6); + } + Assert.False(j519Client.IsCommandQueueDrainedForTests()); + } + + /// + /// 验证真机稠密轨迹接口会等待 J519 队列被状态包实际取完后再返回。 + /// + [Fact] + public async Task ExecuteTrajectory_WithDenseWaypoints_RealMode_WaitsForJ519QueueToDrainBeforeReturning() + { + using var server = new UdpClient(0); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var robot = TestRobotFactory.CreateRobotProfile(); + var port = ((IPEndPoint)server.Client.LocalEndPoint!).Port; + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + j519Client.EnableCommandHistoryForTests(); + await j519Client.ConnectAsync("127.0.0.1", port, cts.Token); + var initResult = await server.ReceiveAsync(cts.Token); + j519Client.StartMotion(); + ForceRealModeEnabled(runtime, speedRatio: 1.0); + + var denseTrajectory = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.008, DegreesToRadians(0.08), 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.016, DegreesToRadians(0.16), 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + var result = new TrajectoryResult( + programName: "wait-drain", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.016), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4, + denseJointTrajectory: denseTrajectory); + + var executeTask = Task.Run(() => runtime.ExecuteTrajectory(result, [DegreesToRadians(0.16), 0.0, 0.0, 0.0, 0.0, 0.0]), cts.Token); + await WaitUntilAsync(() => j519Client.GetCommandHistoryForTests().Count == 3, cts.Token); + + // 只有机器人状态包把队列全部取出后,ExecuteTrajectory 才能向上层返回。 + Assert.False(executeTask.IsCompleted); + + for (uint sequence = 100; sequence < 103; sequence++) + { + await SendStatusPacketAsync(server, initResult.RemoteEndPoint, sequence, cts.Token); + _ = await server.ReceiveAsync(cts.Token); + } + + await executeTask.WaitAsync(TimeSpan.FromSeconds(2), cts.Token); + Assert.True(j519Client.IsCommandQueueDrainedForTests()); + Assert.False(runtime.GetSnapshot().IsInMotion); + } + + /// + /// 验证真机稠密发送会把实际下发给机器人的整条点位与点间跃度按时间目录落盘。 + /// + [Fact] + public void ExecuteTrajectory_WithDenseWaypoints_RealMode_WritesActualSentPointsAndJerkStats() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var robot = TestRobotFactory.CreateRobotProfile(); + var programName = $"dense-send-{Guid.NewGuid():N}"; + var outputRoot = Path.Combine(AppContext.BaseDirectory, "Config", "Data", programName); + + try + { + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio: 0.5); + + var denseTrajectory = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.008, DegreesToRadians(1.0), 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.016, DegreesToRadians(2.0), 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + + var result = new TrajectoryResult( + programName: programName, + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.016), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4, + denseJointTrajectory: denseTrajectory); + + runtime.ExecuteTrajectory(result, [DegreesToRadians(2.0), 0.0, 0.0, 0.0, 0.0, 0.0]); + WaitUntilIdle(runtime); + + var commands = j519Client.GetCommandHistoryForTests(); + var runDir = GetSingleDenseSendRunDirectory(outputRoot); + var pointsPath = Path.Combine(runDir, "ActualSendJointTraj.txt"); + var timingPath = Path.Combine(runDir, "ActualSendTiming.txt"); + var jerkPath = Path.Combine(runDir, "ActualSendJerkStats.txt"); + var shotEventsPath = Path.Combine(runDir, "ShotEvents.json"); + + Assert.True(File.Exists(pointsPath)); + Assert.True(File.Exists(timingPath)); + Assert.True(File.Exists(jerkPath)); + Assert.True(File.Exists(shotEventsPath)); + + var pointLines = File.ReadAllLines(pointsPath); + var timingLines = File.ReadAllLines(timingPath); + var jerkLines = File.ReadAllLines(jerkPath); + var shotEventsJson = File.ReadAllText(shotEventsPath); + Assert.Equal(commands.Count, pointLines.Length); + Assert.Equal(commands.Count, timingLines.Length); + Assert.Equal(Math.Max(0, commands.Count - 1), jerkLines.Length); + + var firstColumns = ParseColumns(pointLines[0]); + var secondColumns = ParseColumns(pointLines[1]); + var lastColumns = ParseColumns(pointLines[^1]); + Assert.Equal(9, firstColumns.Length); + Assert.Equal(9, lastColumns.Length); + Assert.Equal(0.0, firstColumns[0], precision: 6); + Assert.Equal(0.008, secondColumns[0], precision: 6); + Assert.Equal(2.0, lastColumns[1], precision: 6); + + var firstTimingColumns = ParseColumns(timingLines[0]); + var secondTimingColumns = ParseColumns(timingLines[1]); + var lastTimingColumns = ParseColumns(timingLines[^1]); + Assert.Equal(6, firstTimingColumns.Length); + Assert.Equal(0.0, firstTimingColumns[0], precision: 6); + Assert.Equal(0.0, firstTimingColumns[1], precision: 6); + Assert.Equal(0.0, firstTimingColumns[2], precision: 6); + Assert.Equal(1.0, firstTimingColumns[3], precision: 6); + Assert.Equal(0.5, firstTimingColumns[4], precision: 6); + Assert.Equal(0.0, firstTimingColumns[5], precision: 6); + Assert.Equal(1.0, secondTimingColumns[0], precision: 6); + Assert.Equal(0.008, secondTimingColumns[1], precision: 6); + Assert.Equal(0.008, secondTimingColumns[2], precision: 6); + Assert.Equal(commands.Count - 1, lastTimingColumns[0], precision: 6); + Assert.Equal(0.016, lastTimingColumns[2], precision: 6); + + var firstJerkColumns = ParseColumns(jerkLines[0]); + Assert.Equal(10, firstJerkColumns.Length); + Assert.Equal(0.0, firstJerkColumns[0], precision: 6); + Assert.Equal(0.008, firstJerkColumns[2], precision: 6); + + Assert.Equal("[]", shotEventsJson.Trim()); + } + finally + { + if (Directory.Exists(outputRoot)) + { + Directory.Delete(outputRoot, recursive: true); + } + } + } + + /// + /// 使用运行时 RobotConfig.json 中的真实 UTTC_MS11 轨迹执行一次完整 1x 稠密下发, + /// 并把 0.088s 报警窗口附近的实发时间、关节与跃度摘要落盘,便于继续对照现场报警帧。 + /// + [Fact] + public void ExecuteTrajectory_UttcMs11FromHostRuntimeConfig_RealMode_WritesDenseSendDebugWindowAtOneX() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var fixture = LoadUttcMs11RuntimeFixture(); + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var bundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: fixture.Settings); + var result = WithUniqueProgramName(bundle.Result, $"UTTC_MS11_legacyfit_{Guid.NewGuid():N}"); + var outputRoot = Path.Combine(AppContext.BaseDirectory, "Config", "Data", result.ProgramName); + var denseSendRoot = Path.Combine(outputRoot, "DenseSend"); + var beforeRunDirectories = Directory.Exists(denseSendRoot) + ? Directory.GetDirectories(denseSendRoot).ToHashSet(StringComparer.OrdinalIgnoreCase) + : new HashSet(StringComparer.OrdinalIgnoreCase); + + runtime.ResetRobot(fixture.Robot, fixture.Robot.Name); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio: 1.0); + + runtime.ExecuteTrajectory(result, result.DenseJointTrajectory![0].Skip(1).ToArray()); + WaitUntilIdle(runtime); + + var runDirectory = GetNewDenseSendRunDirectory(outputRoot, beforeRunDirectories); + var pointsPath = Path.Combine(runDirectory, "ActualSendJointTraj.txt"); + var timingPath = Path.Combine(runDirectory, "ActualSendTiming.txt"); + var jerkPath = Path.Combine(runDirectory, "ActualSendJerkStats.txt"); + var summaryPath = Path.Combine(runDirectory, "AlarmWindow_0p088s_Summary.txt"); + + Assert.True(File.Exists(pointsPath)); + Assert.True(File.Exists(timingPath)); + Assert.True(File.Exists(jerkPath)); + + var pointsLines = File.ReadAllLines(pointsPath); + var timingLines = File.ReadAllLines(timingPath); + var jerkLines = File.ReadAllLines(jerkPath); + + Assert.NotEmpty(pointsLines); + Assert.NotEmpty(timingLines); + Assert.NotEmpty(jerkLines); + Assert.Equal(result.DenseJointTrajectory!.Count, pointsLines.Length); + Assert.Equal(result.DenseJointTrajectory.Count, timingLines.Length); + + var firstPoint = ParseColumns(pointsLines[0]); + var secondPoint = ParseColumns(pointsLines[1]); + Assert.Equal(0.0, firstPoint[0], precision: 6); + Assert.Equal(0.008, secondPoint[0], precision: 6); + + var firstStepJ1 = Math.Abs(secondPoint[1] - firstPoint[1]); + Assert.True(firstStepJ1 > 1e-6, $"UTTC_MS11 实发首步不应被压成 0,actual={firstStepJ1:F9}deg"); + + const double targetSendTime = 0.088; + const double windowHalfWidth = 0.024; + var summaryLines = new List + { + $"program={result.ProgramName}", + $"send_time_target_seconds={targetSendTime:F6}", + $"window_half_width_seconds={windowHalfWidth:F6}", + $"points_path={pointsPath}", + $"timing_path={timingPath}", + $"jerk_path={jerkPath}", + "timing_window:" + }; + + foreach (var line in timingLines.Select(ParseColumns)) + { + var sendTime = line[1]; + if (Math.Abs(sendTime - targetSendTime) <= windowHalfWidth + 1e-9) + { + summaryLines.Add( + $"timing send_index={line[0]:F0} send_time={line[1]:F6} trajectory_time={line[2]:F6} speed_ratio={line[3]:F6}"); + } + } + + summaryLines.Add("jerk_window:"); + foreach (var line in jerkLines.Select(ParseColumns)) + { + var endTime = line[1]; + if (Math.Abs(endTime - targetSendTime) <= windowHalfWidth + 1e-9) + { + summaryLines.Add( + $"jerk end_time={line[1]:F6} dt={line[2]:F6} j1={line[3]:F6} j2={line[4]:F6} j3={line[5]:F6} j4={line[6]:F6} j5={line[7]:F6} j6={line[8]:F6} max_abs={line.Skip(3).Max(static value => Math.Abs(value)):F6}"); + } + } + + File.WriteAllLines(summaryPath, summaryLines); + Assert.True(File.Exists(summaryPath)); + } + + /// + /// 验证真实 UTTC_MS11 轨迹在非 1 倍 speedRatio 下仍能生成并装载 J519 实发队列。 + /// + [Theory] + [InlineData(0.7)] + [InlineData(0.5)] + public void ExecuteTrajectory_UttcMs11FromHostRuntimeConfig_RealMode_AllowsNonOneSpeedRatio(double speedRatio) + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var fixture = LoadUttcMs11RuntimeFixture(); + var fullSpeedSettings = new CompatibilityRobotSettings( + useDo: fixture.Settings.UseDo, + ioAddresses: fixture.Settings.IoAddresses, + ioKeepCycles: fixture.Settings.IoKeepCycles, + triggerSampleIndexOffsetCycles: fixture.Settings.TriggerSampleIndexOffsetCycles, + accLimitScale: fixture.Settings.AccLimitScale, + jerkLimitScale: fixture.Settings.JerkLimitScale, + adaptIcspTryNum: fixture.Settings.AdaptIcspTryNum, + planningSpeedScale: 1.0, + smoothStartStopTiming: true); + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var bundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: fullSpeedSettings, + planningSpeedScale: fullSpeedSettings.PlanningSpeedScale); + var preparedExecution = FlyshotExecutionSendSequenceBuilder.Build( + fixture.Robot, + bundle.ExecutionTrajectory, + bundle.Result, + fixture.Robot.ServoPeriod.TotalSeconds, + speedRatio); + var result = WithUniqueProgramName(new TrajectoryResult( + programName: bundle.Result.ProgramName, + method: bundle.Result.Method, + isValid: bundle.Result.IsValid, + duration: bundle.Result.Duration, + shotEvents: bundle.Result.ShotEvents, + triggerTimeline: bundle.Result.TriggerTimeline, + artifacts: bundle.Result.Artifacts, + failureReason: bundle.Result.FailureReason, + usedCache: bundle.Result.UsedCache, + originalWaypointCount: bundle.Result.OriginalWaypointCount, + plannedWaypointCount: bundle.Result.PlannedWaypointCount, + triggerSampleIndexOffsetCycles: bundle.Result.TriggerSampleIndexOffsetCycles, + denseJointTrajectory: bundle.Result.DenseJointTrajectory, + preparedFlyshotExecution: preparedExecution), $"UTTC_MS11_speedratio_{speedRatio:F1}_{Guid.NewGuid():N}"); + var outputRoot = Path.Combine(AppContext.BaseDirectory, "Config", "Data", result.ProgramName); + + try + { + runtime.ResetRobot(fixture.Robot, fixture.Robot.Name); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio); + + runtime.ExecuteTrajectory(result, result.DenseJointTrajectory![^1].Skip(1).ToArray()); + WaitUntilIdle(runtime); + + var commands = j519Client.GetCommandHistoryForTests(); + Assert.NotEmpty(commands); + Assert.Equal(preparedExecution.Samples.Count, commands.Count); + Assert.Equal(speedRatio, preparedExecution.FinalSpeedRatio, precision: 6); + Assert.Equal(0, preparedExecution.StretchIterationCount); + Assert.Equal(preparedExecution.Samples[0].JointsDegrees, commands[0].TargetJoints); + } + finally + { + if (Directory.Exists(outputRoot)) + { + Directory.Delete(outputRoot, recursive: true); + } + } + } + + /// + /// 验证运行时拿到飞拍预生成发送队列后,会直接消费该队列,而不是再按当前 speedRatio 临场重采样。 + /// + [Fact] + public void ExecuteTrajectory_WithPreparedFlyshotExecution_RealMode_UsesPreparedSamplesDirectly() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio: 0.5); + + var preparedSamples = new[] + { + new FlyshotPreparedSample(0, 0.0, 0.0, 1.0, [1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + new FlyshotPreparedSample(1, 0.008, 0.008, 1.0, [2.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + new FlyshotPreparedSample(2, 0.016, 0.016, 1.0, [3.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + }; + var preparedExecution = new FlyshotPreparedExecution( + samples: preparedSamples, + triggerBindings: Array.Empty(), + timingRows: preparedSamples.Select(static sample => (IReadOnlyList) + [ + sample.SampleIndex, + Math.Round(sample.SendTime, 6), + Math.Round(sample.TrajectoryTime, 6), + Math.Round(sample.SpeedRatio, 6) + ]).ToArray(), + jerkRows: Array.Empty>(), + requestSpeedRatio: 1.0, + finalSpeedRatio: 1.0, + finalDurationSeconds: 0.016, + stretchIterationCount: 0); + var result = new TrajectoryResult( + programName: "prepared-demo", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.016), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4, + denseJointTrajectory: + [ + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.008, Math.PI / 2.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.016, Math.PI, 0.0, 0.0, 0.0, 0.0, 0.0 } + ], + preparedFlyshotExecution: preparedExecution); + + runtime.ExecuteTrajectory(result, [DegreesToRadians(3.0), 0.0, 0.0, 0.0, 0.0, 0.0]); + WaitUntilIdle(runtime); + + var commands = j519Client.GetCommandHistoryForTests(); + Assert.Equal(preparedSamples.Length, commands.Count); + Assert.Equal([1.0, 2.0, 3.0], commands.Select(static command => command.TargetJoints[0])); + } + + /// + /// 验证 MoveJoint 会在生成阶段折算 speed_ratio,运行时只发送已经规划好的 8ms 稠密点列。 + /// + [Theory] + [InlineData(1.0)] + [InlineData(0.7)] + [InlineData(0.5)] + public void MoveJoint_RealMode_GeneratesTemporaryPtpTrajectoryWithSpeedRatioAlreadyPlanned(double speedRatio) + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var service = CreateCompatService(runtime); + var startJoints = new[] + { + 1.056731, + 0.011664811, + -0.017892333, + -0.01516874, + 0.021492079, + 0.009567846 + }; + var targetJoints = new[] + { + 0.8532358, + 0.03837953, + -0.19235304, + 0.0071595116, + 0.109054826, + 0.040055145 + }; + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio); + SetPrivateField(runtime, "_jointPositions", startJoints); + + var options = new ControllerClientCompatOptions + { + ConfigRoot = TestRobotFactory.GetConfigRoot() + }; + var robot = new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()) + .LoadProfile("FANUC_LR_Mate_200iD", accLimitScale: 1.0, jerkLimitScale: 1.0); + var expectedResult = MoveJointTrajectoryGenerator.CreateResult(robot, startJoints, targetJoints, speedRatio); + + service.MoveJoint(targetJoints); + WaitUntilIdle(runtime); + + var commands = j519Client.GetCommandHistoryForTests(); + Assert.Equal(expectedResult.DenseJointTrajectory!.Count, commands.Count); + AssertJointDegreesEqual(startJoints, commands[0].TargetJoints); + AssertJointDegreesEqual(targetJoints, commands[^1].TargetJoints); + + var middleAlpha = ComputeLineAlpha(commands[commands.Count / 2].TargetJoints, startJoints, targetJoints); + Assert.InRange(middleAlpha, 0.45, 0.55); + + var earlyAlpha = ComputeLineAlpha(commands[Math.Min(5, commands.Count - 1)].TargetJoints, startJoints, targetJoints); + Assert.InRange(earlyAlpha, 0.0, 0.02); + } + + [Fact] + public void MoveJointTrajectoryGenerator_LowerSpeedUsesFixedServoPeriodAndLongerPlannedDuration() + { + var robot = CreateMoveJointReferenceRobotProfile(); + var startJoints = new[] { 1.056731, 0.011664811, -0.017892333, -0.01516874, 0.021492079, 0.009567846 }; + var targetJoints = new[] { 0.8532358, 0.03837953, -0.19235304, 0.0071595116, 0.109054826, 0.040055145 }; + + var fullSpeed = MoveJointTrajectoryGenerator.CreateResult(robot, startJoints, targetJoints, speedRatio: 1.0); + var speed07 = MoveJointTrajectoryGenerator.CreateResult(robot, startJoints, targetJoints, speedRatio: 0.7); + var speed05 = MoveJointTrajectoryGenerator.CreateResult(robot, startJoints, targetJoints, speedRatio: 0.5); + + Assert.True(speed07.DenseJointTrajectory!.Count > fullSpeed.DenseJointTrajectory!.Count); + Assert.True(speed05.DenseJointTrajectory!.Count > speed07.DenseJointTrajectory!.Count); + AssertDenseRowsUseServoPeriod(fullSpeed.DenseJointTrajectory, robot.ServoPeriod.TotalSeconds); + AssertDenseRowsUseServoPeriod(speed07.DenseJointTrajectory, robot.ServoPeriod.TotalSeconds); + AssertDenseRowsUseServoPeriod(speed05.DenseJointTrajectory, robot.ServoPeriod.TotalSeconds); + Assert.True(fullSpeed.Duration.TotalSeconds >= ExpectedSmoothPtpDuration(robot, startJoints, targetJoints, speedRatio: 1.0)); + Assert.True(speed07.Duration.TotalSeconds >= ExpectedSmoothPtpDuration(robot, startJoints, targetJoints, speedRatio: 0.7)); + Assert.True(speed05.Duration.TotalSeconds >= ExpectedSmoothPtpDuration(robot, startJoints, targetJoints, speedRatio: 0.5)); + } + + /// + /// 验证 MovePose 低速倍率仍保持固定伺服周期,并通过拉长时长降低直角运动速度。 + /// + [Fact] + public void MovePoseTrajectoryGenerator_LowerSpeedUsesFixedServoPeriodAndLongerPlannedDuration() + { + var servoPeriod = TimeSpan.FromMilliseconds(8); + var startPose = new[] { 100.0, 200.0, 300.0, 1.0, 2.0, 3.0 }; + var targetPose = new[] { 140.0, 260.0, 330.0, 8.0, 10.0, 12.0 }; + + var fullSpeed = MovePoseTrajectoryGenerator.CreateResult(startPose, targetPose, servoPeriod, speedRatio: 1.0); + var speed07 = MovePoseTrajectoryGenerator.CreateResult(startPose, targetPose, servoPeriod, speedRatio: 0.7); + var speed05 = MovePoseTrajectoryGenerator.CreateResult(startPose, targetPose, servoPeriod, speedRatio: 0.5); + + Assert.True(speed07.DenseCartesianTrajectory!.Count > fullSpeed.DenseCartesianTrajectory!.Count); + Assert.True(speed05.DenseCartesianTrajectory!.Count > speed07.DenseCartesianTrajectory!.Count); + AssertDenseRowsUseServoPeriod(fullSpeed.DenseCartesianTrajectory, servoPeriod.TotalSeconds); + AssertDenseRowsUseServoPeriod(speed07.DenseCartesianTrajectory, servoPeriod.TotalSeconds); + AssertDenseRowsUseServoPeriod(speed05.DenseCartesianTrajectory, servoPeriod.TotalSeconds); + AssertPoseEqual(startPose, fullSpeed.DenseCartesianTrajectory[0].Skip(1).ToArray()); + AssertPoseEqual(targetPose, fullSpeed.DenseCartesianTrajectory[^1].Skip(1).ToArray()); + Assert.True(MovePoseTrajectoryGenerator.SatisfiesDefaultCartesianLimits(speed05.DenseCartesianTrajectory, speedRatio: 0.5)); + } + + [Fact] + public void MoveJoint_RealMode_LeavesFinalTargetForHoldStreaming() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var service = CreateCompatService(runtime); + var startJoints = new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + var targetJoints = new[] { 0.2, -0.1, 0.05, 0.0, 0.0, 0.1 }; + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + ForceRealModeEnabled(runtime, speedRatio: 1.0); + SetPrivateField(runtime, "_jointPositions", startJoints); + + service.MoveJoint(targetJoints); + WaitUntilIdle(runtime); + + var currentCommand = j519Client.GetCurrentCommand(); + Assert.NotNull(currentCommand); + AssertJointDegreesEqual(targetJoints, currentCommand.TargetJoints); + } + + /// + /// 验证 MovePose 会生成直角坐标 J519 队列,并使用 Data format=0 下发 X/Y/Z/W/P/R。 + /// + [Fact] + public void MovePose_RealMode_GeneratesCartesianJ519Queue() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var service = CreateCompatService(runtime); + var startPose = new[] { 100.0, 200.0, 300.0, 1.0, 2.0, 3.0 }; + var targetPose = new[] { 110.0, 220.0, 315.0, 4.0, 5.0, 6.0 }; + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio: 1.0); + SetPrivateField(runtime, "_pose", startPose); + + service.MovePose(targetPose); + WaitUntilIdle(runtime); + + var commands = j519Client.GetCommandHistoryForTests(); + Assert.NotEmpty(commands); + Assert.All(commands, static command => Assert.Equal(0, command.DataStyle)); + AssertPoseEqual(startPose, commands[0].TargetValues.Take(6).ToArray()); + AssertPoseEqual(targetPose, commands[^1].TargetValues.Take(6).ToArray()); + Assert.All(commands, static command => + { + Assert.Equal(0.0, command.TargetValues[6], precision: 6); + Assert.Equal(0.0, command.TargetValues[7], precision: 6); + Assert.Equal(0.0, command.TargetValues[8], precision: 6); + }); + } + + /// + /// 验证运行时稠密发送不再依赖当前 speed_ratio;倍率合法性应在上游规划/生成阶段处理。 + /// + [Fact] + public void ExecuteTrajectory_WithDenseWaypoints_RealMode_IgnoresRuntimeSpeedRatioForDenseFallback() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio: 0.0); + + var denseTrajectory = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.008, DegreesToRadians(0.08), 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.016, DegreesToRadians(0.16), 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + + var result = new TrajectoryResult( + programName: "demo", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.016), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4, + denseJointTrajectory: denseTrajectory); + + runtime.ExecuteTrajectory(result, [DegreesToRadians(0.16), 0.0, 0.0, 0.0, 0.0, 0.0]); + WaitUntilIdle(runtime); + + var commands = j519Client.GetCommandHistoryForTests(); + Assert.Equal(3, commands.Count); + Assert.Equal(0.16, commands[^1].TargetJoints[0], precision: 6); + } + + /// + /// 验证真机模式下若 J519 响应明确显示伺服侧未就绪,则拒绝启动稠密轨迹发送。 + /// + [Fact] + public void ExecuteTrajectory_WithDenseWaypoints_RealMode_RejectsNotReadyJ519Status() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + ForceRealModeEnabled(runtime, speedRatio: 1.0); + SetLatestJ519Response(j519Client, status: 0b0011); + + var result = CreateDenseResult( + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.008, Math.PI / 2.0, 0.0, 0.0, 0.0, 0.0, 0.0] + ], + durationSeconds: 0.008); + + var exception = Assert.Throws( + () => runtime.ExecuteTrajectory(result, [Math.PI / 2.0, 0.0, 0.0, 0.0, 0.0, 0.0])); + + Assert.Contains("J519 status is not ready", exception.Message); + Assert.Contains("sysrdy=False", exception.Message); + } + + /// + /// 验证控制器快照暴露最近一次 J519 响应中的四个状态位,便于状态页和诊断接口显示。 + /// + [Fact] + public void GetSnapshot_RealMode_IncludesLatestJ519StatusBits() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + ForceRealModeEnabled(runtime, speedRatio: 1.0); + SetLatestJ519Response(j519Client, status: 0b0111); + + var snapshot = runtime.GetSnapshot(); + + Assert.Equal((byte)0b0111, snapshot.J519Status); + Assert.Equal(10u, snapshot.J519Sequence); + Assert.True(snapshot.J519AcceptsCommand); + Assert.True(snapshot.J519ReceivedCommand); + Assert.True(snapshot.J519SystemReady); + Assert.False(snapshot.J519RobotInMotion); + } + + /// + /// 验证飞拍 IO 脉冲按轨迹时间轴嵌入 J519 命令,并在保持周期后用同一 mask 清零。 + /// + [Fact] + public void ExecuteTrajectory_WithDenseWaypoints_RealMode_EmbedsIoPulseOnTrajectoryTimeline() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio: 1.0); + + var denseTrajectory = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.024, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + + var result = new TrajectoryResult( + programName: "demo", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.024), + shotEvents: Array.Empty(), + triggerTimeline: + [ + new TrajectoryDoEvent( + waypointIndex: 1, + triggerTime: 0.008, + offsetCycles: 0, + holdCycles: 2, + addressGroup: new IoAddressGroup([1, 3])) + ], + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4, + denseJointTrajectory: denseTrajectory); + + runtime.ExecuteTrajectory(result, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + WaitUntilIdle(runtime); + + var commands = j519Client.GetCommandHistoryForTests(); + Assert.Equal(4, commands.Count); + Assert.Equal([(ushort)0, (ushort)5, (ushort)5, (ushort)5], commands.Select(static command => command.WriteIoMask)); + Assert.Equal([(ushort)0, (ushort)5, (ushort)5, (ushort)0], commands.Select(static command => command.WriteIoValue)); + } + + /// + /// 验证同一时间窗口内存在多个候选 sample 时,会优先把 IO 挂到关节坐标最接近示教点的那一帧。 + /// + [Fact] + public void ExecuteTrajectory_WithDenseWaypoints_RealMode_PrefersClosestJointSampleWithinTriggerWindow() + { + using var commandClient = new FanucCommandClient(); + using var stateClient = new FanucStateClient(); + using var j519Client = new FanucJ519Client(); + using var runtime = new FanucControllerRuntime(commandClient, stateClient, j519Client); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + j519Client.EnableCommandHistoryForTests(); + ForceRealModeEnabled(runtime, speedRatio: 1.0); + + var denseTrajectory = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.008, DegreesToRadians(1.0), 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.016, DegreesToRadians(2.0), 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.024, DegreesToRadians(3.0), 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + + var result = new TrajectoryResult( + programName: "demo", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.024), + shotEvents: Array.Empty(), + triggerTimeline: + [ + new TrajectoryDoEvent( + waypointIndex: 1, + triggerTime: 0.012, + offsetCycles: 0, + holdCycles: 1, + addressGroup: new IoAddressGroup([1]), + referenceJointsDegrees: [2.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + ], + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4, + denseJointTrajectory: denseTrajectory); + + runtime.ExecuteTrajectory(result, [DegreesToRadians(3.0), 0.0, 0.0, 0.0, 0.0, 0.0]); + WaitUntilIdle(runtime); + + var commands = j519Client.GetCommandHistoryForTests(); + Assert.Equal(4, commands.Count); + Assert.Equal([(ushort)0, (ushort)0, (ushort)1, (ushort)1], commands.Select(static command => command.WriteIoMask)); + Assert.Equal([(ushort)0, (ushort)0, (ushort)1, (ushort)0], commands.Select(static command => command.WriteIoValue)); + } + + /// + /// 验证仿真模式下即使传入稠密路点,也会回退到单点同步更新。 + /// + [Fact] + public void ExecuteTrajectory_WithDenseWaypoints_SimulationMode_FallsBackToSinglePoint() + { + var runtime = new FanucControllerRuntime(); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + runtime.SetActiveController(sim: true); + runtime.Connect("192.168.10.101"); + runtime.EnableRobot(bufferSize: 2); + + var denseTrajectory = new[] + { + new[] { 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6 }, + new[] { 0.008, 0.11, 0.21, 0.31, 0.41, 0.51, 0.61 }, + new[] { 0.016, 0.12, 0.22, 0.32, 0.42, 0.52, 0.62 } + }; + + var result = new TrajectoryResult( + programName: "demo", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.016), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4, + denseJointTrajectory: denseTrajectory); + + runtime.ExecuteTrajectory(result, [0.12, 0.22, 0.32, 0.42, 0.52, 0.62]); + + var snapshot = runtime.GetSnapshot(); + Assert.False(snapshot.IsInMotion); + Assert.Equal([0.12, 0.22, 0.32, 0.42, 0.52, 0.62], snapshot.JointPositions); + } + + /// + /// 验证稠密执行前若 J519 首次未就绪,会先尝试一次 EnableRobot,再在 500ms 后复查状态。 + /// + [Fact] + public void EnsureJ519ReadyForDenseExecutionCore_RetriesEnableRobotOnceBeforePassing() + { + var responses = new Queue( + [ + CreateJ519Response(status: 0b0001, sequence: 11), + CreateJ519Response(status: 0b0101, sequence: 12) + ]); + var enableRobotRetryCount = 0; + var waitCount = 0; + + using var runtime = new FanucControllerRuntime(); + + var exception = Record.Exception( + () => runtime.EnsureJ519ReadyForDenseExecutionCore( + () => responses.Dequeue(), + () => enableRobotRetryCount++, + () => waitCount++)); + + Assert.Null(exception); + Assert.Equal(1, enableRobotRetryCount); + Assert.Equal(1, waitCount); + } + + /// + /// 验证重试 EnableRobot 之后若 J519 仍未就绪,会继续抛出带状态位的异常。 + /// + [Fact] + public void EnsureJ519ReadyForDenseExecutionCore_ThrowsWhenStillNotReadyAfterRetry() + { + var responses = new Queue( + [ + CreateJ519Response(status: 0b0000, sequence: 21), + CreateJ519Response(status: 0b0010, sequence: 22) + ]); + var enableRobotRetryCount = 0; + var waitCount = 0; + + using var runtime = new FanucControllerRuntime(); + + var exception = Assert.Throws( + () => runtime.EnsureJ519ReadyForDenseExecutionCore( + () => responses.Dequeue(), + () => enableRobotRetryCount++, + () => waitCount++)); + + Assert.Equal(1, enableRobotRetryCount); + Assert.Equal(1, waitCount); + Assert.Contains("accept_cmd=False", exception.Message, StringComparison.Ordinal); + Assert.Contains("sysrdy=False", exception.Message, StringComparison.Ordinal); + Assert.Contains("seq=22", exception.Message, StringComparison.Ordinal); + } + + /// + /// 验证 StopMove 在没有任何后台发送任务运行时不会抛出异常。 + /// + [Fact] + public void StopMove_DoesNotThrowWhenNoSendTaskRunning() + { + var runtime = new FanucControllerRuntime(); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + runtime.SetActiveController(sim: true); + runtime.Connect("192.168.10.101"); + runtime.EnableRobot(bufferSize: 2); + + var exception = Record.Exception(() => runtime.StopMove()); + Assert.Null(exception); + Assert.False(runtime.GetSnapshot().IsInMotion); + } + + /// + /// 验证 IO 地址组中的地址号按 writeIoIndex=1 的手册语义映射为 writeIoValue 位掩码。 + /// + [Theory] + [InlineData(new[] { 1 }, (ushort)1)] + [InlineData(new[] { 2, 4 }, (ushort)10)] + [InlineData(new[] { 3, 4 }, (ushort)12)] + [InlineData(new[] { 2, 3, 4 }, (ushort)14)] + [InlineData(new[] { 8 }, (ushort)128)] + [InlineData(new[] { 8, 9 }, (ushort)384)] // 128 + 256 + [InlineData(new[] { 16 }, (ushort)32768)] + [InlineData(new int[] { }, (ushort)0)] + public void ComputeIoValue_MapsAddressesToBitMask(int[] addresses, ushort expected) + { + var group = new IoAddressGroup(addresses); + var actual = FanucControllerRuntime.ComputeIoValue(group); + Assert.Equal(expected, actual); + } + + /// + /// 验证超出 1..16 范围的地址号会被安全忽略,不会污染位掩码。 + /// + [Fact] + public void ComputeIoValue_IgnoresOutOfRangeAddresses() + { + var group = new IoAddressGroup([0, 1, 17, 8]); + var actual = FanucControllerRuntime.ComputeIoValue(group); + Assert.Equal((ushort)(1 | 128), actual); + } + + [Fact] + public void MoveJointTrajectoryGenerator_GeneratesSmoothJerkLimitedPtpTrajectoryAtSpeedOne() + { + var robot = CreateMoveJointReferenceRobotProfile(); + var startJoints = new[] + { + DegreesToRadians(60.546227), + DegreesToRadians(0.668344), + DegreesToRadians(-1.025155), + DegreesToRadians(-0.869105), + DegreesToRadians(1.231405), + DegreesToRadians(0.548197) + }; + var targetJoints = new[] + { + DegreesToRadians(48.886810), + DegreesToRadians(2.198985), + DegreesToRadians(-11.021017), + DegreesToRadians(0.410210), + DegreesToRadians(6.248381), + DegreesToRadians(2.294991) + }; + + var result = MoveJointTrajectoryGenerator.CreateResult(robot, startJoints, targetJoints, speedRatio: 1.0); + var rows = result.DenseJointTrajectory!; + + Assert.True(rows.Count > 41, $"平滑 MoveJoint 不应再固定输出 41 点,actual={rows.Count}。"); + Assert.True(result.Duration.TotalSeconds >= ExpectedSmoothPtpDuration(robot, startJoints, targetJoints)); + AssertJointDegreesEqual(startJoints, rows[0].Skip(1).Select(RadiansToDegrees).ToArray()); + AssertJointDegreesEqual(targetJoints, rows[^1].Skip(1).Select(RadiansToDegrees).ToArray()); + AssertAllJointJerksWithinLimits(rows, robot); + } + + /// + /// 验证报警样本中的 MoveJoint 预移动作不会因为点到点时间律重采样而产生任一轴跃度尖峰。 + /// + [Fact] + public void MoveJointTrajectoryGenerator_ActualAlarmSampleKeepsJointOneJerkWithinLimit() + { + var robot = CreateMoveJointRuntimeLimitRobotProfile(); + var startJoints = new[] + { + DegreesToRadians(71.454618), + DegreesToRadians(0.688433), + DegreesToRadians(-1.074197), + DegreesToRadians(-0.869001), + DegreesToRadians(1.218057), + DegreesToRadians(0.547036) + }; + var targetJoints = new[] + { + DegreesToRadians(60.546227), + DegreesToRadians(0.668344), + DegreesToRadians(-1.025155), + DegreesToRadians(-0.869105), + DegreesToRadians(1.231405), + DegreesToRadians(0.548197) + }; + + var result = MoveJointTrajectoryGenerator.CreateResult(robot, startJoints, targetJoints, speedRatio: 1.0); + var rows = result.DenseJointTrajectory!; + AssertAllJointJerksWithinLimits(rows, robot); + } + + [Fact] + public void MoveJointTrajectoryGenerator_DoesNotShortenLimitDurationWhenSpeedRatioDoesNotDivideWindow() + { + var robot = CreateMoveJointReferenceRobotProfile(); + var startJoints = new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + var targetJoints = new[] { 0.05, 0.0, 0.0, 0.0, 0.0, 0.0 }; + + var result = MoveJointTrajectoryGenerator.CreateResult(robot, startJoints, targetJoints, speedRatio: 0.7); + var rows = result.DenseJointTrajectory!; + + Assert.True( + result.Duration.TotalSeconds >= ExpectedSmoothPtpDuration(robot, startJoints, targetJoints, speedRatio: 0.7), + $"Duration was shortened to {result.Duration.TotalSeconds:F6}s."); + AssertDenseRowsUseServoPeriod(rows, robot.ServoPeriod.TotalSeconds); + AssertJointDegreesEqual(startJoints, rows[0].Skip(1).Select(RadiansToDegrees).ToArray()); + AssertJointDegreesEqual(targetJoints, rows[^1].Skip(1).Select(RadiansToDegrees).ToArray()); + } + + [Fact] + public void MoveJointTrajectoryGenerator_RejectsUnrepresentableSampleCountForTinySpeedRatio() + { + var robot = CreateMoveJointReferenceRobotProfile(); + var startJoints = new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + var targetJoints = new[] { 0.05, 0.0, 0.0, 0.0, 0.0, 0.0 }; + + var exception = Assert.Throws( + () => MoveJointTrajectoryGenerator.CreateResult(robot, startJoints, targetJoints, speedRatio: 1e-12)); + + Assert.Contains("sample count", exception.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void MoveJointTrajectoryGenerator_StretchesLongMoveFromJointLimitsInsteadOfKeepingFortyCycles() + { + var robot = CreateMoveJointReferenceRobotProfile(); + var startJoints = new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + var targetJoints = new[] { Math.PI, 0.0, 0.0, 0.0, 0.0, 0.0 }; + + var result = MoveJointTrajectoryGenerator.CreateResult(robot, startJoints, targetJoints, speedRatio: 1.0); + var rows = result.DenseJointTrajectory!; + var expectedVelocityDuration = Math.PI * SmoothPtpVelocityShapeCoefficient / robot.JointLimits[0].VelocityLimit; + var expectedAccelerationDuration = Math.Sqrt(Math.PI * SmoothPtpAccelerationShapeCoefficient / robot.JointLimits[0].AccelerationLimit); + var expectedJerkDuration = Math.Cbrt(Math.PI * SmoothPtpJerkShapeCoefficient / robot.JointLimits[0].JerkLimit); + var expectedMinimumDuration = new[] + { + expectedVelocityDuration, + expectedAccelerationDuration, + expectedJerkDuration + }.Max(); + var expectedCountFromDuration = (int)Math.Floor(result.Duration.TotalSeconds / robot.ServoPeriod.TotalSeconds + 1e-9) + 1; + + Assert.True(rows.Count > 41, $"Expected long MoveJoint to produce more than 41 points, got {rows.Count}."); + Assert.True( + result.Duration.TotalSeconds >= expectedMinimumDuration, + $"Expected duration >= {expectedMinimumDuration:F6}s from joint limits, got {result.Duration.TotalSeconds:F6}s."); + Assert.Equal(expectedCountFromDuration, rows.Count); + AssertJointDegreesEqual(startJoints, rows[0].Skip(1).Select(RadiansToDegrees).ToArray()); + AssertJointDegreesEqual(targetJoints, rows[^1].Skip(1).Select(RadiansToDegrees).ToArray()); + } + + private static void ForceRealModeEnabled(FanucControllerRuntime runtime, double speedRatio) + { + SetPrivateField(runtime, "_activeControllerIsSimulation", false); + SetPrivateField(runtime, "_connectedRobotIp", "127.0.0.1"); + SetPrivateField(runtime, "_isEnabled", true); + SetPrivateField(runtime, "_bufferSize", 2); + SetPrivateField(runtime, "_speedRatio", speedRatio); + } + + private static ControllerClientCompatService CreateCompatService(FanucControllerRuntime runtime) + { + var options = new ControllerClientCompatOptions + { + ConfigRoot = TestRobotFactory.GetConfigRoot() + }; + + return new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + } + + private static double ComputeLineAlpha( + IReadOnlyList actualDegrees, + IReadOnlyList startRadians, + IReadOnlyList targetRadians) + { + var numerator = 0.0; + var denominator = 0.0; + for (var index = 0; index < startRadians.Count; index++) + { + var startDegrees = RadiansToDegrees(startRadians[index]); + var deltaDegrees = RadiansToDegrees(targetRadians[index]) - startDegrees; + numerator += (actualDegrees[index] - startDegrees) * deltaDegrees; + denominator += deltaDegrees * deltaDegrees; + } + + return denominator <= 0.0 ? 0.0 : numerator / denominator; + } + + private static void AssertJointDegreesEqual(IReadOnlyList expectedRadians, IReadOnlyList actualDegrees) + { + Assert.Equal(expectedRadians.Count, actualDegrees.Count); + for (var index = 0; index < expectedRadians.Count; index++) + { + Assert.Equal(RadiansToDegrees(expectedRadians[index]), actualDegrees[index], precision: 4); + } + } + + private static void AssertPoseEqual(IReadOnlyList expected, IReadOnlyList actual) + { + Assert.Equal(expected.Count, actual.Count); + for (var index = 0; index < expected.Count; index++) + { + Assert.Equal(expected[index], actual[index], precision: 6); + } + } + + /// + /// 创建用于就绪状态测试的最小 J519 响应。 + /// + /// 待注入的 J519 状态位。 + /// 待注入的响应序号。 + /// 包含最小字段集合的测试响应。 + private static FanucJ519Response CreateJ519Response(byte status, uint sequence) + { + return new FanucJ519Response( + messageType: 0, + version: 1, + sequence: sequence, + status: status, + readIoType: 0, + readIoIndex: 0, + readIoMask: 0, + readIoValue: 0, + timestamp: 0, + pose: new double[6], + externalAxes: new double[3], + jointDegrees: new double[6], + motorCurrents: new double[6]); + } + + private static RobotProfile CreateMoveJointReferenceRobotProfile() + { + return new RobotProfile( + name: "FANUC_LR_Mate_200iD", + modelPath: "Models/FANUC_LR_Mate_200iD.robot", + degreesOfFreedom: 6, + jointLimits: + [ + new JointLimit("J1", 7.85, 32.72, 272.7), + new JointLimit("J2", 6.63, 27.63, 230.28), + new JointLimit("J3", 9.07, 37.81, 315.12), + new JointLimit("J4", 9.59, 39.99, 333.3), + new JointLimit("J5", 9.51, 39.63, 330.27), + new JointLimit("J6", 17.45, 72.72, 606.01) + ], + jointCouplings: Array.Empty(), + servoPeriod: TimeSpan.FromMilliseconds(8), + triggerPeriod: TimeSpan.FromMilliseconds(8)); + } + + /// + /// 创建当前运行配置下的 MoveJoint 限值模型,覆盖报警样本使用的 acc_limit/jerk_limit=0.74。 + /// + private static RobotProfile CreateMoveJointRuntimeLimitRobotProfile() + { + const double limitScale = 0.74; + return new RobotProfile( + name: "FANUC_LR_Mate_200iD", + modelPath: "Models/FANUC_LR_Mate_200iD.robot", + degreesOfFreedom: 6, + jointLimits: + [ + new JointLimit("J1", 6.45, 26.90 * limitScale, 224.22 * limitScale), + new JointLimit("J2", 5.41, 22.54 * limitScale, 187.86 * limitScale), + new JointLimit("J3", 7.15, 29.81 * limitScale, 248.46 * limitScale), + new JointLimit("J4", 9.59, 39.99 * limitScale, 333.30 * limitScale), + new JointLimit("J5", 9.51, 39.63 * limitScale, 330.27 * limitScale), + new JointLimit("J6", 17.45, 72.72 * limitScale, 606.01 * limitScale) + ], + jointCouplings: Array.Empty(), + servoPeriod: TimeSpan.FromMilliseconds(8), + triggerPeriod: TimeSpan.FromMilliseconds(8)); + } + + /// + /// 以发送周期上的二阶后向差分估算单轴最大绝对跃度,用来复现 ActualSendJerkStats 的判定口径。 + /// + private static double MaxAbsJointJerk(IReadOnlyList> rows, int jointIndex) + { + double? previousTime = null; + double? previousPosition = null; + double? previousVelocity = null; + double? previousAcceleration = null; + var maxAbsJerk = 0.0; + + foreach (var row in rows) + { + var currentTime = row[0]; + var currentPosition = row[jointIndex + 1]; + if (previousTime is not null && previousPosition is not null) + { + var dt = currentTime - previousTime.Value; + Assert.True(dt > 0.0, "MoveJoint 稠密轨迹时间戳必须严格递增。"); + + var velocity = (currentPosition - previousPosition.Value) / dt; + var acceleration = previousVelocity is null ? 0.0 : (velocity - previousVelocity.Value) / dt; + if (previousAcceleration is not null) + { + var jerk = (acceleration - previousAcceleration.Value) / dt; + maxAbsJerk = Math.Max(maxAbsJerk, Math.Abs(jerk)); + } + + previousVelocity = velocity; + previousAcceleration = acceleration; + } + + previousTime = currentTime; + previousPosition = currentPosition; + } + + return maxAbsJerk; + } + + /// + /// 验证稠密 MoveJoint 轨迹按 8ms 差分后不会超过机器人每轴 jerk 限值。 + /// + private static void AssertAllJointJerksWithinLimits(IReadOnlyList> rows, RobotProfile robot) + { + for (var jointIndex = 0; jointIndex < robot.DegreesOfFreedom; jointIndex++) + { + var peakJerk = MaxAbsJointJerk(rows, jointIndex); + var limit = robot.JointLimits[jointIndex].JerkLimit; + Assert.True( + peakJerk <= limit * 1.000001, + $"J{jointIndex + 1} jerk {peakJerk:F6} rad/s^3 exceeds limit {limit:F6} rad/s^3."); + } + } + + /// + /// 按 7 阶平滑点到点时间律的速度、加速度、jerk 形状系数计算理论最小时长。 + /// + private static double ExpectedSmoothPtpDuration( + RobotProfile robot, + IReadOnlyList startJoints, + IReadOnlyList targetJoints, + double speedRatio = 1.0) + { + if (speedRatio <= 0.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio)) + { + throw new ArgumentOutOfRangeException(nameof(speedRatio), "speed_ratio 必须是有限正数。"); + } + + var duration = 0.0; + for (var index = 0; index < robot.DegreesOfFreedom; index++) + { + var distance = Math.Abs(targetJoints[index] - startJoints[index]); + if (distance <= 0.0) + { + continue; + } + + var limit = robot.JointLimits[index]; + var velocityDuration = distance * SmoothPtpVelocityShapeCoefficient / (limit.VelocityLimit * speedRatio); + var accelerationDuration = Math.Sqrt(distance * SmoothPtpAccelerationShapeCoefficient / (limit.AccelerationLimit * speedRatio * speedRatio)); + var jerkDuration = Math.Cbrt(distance * SmoothPtpJerkShapeCoefficient / (limit.JerkLimit * speedRatio * speedRatio * speedRatio)); + duration = Math.Max(duration, Math.Max(velocityDuration, Math.Max(accelerationDuration, jerkDuration))); + } + + return duration; + } + + /// + /// 验证 MoveJoint 稠密轨迹已经是物理伺服周期,不把 speed_ratio 编码到采样间隔里。 + /// + private static void AssertDenseRowsUseServoPeriod(IReadOnlyList> rows, double servoPeriodSeconds) + { + Assert.True(rows.Count > 1); + for (var index = 1; index < rows.Count; index++) + { + Assert.Equal(servoPeriodSeconds, rows[index][0] - rows[index - 1][0], precision: 6); + } + } + + private static double DegreesToRadians(double degrees) + { + return degrees * Math.PI / 180.0; + } + + private static double RadiansToDegrees(double radians) + { + return radians * 180.0 / Math.PI; + } + + private static void WaitUntilIdle(FanucControllerRuntime runtime) + { + var deadline = DateTimeOffset.UtcNow.AddSeconds(1); + while (DateTimeOffset.UtcNow < deadline) + { + if (!runtime.GetSnapshot().IsInMotion) + { + return; + } + + Thread.Sleep(1); + } + + throw new TimeoutException("Timed out waiting for dense trajectory send task to finish."); + } + + /// + /// 等待测试条件成立,用于观察后台执行路径是否已经到达指定状态。 + /// + private static async Task WaitUntilAsync(Func condition, CancellationToken cancellationToken) + { + var deadline = DateTimeOffset.UtcNow.AddSeconds(1); + while (DateTimeOffset.UtcNow < deadline) + { + cancellationToken.ThrowIfCancellationRequested(); + if (condition()) + { + return; + } + + await Task.Delay(1, cancellationToken); + } + + throw new TimeoutException("Timed out waiting for test condition."); + } + + /// + /// 向被测 J519 客户端发送一帧最小状态包,用机器人侧 status sequence 驱动下一帧命令。 + /// + private static async Task SendStatusPacketAsync( + UdpClient server, + IPEndPoint clientEndpoint, + uint sequence, + CancellationToken cancellationToken, + byte status = 0b0111) + { + var responsePacket = new byte[FanucJ519Protocol.ResponsePacketLength]; + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x00, 4), 0); + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x04, 4), 1); + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x08, 4), sequence); + responsePacket[0x0c] = status; + await server.SendAsync(responsePacket, clientEndpoint, cancellationToken); + } + + /// + /// 获取一次测试执行生成的唯一稠密发送记录目录。 + /// + private static string GetSingleDenseSendRunDirectory(string outputRoot) + { + var denseSendRoot = Path.Combine(outputRoot, "DenseSend"); + Assert.True(Directory.Exists(denseSendRoot)); + + var runDirectories = Directory.GetDirectories(denseSendRoot); + Assert.Single(runDirectories); + return runDirectories[0]; + } + + /// + /// 获取本次测试刚生成的新稠密发送记录目录,避免误读历史运行产物。 + /// + private static string GetNewDenseSendRunDirectory(string outputRoot, IReadOnlySet beforeRunDirectories) + { + var denseSendRoot = Path.Combine(outputRoot, "DenseSend"); + Assert.True(Directory.Exists(denseSendRoot)); + + var newDirectories = Directory.GetDirectories(denseSendRoot) + .Where(path => !beforeRunDirectories.Contains(path)) + .ToArray(); + Assert.Single(newDirectories); + return newDirectories[0]; + } + + /// + /// 加载运行时 Config/RobotConfig.json 中的 UTTC_MS11 轨迹和对应机器人配置。 + /// + private static UttcMs11RuntimeFixture LoadUttcMs11RuntimeFixture() + { + var configRoot = TestRobotFactory.GetConfigRoot(); + var configPath = Path.Combine(configRoot, "RobotConfig.json"); + var loaded = new RobotConfigLoader().Load(configPath, configRoot); + var program = loaded.Programs["UTTC_MS11"]; + var uploaded = new ControllerClientCompatUploadedTrajectory( + name: program.Name, + waypoints: program.Waypoints.Select(static waypoint => waypoint.Positions), + shotFlags: program.ShotFlags, + offsetValues: program.OffsetValues, + addressGroups: program.AddressGroups.Select(static group => group.Addresses)); + var options = new ControllerClientCompatOptions + { + ConfigRoot = configRoot + }; + var robot = new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()) + .LoadProfile("FANUC_LR_Mate_200iD", loaded.Robot.AccLimitScale, loaded.Robot.JerkLimitScale); + + return new UttcMs11RuntimeFixture(configRoot, loaded.Robot, uploaded, robot); + } + + /// + /// 复制一份结果并替换程序名,让 DenseSend 调试文件写入唯一目录,避免测试之间复用旧目录权限。 + /// + private static TrajectoryResult WithUniqueProgramName(TrajectoryResult result, string programName) + { + return new TrajectoryResult( + programName: programName, + method: result.Method, + isValid: result.IsValid, + duration: result.Duration, + shotEvents: result.ShotEvents, + triggerTimeline: result.TriggerTimeline, + artifacts: result.Artifacts, + failureReason: result.FailureReason, + usedCache: result.UsedCache, + originalWaypointCount: result.OriginalWaypointCount, + plannedWaypointCount: result.PlannedWaypointCount, + denseJointTrajectory: result.DenseJointTrajectory, + preparedFlyshotExecution: result.PreparedFlyshotExecution); + } + + /// + /// 解析空格分隔的纯文本数值列。 + /// + private static double[] ParseColumns(string line) + { + return line + .Split(' ', StringSplitOptions.RemoveEmptyEntries) + .Select(static value => double.Parse(value)) + .ToArray(); + } + + private static void SetPrivateField(FanucControllerRuntime runtime, string name, T value) + { + var field = typeof(FanucControllerRuntime).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field); + field.SetValue(runtime, value); + } + + private static TrajectoryResult CreateDenseResult(IReadOnlyList> denseTrajectory, double durationSeconds) + { + return new TrajectoryResult( + programName: "demo", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(durationSeconds), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4, + denseJointTrajectory: denseTrajectory); + } + + private static void SetLatestJ519Response(FanucJ519Client client, byte status) + { + var response = new FanucJ519Response( + messageType: 0, + version: 1, + sequence: 10, + status: status, + readIoType: 2, + readIoIndex: 1, + readIoMask: 255, + readIoValue: 0, + timestamp: 1234, + pose: new double[6], + externalAxes: new double[3], + jointDegrees: new double[9], + motorCurrents: new double[9]); + var field = typeof(FanucJ519Client).GetField("_latestResponse", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field); + field.SetValue(client, response); + } + + /// + /// 封装运行时 UTTC_MS11 轨迹、配置和机器人模型,避免测试反复拼装。 + /// + private sealed record UttcMs11RuntimeFixture( + string ConfigRoot, + CompatibilityRobotSettings Settings, + ControllerClientCompatUploadedTrajectory Uploaded, + RobotProfile Robot); +} diff --git a/tests/Flyshot.Core.Tests/FanucControllerRuntimeLoggingRegistrationTests.cs b/tests/Flyshot.Core.Tests/FanucControllerRuntimeLoggingRegistrationTests.cs new file mode 100644 index 0000000..357b7a3 --- /dev/null +++ b/tests/Flyshot.Core.Tests/FanucControllerRuntimeLoggingRegistrationTests.cs @@ -0,0 +1,32 @@ +using Flyshot.Runtime.Common; +using Flyshot.Runtime.Fanuc; +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; + +namespace Flyshot.Core.Tests; + +/// +/// 验证 FANUC 运行时通过依赖注入解析时,能够稳定拿到日志依赖。 +/// +public sealed class FanucControllerRuntimeLoggingRegistrationTests +{ + /// + /// 验证宿主按 IControllerRuntime 解析 FANUC 运行时时,不会因为可选日志参数而退回到无日志实例。 + /// + [Fact] + public void ServiceProvider_Resolves_FanucControllerRuntime_WithLogger() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddSingleton(); + + using var serviceProvider = services.BuildServiceProvider(); + + var runtime = serviceProvider.GetRequiredService(); + var concreteRuntime = Assert.IsType(runtime); + var loggerField = typeof(FanucControllerRuntime).GetField("_logger", BindingFlags.Instance | BindingFlags.NonPublic); + + Assert.NotNull(loggerField); + Assert.NotNull(loggerField!.GetValue(concreteRuntime)); + } +} diff --git a/tests/Flyshot.Core.Tests/FanucJ519ClientTests.cs b/tests/Flyshot.Core.Tests/FanucJ519ClientTests.cs new file mode 100644 index 0000000..f026b2b --- /dev/null +++ b/tests/Flyshot.Core.Tests/FanucJ519ClientTests.cs @@ -0,0 +1,546 @@ +using System.Buffers.Binary; +using System.Net; +using System.Net.Sockets; +using Flyshot.Runtime.Fanuc.Protocol; +using Microsoft.Extensions.Logging; + +namespace Flyshot.Core.Tests; + +/// +/// 验证 FANUC UDP 60015 J519 运动客户端的初始化、状态包驱动发送与响应解析。 +/// +public sealed class FanucJ519ClientTests : IDisposable +{ + private readonly UdpClient _server; + private readonly CancellationTokenSource _cts = new(); + + /// + /// 在随机可用端口启动本地 UDP 模拟控制器。 + /// + public FanucJ519ClientTests() + { + _server = new UdpClient(0); + } + + /// + /// 获取分配给本地模拟控制器的端口。 + /// + private int Port => ((IPEndPoint)_server.Client.LocalEndPoint!).Port; + + /// + /// 清理模拟控制器和取消源。 + /// + public void Dispose() + { + _cts.Cancel(); + _server.Dispose(); + _cts.Dispose(); + } + + /// + /// 验证连接时会发送初始化包。 + /// + [Fact] + public async Task ConnectAsync_SendsInitPacket() + { + using var client = new FanucJ519Client(); + var receiveTask = _server.ReceiveAsync(_cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + + Assert.True(client.IsConnected); + var result = await receiveTask.AsTask().WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + Assert.Equal(FanucJ519Protocol.ControlPacketLength, result.Buffer.Length); + Assert.Equal(Convert.FromHexString("0000000000000001"), result.Buffer); + } + + /// + /// 验证启动运动后必须等到状态包到达,不能由上位机本地 8ms 循环主动发命令。 + /// + [Fact] + public async Task StartMotion_WaitsForStatusPacketBeforeSendingCommand() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + + // 接收并丢弃初始化包。 + await _server.ReceiveAsync(_cts.Token); + + var command = new FanucJ519Command(sequence: 1, targetJoints: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + client.UpdateCommand(command); + client.StartMotion(); + + // 机器人尚未回状态包时,上位机不应自行发 64B command packet。 + await Assert.ThrowsAsync( + () => _server.ReceiveAsync(_cts.Token).AsTask().WaitAsync(TimeSpan.FromMilliseconds(120))); + + await client.StopMotionAsync(_cts.Token); + } + + /// + /// 验证停止运动时会发送结束包。 + /// + [Fact] + public async Task StopMotionAsync_SendsEndPacket() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + + // 接收并丢弃初始化包。 + await _server.ReceiveAsync(_cts.Token); + + await client.StopMotionAsync(_cts.Token); + + // 服务器应该收到结束包。 + var endResult = await _server.ReceiveAsync(_cts.Token); + Assert.Equal(FanucJ519Protocol.ControlPacketLength, endResult.Buffer.Length); + Assert.Equal(Convert.FromHexString("0000000200000001"), endResult.Buffer); + } + + /// + /// 验证响应解析和最新响应缓存。 + /// + [Fact] + public async Task GetLatestResponse_ParsesIncomingResponse() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + + // 接收初始化包,获取客户端端点。 + var initResult = await _server.ReceiveAsync(_cts.Token); + var clientEndpoint = initResult.RemoteEndPoint; + + // 构造 132B 响应包并发送回客户端。 + var responsePacket = new byte[FanucJ519Protocol.ResponsePacketLength]; + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x00, 4), 0); + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x04, 4), 1); + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x08, 4), 5); + responsePacket[0x0c] = 15; // 所有状态位为真。 + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x14, 4), 999u); + BinaryPrimitives.WriteSingleBigEndian(responsePacket.AsSpan(0x18, 4), 10.0f); + BinaryPrimitives.WriteSingleBigEndian(responsePacket.AsSpan(0x3c, 4), 0.5f); + BinaryPrimitives.WriteSingleBigEndian(responsePacket.AsSpan(0x60, 4), 1.0f); + + await _server.SendAsync(responsePacket, clientEndpoint, _cts.Token); + + // 给接收循环留出时间。 + await Task.Delay(200, _cts.Token); + + var latest = client.GetLatestResponse(); + Assert.NotNull(latest); + Assert.Equal(5u, latest.Sequence); + Assert.True(latest.AcceptsCommand); + Assert.True(latest.ReceivedCommand); + Assert.True(latest.SystemReady); + Assert.True(latest.RobotInMotion); + Assert.Equal(999u, latest.Timestamp); + Assert.Equal(10.0, latest.Pose[0], precision: 6); + Assert.Equal(0.5, latest.JointDegrees[0], precision: 6); + Assert.Equal(1.0, latest.MotorCurrents[0], precision: 6); + } + + /// + /// 验证收到状态包后,下一帧命令使用该状态包的序号。 + /// + [Fact] + public async Task StartMotion_UsesLatestStatusSequenceForFirstCommand() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var initResult = await _server.ReceiveAsync(_cts.Token); + + var command = new FanucJ519Command(sequence: 1, targetJoints: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + client.UpdateCommand(command); + client.StartMotion(); + + await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence: 100); + + var result = await _server.ReceiveAsync(_cts.Token); + Assert.Equal(FanucJ519Protocol.CommandPacketLength, result.Buffer.Length); + Assert.Equal(100u, BinaryPrimitives.ReadUInt32BigEndian(result.Buffer.AsSpan(0x08, 4))); + Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(result.Buffer.AsSpan(0x1c, 4)), precision: 6); + + await client.StopMotionAsync(_cts.Token); + } + + /// + /// 验证直角坐标命令会把 Data format 写为 0,并按通用目标槽位写入 X/Y/Z/W/P/R。 + /// + [Fact] + public void PackCommandPacket_WritesCartesianDataFormatAndTargetValues() + { + var command = new FanucJ519Command( + sequence: 7, + targetValues: [100.0, 200.0, 300.0, 1.0, 2.0, 3.0, 0.0, 0.0, 0.0], + dataStyle: 0); + + var packet = FanucJ519Protocol.PackCommandPacket(command); + + Assert.Equal(0, packet[0x12]); + Assert.Equal(100.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x1c, 4))); + Assert.Equal(200.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x20, 4))); + Assert.Equal(300.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x24, 4))); + Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x28, 4))); + Assert.Equal(2.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x2c, 4))); + Assert.Equal(3.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x30, 4))); + } + + /// + /// 验证配置 J519 buffer size 后,实际回发命令序号会在状态包序号基础上增加该缓冲深度。 + /// + [Fact] + public async Task StartMotion_AddsConfiguredBufferSizeToStatusSequence() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var initResult = await _server.ReceiveAsync(_cts.Token); + + client.SetSequenceBufferSize(8); + client.UpdateCommand(new FanucJ519Command(sequence: 1, targetJoints: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0])); + client.StartMotion(); + + await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence: 100); + + var result = await _server.ReceiveAsync(_cts.Token); + Assert.Equal(FanucJ519Protocol.CommandPacketLength, result.Buffer.Length); + Assert.Equal(108u, BinaryPrimitives.ReadUInt32BigEndian(result.Buffer.AsSpan(0x08, 4))); + + await client.StopMotionAsync(_cts.Token); + } + + /// + /// 验证连续状态包会逐包驱动命令发送,并使用各自的状态包序号。 + /// + [Fact] + public async Task StartMotion_SendsOneCommandForEachStatusPacketWithMatchingSequence() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var initResult = await _server.ReceiveAsync(_cts.Token); + + var command = new FanucJ519Command(sequence: 99, targetJoints: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + client.UpdateCommand(command); + client.StartMotion(); + + var packets = new List(); + for (uint sequence = 100; sequence < 104; sequence++) + { + await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence); + var result = await _server.ReceiveAsync(_cts.Token); + packets.Add(result.Buffer); + } + + await client.StopMotionAsync(_cts.Token); + + var sequences = packets + .Select(packet => BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x08, 4))) + .ToArray(); + Assert.Equal([100u, 101u, 102u, 103u], sequences); + Assert.All(packets, packet => Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x1c, 4)), precision: 6)); + } + + /// + /// 验证预装命令队列会被机器人状态包逐帧出队,队列耗尽后继续保持最后目标。 + /// + [Fact] + public async Task StartMotion_DequeuesPreparedCommandsForStatusPacketsAndHoldsLastCommand() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var initResult = await _server.ReceiveAsync(_cts.Token); + + client.StartMotion(); + client.LoadCommandQueue( + [ + new FanucJ519Command(sequence: 0, targetJoints: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + new FanucJ519Command(sequence: 0, targetJoints: [2.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + new FanucJ519Command(sequence: 0, targetJoints: [3.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + ]); + + var packets = new List(); + for (uint sequence = 700; sequence < 704; sequence++) + { + await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence); + var result = await _server.ReceiveAsync(_cts.Token); + packets.Add(result.Buffer); + } + + await client.StopMotionAsync(_cts.Token); + + var sequences = packets + .Select(packet => BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x08, 4))) + .ToArray(); + var firstJointTargets = packets + .Select(packet => BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x1c, 4))) + .ToArray(); + + Assert.Equal([700u, 701u, 702u, 703u], sequences); + Assert.Equal([1.0f, 2.0f, 3.0f, 3.0f], firstJointTargets); + Assert.True(client.IsCommandQueueDrainedForTests()); + } + + /// + /// 验证停止运动后可在同一连接内重启发送,命令序号仍由新的状态包决定。 + /// + [Fact] + public async Task StartMotion_CanRestartAfterStopMotionAndUseNewStatusSequence() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var initResult = await _server.ReceiveAsync(_cts.Token); + + client.UpdateCommand(new FanucJ519Command(sequence: 10, targetJoints: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0])); + client.StartMotion(); + + await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence: 200); + var first = await _server.ReceiveAsync(_cts.Token); + var firstSequence = BinaryPrimitives.ReadUInt32BigEndian(first.Buffer.AsSpan(0x08, 4)); + Assert.Equal(200u, firstSequence); + + await client.StopMotionAsync(_cts.Token); + + byte[] packet; + do + { + packet = (await _server.ReceiveAsync(_cts.Token)).Buffer; + } + while (packet.Length != FanucJ519Protocol.ControlPacketLength); + + client.UpdateCommand(new FanucJ519Command(sequence: 20, targetJoints: [2.0, 0.0, 0.0, 0.0, 0.0, 0.0])); + client.StartMotion(); + + await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence: 300); + var restarted = await _server.ReceiveAsync(_cts.Token).AsTask().WaitAsync(TimeSpan.FromSeconds(1), _cts.Token); + + Assert.Equal(FanucJ519Protocol.CommandPacketLength, restarted.Buffer.Length); + Assert.Equal(300u, BinaryPrimitives.ReadUInt32BigEndian(restarted.Buffer.AsSpan(0x08, 4))); + Assert.Equal(2.0f, BinaryPrimitives.ReadSingleBigEndian(restarted.Buffer.AsSpan(0x1c, 4)), precision: 6); + + await client.StopMotionAsync(_cts.Token); + } + + /// + /// 验证没有显式目标时,会使用最近一帧状态反馈里的关节角持续构造 hold 命令。 + /// + [Fact] + public async Task StartMotion_WithoutExplicitCommand_HoldsLatestResponseJointDegrees() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var initResult = await _server.ReceiveAsync(_cts.Token); + + client.StartMotion(); + + await SendStatusPacketAsync( + initResult.RemoteEndPoint, + sequence: 401, + jointDegrees: [10.0, 20.0, 30.0, 40.0, 50.0, 60.0]); + var holdPacket = await _server.ReceiveAsync(_cts.Token).AsTask().WaitAsync(TimeSpan.FromSeconds(1), _cts.Token); + + Assert.Equal(FanucJ519Protocol.CommandPacketLength, holdPacket.Buffer.Length); + Assert.Equal(401u, BinaryPrimitives.ReadUInt32BigEndian(holdPacket.Buffer.AsSpan(0x08, 4))); + Assert.Equal(10.0f, BinaryPrimitives.ReadSingleBigEndian(holdPacket.Buffer.AsSpan(0x1c, 4)), precision: 6); + Assert.Equal(20.0f, BinaryPrimitives.ReadSingleBigEndian(holdPacket.Buffer.AsSpan(0x20, 4)), precision: 6); + Assert.Equal(60.0f, BinaryPrimitives.ReadSingleBigEndian(holdPacket.Buffer.AsSpan(0x30, 4)), precision: 6); + + await client.StopMotionAsync(_cts.Token); + } + + /// + /// 验证存在显式目标时,状态反馈生成的 hold 命令不会覆盖当前待发送目标。 + /// + [Fact] + public async Task StartMotion_WithExplicitCommand_PrefersExplicitTargetOverHoldCommand() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var initResult = await _server.ReceiveAsync(_cts.Token); + + client.UpdateCommand(new FanucJ519Command(sequence: 1, targetJoints: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])); + client.StartMotion(); + + await SendStatusPacketAsync( + initResult.RemoteEndPoint, + sequence: 402, + jointDegrees: [10.0, 20.0, 30.0, 40.0, 50.0, 60.0]); + var commandPacket = await _server.ReceiveAsync(_cts.Token).AsTask().WaitAsync(TimeSpan.FromSeconds(1), _cts.Token); + + Assert.Equal(FanucJ519Protocol.CommandPacketLength, commandPacket.Buffer.Length); + Assert.Equal(402u, BinaryPrimitives.ReadUInt32BigEndian(commandPacket.Buffer.AsSpan(0x08, 4))); + Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(commandPacket.Buffer.AsSpan(0x1c, 4)), precision: 6); + Assert.Equal(2.0f, BinaryPrimitives.ReadSingleBigEndian(commandPacket.Buffer.AsSpan(0x20, 4)), precision: 6); + Assert.Equal(6.0f, BinaryPrimitives.ReadSingleBigEndian(commandPacket.Buffer.AsSpan(0x30, 4)), precision: 6); + + await client.StopMotionAsync(_cts.Token); + } + + /// + /// 验证在连接前调用 StartMotion 会抛出 InvalidOperationException。 + /// + [Fact] + public void StartMotion_BeforeConnect_Throws() + { + using var client = new FanucJ519Client(); + Assert.Throws(() => client.StartMotion()); + } + + /// + /// 验证状态包驱动发送能持续输出命令包。 + /// + [Fact] + public async Task StartMotion_MaintainsStatusDrivenCommandStream() + { + using var client = new FanucJ519Client(); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var initResult = await _server.ReceiveAsync(_cts.Token); + + var command = new FanucJ519Command(sequence: 1, targetJoints: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + client.UpdateCommand(command); + client.StartMotion(); + + // 收集 5 个命令包到达时间戳和序号。 + var timestamps = new List(); + var sequences = new List(); + for (var i = 0; i < 5; i++) + { + await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence: (uint)(500 + i)); + var result = await _server.ReceiveAsync(_cts.Token); + Assert.Equal(FanucJ519Protocol.CommandPacketLength, result.Buffer.Length); + sequences.Add(BinaryPrimitives.ReadUInt32BigEndian(result.Buffer.AsSpan(0x08, 4))); + timestamps.Add(DateTimeOffset.UtcNow); + } + + await client.StopMotionAsync(_cts.Token); + + Assert.Equal([500u, 501u, 502u, 503u, 504u], sequences); + + // 计算相邻包间隔并使用 CI 安全的宽松边界验证周期流仍在推进。 + var intervals = new List(); + for (var i = 1; i < timestamps.Count; i++) + { + intervals.Add(timestamps[i] - timestamps[i - 1]); + } + + Assert.All(intervals, interval => + { + Assert.True(interval > TimeSpan.Zero, $"间隔 {interval.TotalMilliseconds:F2}ms 必须为正。"); + Assert.True(interval <= TimeSpan.FromMilliseconds(30), $"间隔 {interval.TotalMilliseconds:F2}ms 过长。"); + }); + } + + /// + /// 验证状态变化日志会附带最近一次实际发送的目标关节轴,便于联调时对照控制目标。 + /// + [Fact] + public async Task ReceiveLoop_LogsLastSentTargetJointsWhenStatusChanges() + { + var logger = new CapturingLogger(); + using var client = new FanucJ519Client(logger); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + var initResult = await _server.ReceiveAsync(_cts.Token); + + client.UpdateCommand(new FanucJ519Command(sequence: 1, targetJoints: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])); + client.StartMotion(); + + await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence: 42); + _ = await _server.ReceiveAsync(_cts.Token); + await Task.Delay(200, _cts.Token); + + Assert.Contains( + logger.Entries, + entry => entry.Level == LogLevel.Information + && entry.Message.Contains("J519 最后一条发送目标关节轴", StringComparison.Ordinal) + && entry.Message.Contains("1.00000, 2.00000, 3.00000, 4.00000, 5.00000, 6.00000", StringComparison.Ordinal)); + } + + /// + /// 向被测 J519 客户端发送一帧最小状态包,用机器人侧 status sequence 驱动下一帧命令。 + /// + private async Task SendStatusPacketAsync( + IPEndPoint clientEndpoint, + uint sequence, + IReadOnlyList? jointDegrees = null) + { + var responsePacket = new byte[FanucJ519Protocol.ResponsePacketLength]; + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x00, 4), 0); + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x04, 4), 1); + BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x08, 4), sequence); + responsePacket[0x0c] = 15; + + if (jointDegrees is not null) + { + for (var index = 0; index < Math.Min(6, jointDegrees.Count); index++) + { + BinaryPrimitives.WriteSingleBigEndian( + responsePacket.AsSpan(0x3c + index * sizeof(float), sizeof(float)), + (float)jointDegrees[index]); + } + } + + await _server.SendAsync(responsePacket, clientEndpoint, _cts.Token); + } + + /// + /// 收集测试过程中的结构化日志,便于断言运行期输出内容。 + /// + private sealed class CapturingLogger : ILogger + { + /// + /// 获取已记录的日志条目。 + /// + public List Entries { get; } = []; + + /// + /// 开始日志作用域;当前测试无需作用域,直接返回空对象。 + /// + public IDisposable BeginScope(TState state) + where TState : notnull + { + return NullScope.Instance; + } + + /// + /// 指示所有日志级别均启用,便于测试完整捕获输出。 + /// + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + /// + /// 记录一条格式化后的日志消息。 + /// + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) + { + Entries.Add(new LogEntry(logLevel, formatter(state, exception))); + } + + /// + /// 表示一次日志记录。 + /// + public sealed record LogEntry(LogLevel Level, string Message); + + /// + /// 提供空日志作用域,避免测试中额外分配。 + /// + private sealed class NullScope : IDisposable + { + /// + /// 获取单例空作用域。 + /// + public static NullScope Instance { get; } = new(); + + /// + /// 释放空作用域;无需实际动作。 + /// + public void Dispose() + { + } + } + } +} diff --git a/tests/Flyshot.Core.Tests/FanucProtocolTests.cs b/tests/Flyshot.Core.Tests/FanucProtocolTests.cs new file mode 100644 index 0000000..2b331b2 --- /dev/null +++ b/tests/Flyshot.Core.Tests/FanucProtocolTests.cs @@ -0,0 +1,238 @@ +using System.Buffers.Binary; +using Flyshot.Runtime.Fanuc.Protocol; + +namespace Flyshot.Core.Tests; + +/// +/// 验证 FANUC 真机三条通信链路的二进制协议基础与逆向抓包样本一致。 +/// +public sealed class FanucProtocolTests +{ + /// + /// 验证 TCP 10012 程序命令封包与抓包中的 StopProg("RVBUSTSM") 完全一致。 + /// + [Fact] + public void CommandProtocol_PacksCapturedStopProgramFrame() + { + var frame = FanucCommandProtocol.PackProgramCommand(FanucCommandMessageIds.StopProgram, "RVBUSTSM"); + + Assert.Equal( + Convert.FromHexString("646f7a0000001a0000210300000008525642555354534d7a6f64"), + frame); + } + + /// + /// 验证 TCP 10012 短响应和程序状态响应可以按抓包字段解析。 + /// + [Fact] + public void CommandProtocol_ParsesCapturedResponses() + { + var stopResponse = FanucCommandProtocol.ParseResultResponse( + Convert.FromHexString("646f7a0000001200002103000000007a6f64")); + var statusResponse = FanucCommandProtocol.ParseProgramStatusResponse( + Convert.FromHexString("646f7a000000160000200300000001000000007a6f64")); + + Assert.Equal(FanucCommandMessageIds.StopProgram, stopResponse.MessageId); + Assert.True(stopResponse.IsSuccess); + Assert.Equal(FanucCommandMessageIds.GetProgramStatus, statusResponse.MessageId); + Assert.True(statusResponse.IsSuccess); + Assert.Equal(1u, statusResponse.ProgramStatus); + } + + /// + /// 验证 TCP 10012 的速度倍率、TCP 和 IO 请求体字段顺序与逆向文档一致。 + /// + [Fact] + public void CommandProtocol_PacksParameterCommandBodies() + { + var setTcpFrame = FanucCommandProtocol.PackSetTcpCommand(1, [1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 1.0]); + + Assert.Equal( + Convert.FromHexString("646f7a0000000e000022067a6f64"), + FanucCommandProtocol.PackGetSpeedRatioCommand()); + Assert.Equal( + Convert.FromHexString("646f7a0000001200002207000000507a6f64"), + FanucCommandProtocol.PackSetSpeedRatioCommand(0.8)); + Assert.Equal( + Convert.FromHexString("646f7a0000001200002200000000017a6f64"), + FanucCommandProtocol.PackGetTcpCommand(1)); + Assert.Equal( + Convert.FromHexString("646f7a000000160000220800000002000000077a6f64"), + FanucCommandProtocol.PackGetIoCommand(FanucIoTypes.DigitalOutput, 7)); + Assert.Equal( + Convert.FromHexString("646f7a0000001a0000220900000002000000073f8000007a6f64"), + FanucCommandProtocol.PackSetIoCommand(FanucIoTypes.DigitalOutput, 7, true)); + Assert.Equal(FanucCommandMessageIds.SetTcp, BinaryPrimitives.ReadUInt32BigEndian(setTcpFrame.AsSpan(7, 4))); + Assert.Equal(1u, BinaryPrimitives.ReadUInt32BigEndian(setTcpFrame.AsSpan(11, 4))); + Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(setTcpFrame.AsSpan(15, 4))); + Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(setTcpFrame.AsSpan(39, 4))); + } + + /// + /// 验证 TCP 10012 参数响应解析使用各自不同的字段顺序。 + /// + [Fact] + public void CommandProtocol_ParsesParameterResponses() + { + var speedRatioResponse = FanucCommandProtocol.ParseSpeedRatioResponse( + FanucCommandProtocol.PackFrame( + FanucCommandMessageIds.GetSpeedRatio, + Convert.FromHexString("0000005000000000"))); + var tcpResponse = FanucCommandProtocol.ParseTcpResponse( + FanucCommandProtocol.PackFrame( + FanucCommandMessageIds.GetTcp, + Convert.FromHexString("00000000000000013f80000040000000404000000000000000000000000000003f800000"))); + var ioResponse = FanucCommandProtocol.ParseIoResponse( + FanucCommandProtocol.PackFrame( + FanucCommandMessageIds.GetIo, + Convert.FromHexString("000000003f800000"))); + + Assert.True(speedRatioResponse.IsSuccess); + Assert.Equal(0.8, speedRatioResponse.Ratio, precision: 6); + Assert.True(tcpResponse.IsSuccess); + Assert.Equal(1u, tcpResponse.TcpId); + Assert.Equal([1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 1.0], tcpResponse.Pose); + Assert.True(ioResponse.IsSuccess); + Assert.True(ioResponse.Value); + Assert.Equal(1.0, ioResponse.NumericValue, precision: 6); + } + + /// + /// 验证 TCP 10010 状态帧可以从抓包样本解析出尾部状态槽位。 + /// + [Fact] + public void StateProtocol_ParsesCapturedStateFrame() + { + var frame = FanucStateProtocol.ParseFrame(Convert.FromHexString( + "646f7a0000005a000000004388a23243f1ed7f43e9de6bc265031ec2b33cc3c278e0153f8742f53c3f128dbc929529bc7861d63cb0184c3c1ca1a7000000000000000000000000000000020000000000000000000000017a6f64")); + + Assert.Equal(0u, frame.MessageId); + Assert.Equal(6, frame.Pose.Count); + Assert.Equal(9, frame.JointOrExtensionValues.Count); + Assert.Equal([2u, 0u, 0u, 1u], frame.TailWords); + Assert.Equal(frame.Pose, frame.CartesianPose); + Assert.Equal(frame.JointOrExtensionValues.Take(6), frame.JointRadians); + Assert.Equal(frame.JointOrExtensionValues.Skip(6), frame.ExternalAxes); + Assert.Equal(frame.TailWords, frame.RawTailWords); + Assert.Equal(2u, frame.StatusWord0); + Assert.Equal(0u, frame.StatusWord1); + Assert.Equal(0u, frame.StatusWord2); + Assert.Equal(1u, frame.StatusWord3); + } + + /// + /// 验证 pcap 中多条唯一 TCP 10010 状态帧都符合固定 90B 布局。 + /// + [Theory] + [InlineData("646f7a0000005a0000000040eac85a43b2ef4043aba8e9421ed9c1c2828105c2ed981f3fbdbda0bed4764ebe92aacc3efd9f0a3f317ce9be5d4580000000000000000000000000000000020000000000000000000000017a6f64")] + [InlineData("646f7a0000005a00000000415aab64440a5302439adef542b39739c293c441431d50423fcdb7003d862fe3beca5730bf60eab23f148e403f89269d000000000000000000000000000000020000000000000000000000017a6f64")] + [InlineData("646f7a0000005a000000004221b6f9440b9ce043a129ac42b292bac29cba78431bddcb3fc743213d90268dbeba5351bf64bc1b3f0cbdf73f826864000000000000000000000000000000020000000000000000000000017a6f64")] + public void StateProtocol_ParsesMultipleCapturedPcapFrames(string frameHex) + { + var frameBytes = Convert.FromHexString(frameHex); + + var frame = FanucStateProtocol.ParseFrame(frameBytes); + + Assert.Equal(FanucStateProtocol.StateFrameLength, frameBytes.Length); + Assert.Equal(6, frame.CartesianPose.Count); + Assert.Equal(6, frame.JointRadians.Count); + Assert.Equal(3, frame.ExternalAxes.Count); + Assert.Equal([2u, 0u, 0u, 1u], frame.RawTailWords); + } + + /// + /// 验证 TCP 10010 状态帧会拒绝损坏的长度和 magic,避免后台循环缓存坏帧。 + /// + [Fact] + public void StateProtocol_RejectsMalformedStateFrames() + { + var validFrame = Convert.FromHexString( + "646f7a0000005a000000004388a23243f1ed7f43e9de6bc265031ec2b33cc3c278e0153f8742f53c3f128dbc929529bc7861d63cb0184c3c1ca1a7000000000000000000000000000000020000000000000000000000017a6f64"); + var wrongMagic = validFrame.ToArray(); + wrongMagic[0] = 0; + var wrongLength = validFrame.ToArray(); + wrongLength[6] = 0x59; + + Assert.Throws(() => FanucStateProtocol.ParseFrame(validFrame.AsSpan(0, validFrame.Length - 1))); + Assert.Throws(() => FanucStateProtocol.ParseFrame(wrongMagic)); + Assert.Throws(() => FanucStateProtocol.ParseFrame(wrongLength)); + } + + /// + /// 验证 UDP 60015 的 J519 初始化、结束和命令包字段布局。 + /// + [Fact] + public void J519Protocol_PacksControlAndCommandPackets() + { + var command = new FanucJ519Command( + sequence: 2, + targetJoints: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + + var packet = FanucJ519Protocol.PackCommandPacket(command); + + Assert.Equal(Convert.FromHexString("0000000000000001"), FanucJ519Protocol.PackInitPacket()); + Assert.Equal(Convert.FromHexString("0000000200000001"), FanucJ519Protocol.PackEndPacket()); + Assert.Equal(FanucJ519Protocol.CommandPacketLength, packet.Length); + Assert.Equal(1u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x00, 4))); + Assert.Equal(1u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x04, 4))); + Assert.Equal(2u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x08, 4))); + Assert.Equal(2, packet[0x0d]); + Assert.Equal(1, packet[0x12]); + Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x1c, 4))); + Assert.Equal(6.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x30, 4))); + Assert.Equal(0.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x38, 4))); + } + + /// + /// 验证实时发送路径可以复用命令对象,并用机器人状态包序号覆盖出包序号。 + /// + [Fact] + public void J519Protocol_PacksCommandWithOverrideSequenceIntoReusableBuffer() + { + var command = new FanucJ519Command( + sequence: 2, + targetJoints: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + var packet = new byte[FanucJ519Protocol.CommandPacketLength]; + + FanucJ519Protocol.PackCommandPacket(command, sequence: 456, packet); + + Assert.Equal(456u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x08, 4))); + Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x1c, 4))); + Assert.Equal(6.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x30, 4))); + } + + /// + /// 验证 UDP 60015 的 132 字节响应包字段可以被解析成状态位和关节反馈。 + /// + [Fact] + public void J519Protocol_ParsesResponsePacket() + { + var packet = new byte[FanucJ519Protocol.ResponsePacketLength]; + BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x00, 4), 0); + BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x04, 4), 1); + BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x08, 4), 12); + packet[0x0c] = 15; + packet[0x0d] = 2; + BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x0e, 2), 1); + BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x10, 2), 255); + BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x12, 2), 10); + BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x14, 4), 1234); + BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x18, 4), 100.5f); + BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x3c, 4), 1.25f); + BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x60, 4), 2.5f); + + var response = FanucJ519Protocol.ParseResponse(packet); + + Assert.Equal(12u, response.Sequence); + Assert.Equal(15, response.Status); + Assert.True(response.AcceptsCommand); + Assert.True(response.ReceivedCommand); + Assert.True(response.SystemReady); + Assert.True(response.RobotInMotion); + Assert.Equal(10, response.ReadIoValue); + Assert.Equal(1234u, response.Timestamp); + Assert.Equal(100.5, response.Pose[0], precision: 6); + Assert.Equal(1.25, response.JointDegrees[0], precision: 6); + Assert.Equal(2.5, response.MotorCurrents[0], precision: 6); + } +} diff --git a/tests/Flyshot.Core.Tests/FanucStateClientTests.cs b/tests/Flyshot.Core.Tests/FanucStateClientTests.cs new file mode 100644 index 0000000..b014d48 --- /dev/null +++ b/tests/Flyshot.Core.Tests/FanucStateClientTests.cs @@ -0,0 +1,263 @@ +using System.Buffers.Binary; +using System.Net; +using System.Net.Sockets; +using Flyshot.Runtime.Fanuc.Protocol; + +namespace Flyshot.Core.Tests; + +/// +/// 验证 FANUC TCP 10010 状态通道客户端的后台接收与缓存能力。 +/// +public sealed class FanucStateClientTests : IDisposable +{ + private readonly TcpListener _listener; + private readonly CancellationTokenSource _cts = new(); + + /// + /// 在随机可用端口启动本地模拟控制器。 + /// + public FanucStateClientTests() + { + _listener = new TcpListener(IPAddress.Loopback, 0); + _listener.Start(); + } + + /// + /// 获取分配给本地模拟控制器的端口。 + /// + private int Port => ((IPEndPoint)_listener.LocalEndpoint).Port; + + /// + /// 清理模拟控制器和取消源。 + /// + public void Dispose() + { + _cts.Cancel(); + _listener.Stop(); + _cts.Dispose(); + } + + /// + /// 验证状态客户端可以连接本地模拟控制器。 + /// + [Fact] + public async Task ConnectAsync_ConnectsToLocalListener() + { + using var client = new FanucStateClient(); + var acceptTask = _listener.AcceptTcpClientAsync(); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + + Assert.True(client.IsConnected); + await acceptTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证后台循环能正确解析抓包样本状态帧。 + /// + [Fact] + public async Task GetLatestFrame_ReceivesAndParsesCapturedStateFrame() + { + using var client = new FanucStateClient(); + var capturedFrame = Convert.FromHexString( + "646f7a0000005a000000004388a23243f1ed7f43e9de6bc265031ec2b33cc3c278e0153f8742f53c3f128dbc929529bc7861d63cb0184c3c1ca1a7000000000000000000000000000000020000000000000000000000017a6f64"); + + var handlerTask = RunStreamingControllerAsync(capturedFrame, _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + + // 给后台循环留出接收和解析的时间。 + await Task.Delay(200, _cts.Token); + + var latest = client.GetLatestFrame(); + Assert.NotNull(latest); + Assert.Equal(0u, latest.MessageId); + Assert.Equal(6, latest.Pose.Count); + Assert.Equal(9, latest.JointOrExtensionValues.Count); + Assert.Equal([2u, 0u, 0u, 1u], latest.TailWords); + + client.Disconnect(); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证状态客户端在连接前调用 GetLatestFrame 返回 null。 + /// + [Fact] + public void GetLatestFrame_BeforeConnect_ReturnsNull() + { + using var client = new FanucStateClient(); + Assert.Null(client.GetLatestFrame()); + } + + /// + /// 验证 Disconnect 后最新帧被清空。 + /// + [Fact] + public async Task Disconnect_ClearsLatestFrame() + { + using var client = new FanucStateClient(); + var capturedFrame = CapturedStateFrame(); + + var handlerTask = RunStreamingControllerAsync(capturedFrame, _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + await Task.Delay(200, _cts.Token); + Assert.NotNull(client.GetLatestFrame()); + + client.Disconnect(); + Assert.Null(client.GetLatestFrame()); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 验证状态通道长时间收不到完整帧时会标记陈旧并触发重连。 + /// + [Fact] + public async Task GetStatus_MarksFrameStaleAndReconnectsWhenFrameTimesOut() + { + using var client = new FanucStateClient(new FanucStateClientOptions + { + FrameTimeout = TimeSpan.FromMilliseconds(100), + ReconnectInitialDelay = TimeSpan.FromMilliseconds(20), + ReconnectMaxDelay = TimeSpan.FromMilliseconds(50), + ConnectTimeout = TimeSpan.FromSeconds(1), + }); + + var acceptTask = _listener.AcceptTcpClientAsync(_cts.Token); + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + using var controller = await acceptTask.AsTask().WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + + await WaitUntilAsync( + () => client.GetStatus().ReconnectAttemptCount > 0, + TimeSpan.FromSeconds(2), + _cts.Token); + + var status = client.GetStatus(); + Assert.True(status.IsFrameStale); + Assert.True(status.State is FanucStateConnectionState.TimedOut or FanucStateConnectionState.Reconnecting or FanucStateConnectionState.Connected); + Assert.NotNull(status.LastErrorMessage); + Assert.Contains("超时", status.LastErrorMessage); + } + + /// + /// 验证状态通道在控制柜主动断开后可以退避重连并接收新连接上的状态帧。 + /// + [Fact] + public async Task ReceiveLoop_ReconnectsAfterEofAndKeepsReceivingFrames() + { + using var client = new FanucStateClient(new FanucStateClientOptions + { + FrameTimeout = TimeSpan.FromMilliseconds(500), + ReconnectInitialDelay = TimeSpan.FromMilliseconds(20), + ReconnectMaxDelay = TimeSpan.FromMilliseconds(50), + ConnectTimeout = TimeSpan.FromSeconds(1), + }); + + var firstFrame = CapturedStateFrame(1); + var secondFrame = CapturedStateFrame(2); + var handlerTask = RunReconnectControllerAsync(firstFrame, secondFrame, _cts.Token); + + await client.ConnectAsync("127.0.0.1", Port, _cts.Token); + + await WaitUntilAsync( + () => client.GetLatestFrame()?.MessageId == 2u, + TimeSpan.FromSeconds(2), + _cts.Token); + + var status = client.GetStatus(); + Assert.Equal(FanucStateConnectionState.Connected, status.State); + Assert.True(status.ReconnectAttemptCount >= 1); + + client.Disconnect(); + await handlerTask.WaitAsync(TimeSpan.FromSeconds(2), _cts.Token); + } + + /// + /// 启动模拟控制器,持续发送状态帧流。 + /// + private async Task RunStreamingControllerAsync(byte[] frames, CancellationToken cancellationToken) + { + using var controller = await _listener.AcceptTcpClientAsync(cancellationToken); + await using var stream = controller.GetStream(); + + try + { + while (!cancellationToken.IsCancellationRequested) + { + await stream.WriteAsync(frames, cancellationToken); + await Task.Delay(50, cancellationToken); + } + } + catch (OperationCanceledException) + { + // 正常取消。 + } + catch (IOException) + { + // 客户端断开。 + } + } + + /// + /// 启动模拟控制器:第一条连接发一帧后主动断开,第二条连接持续发送新帧。 + /// + private async Task RunReconnectControllerAsync(byte[] firstFrame, byte[] secondFrame, CancellationToken cancellationToken) + { + using (var firstController = await _listener.AcceptTcpClientAsync(cancellationToken)) + { + await using var firstStream = firstController.GetStream(); + await firstStream.WriteAsync(firstFrame, cancellationToken); + } + + using var secondController = await _listener.AcceptTcpClientAsync(cancellationToken); + await using var secondStream = secondController.GetStream(); + + try + { + while (!cancellationToken.IsCancellationRequested) + { + await secondStream.WriteAsync(secondFrame, cancellationToken); + await Task.Delay(50, cancellationToken); + } + } + catch (OperationCanceledException) + { + // 正常取消。 + } + catch (IOException) + { + // 客户端断开。 + } + } + + /// + /// 构造来自 j519 抓包的状态帧,并按测试需要覆写 message_id。 + /// + private static byte[] CapturedStateFrame(uint messageId = 0) + { + var frame = Convert.FromHexString( + "646f7a0000005a000000004388a23243f1ed7f43e9de6bc265031ec2b33cc3c278e0153f8742f53c3f128dbc929529bc7861d63cb0184c3c1ca1a7000000000000000000000000000000020000000000000000000000017a6f64"); + BinaryPrimitives.WriteUInt32BigEndian(frame.AsSpan(7, 4), messageId); + return frame; + } + + /// + /// 等待异步后台循环达到预期状态,超时后让测试明确失败。 + /// + private static async Task WaitUntilAsync(Func predicate, TimeSpan timeout, CancellationToken cancellationToken) + { + var deadline = DateTimeOffset.UtcNow + timeout; + while (DateTimeOffset.UtcNow < deadline) + { + if (predicate()) + { + return; + } + + await Task.Delay(20, cancellationToken); + } + + Assert.True(predicate(), "等待状态通道后台循环达到预期状态超时。"); + } +} diff --git a/tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj b/tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj index 39acecc..06712ec 100644 --- a/tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj +++ b/tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj @@ -23,10 +23,12 @@ + + diff --git a/tests/Flyshot.Core.Tests/J519SendTrajectorySamplerTests.cs b/tests/Flyshot.Core.Tests/J519SendTrajectorySamplerTests.cs new file mode 100644 index 0000000..c2d1d27 --- /dev/null +++ b/tests/Flyshot.Core.Tests/J519SendTrajectorySamplerTests.cs @@ -0,0 +1,233 @@ +using Flyshot.Core.Planning.Sampling; +using Flyshot.Core.Domain; +using Flyshot.Core.Planning; + +namespace Flyshot.Core.Tests; + +/// +/// 验证 J519 实发重采样器在离线导出和运行时下发之间保持一致的时间轴语义。 +/// +public sealed class J519SendTrajectorySamplerTests +{ + /// + /// 验证 speed_ratio 只缩放轨迹时间,物理发送时间仍按固定伺服周期推进。 + /// + [Fact] + public void SampleDenseJointTrajectory_MapsSendTimeToScaledTrajectoryTimeAndDegrees() + { + var denseTrajectory = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.008, Math.PI / 2.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.016, Math.PI, 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + + var samples = J519SendTrajectorySampler.SampleDenseJointTrajectory( + denseTrajectory, + durationSeconds: 0.016, + servoPeriodSeconds: 0.008, + speedRatio: 0.5); + + Assert.Equal(5, samples.Count); + Assert.Equal(0, samples[0].SampleIndex); + Assert.Equal(0.0, samples[0].SendTime, precision: 6); + Assert.Equal(0.0, samples[0].TrajectoryTime, precision: 6); + Assert.Equal(0.0, samples[0].JointsDegrees[0], precision: 6); + + Assert.Equal(1, samples[1].SampleIndex); + Assert.Equal(0.008, samples[1].SendTime, precision: 6); + Assert.Equal(0.004, samples[1].TrajectoryTime, precision: 6); + Assert.Equal(45.0, samples[1].JointsDegrees[0], precision: 6); + + Assert.Equal(4, samples[^1].SampleIndex); + Assert.Equal(0.032, samples[^1].SendTime, precision: 6); + Assert.Equal(0.016, samples[^1].TrajectoryTime, precision: 6); + Assert.Equal(180.0, samples[^1].JointsDegrees[0], precision: 6); + } + + /// + /// 验证飞拍 speed_ratio 安全队列从连续规划样条直接采样,而不是先把规划轨迹离散成稠密点后再线性插值。 + /// + [Fact] + public void SamplePlannedTrajectory_DirectlyEvaluatesContinuousSplineForSpeedRatio() + { + var trajectory = CreateSingleJointTrajectory(); + + var samples = J519SendTrajectorySampler.SamplePlannedTrajectory( + trajectory, + servoPeriodSeconds: 0.008, + speedRatio: 0.5); + + var sample = Assert.Single(samples.Where(static item => Math.Abs(item.TrajectoryTime - 0.004) < 1e-9)); + Assert.Equal(0.008, sample.SendTime, precision: 6); + Assert.Equal(0.004, sample.TrajectoryTime, precision: 6); + Assert.NotEqual(45.0, sample.JointsDegrees[0], precision: 3); + + var expected = new CubicSplineInterpolator( + trajectory.WaypointTimes.ToArray(), + trajectory.PlannedWaypoints.Select(static waypoint => waypoint.Positions.ToArray()).ToArray()) + .Evaluate(0.004)[0] * 180.0 / Math.PI; + Assert.Equal(expected, sample.JointsDegrees[0], precision: 6); + } + + /// + /// 验证 speed_ratio 等价于执行侧时间轴拉长:原轨迹 0.8 倍执行应与时间整体拉长 1/0.8 的轨迹 1 倍执行一致。 + /// + [Fact] + public void SamplePlannedTrajectory_SpeedRatioMatchesUniformPlanningTimeStretch() + { + var trajectory = CreateSingleJointTrajectory(); + var stretchedTrajectory = StretchTrajectoryTime(trajectory, stretchFactor: 1.0 / 0.8); + + var ratioSamples = J519SendTrajectorySampler.SamplePlannedTrajectory( + trajectory, + servoPeriodSeconds: 0.008, + speedRatio: 0.8); + var stretchedSamples = J519SendTrajectorySampler.SamplePlannedTrajectory( + stretchedTrajectory, + servoPeriodSeconds: 0.008, + speedRatio: 1.0); + + Assert.Equal(stretchedSamples.Count, ratioSamples.Count); + for (var index = 0; index < ratioSamples.Count; index++) + { + Assert.Equal(stretchedSamples[index].SendTime, ratioSamples[index].SendTime, precision: 6); + Assert.Equal(stretchedSamples[index].TrajectoryTime * 0.8, ratioSamples[index].TrajectoryTime, precision: 6); + Assert.Equal(stretchedSamples[index].JointsDegrees[0], ratioSamples[index].JointsDegrees[0], precision: 6); + } + } + + /// + /// 验证空稠密轨迹会直接暴露为调用错误,避免生成无意义下发点。 + /// + [Fact] + public void SampleDenseJointTrajectory_RejectsEmptyDenseTrajectory() + { + var exception = Assert.Throws(() => + J519SendTrajectorySampler.SampleDenseJointTrajectory( + Array.Empty>(), + durationSeconds: 0.016, + servoPeriodSeconds: 0.008, + speedRatio: 1.0)); + + Assert.Contains("稠密关节轨迹为空", exception.Message); + } + + /// + /// 验证非法 speed_ratio 会在公共入口统一拦截。 + /// + [Theory] + [InlineData(0.0)] + [InlineData(double.NaN)] + [InlineData(double.PositiveInfinity)] + public void SampleDenseJointTrajectory_RejectsInvalidSpeedRatio(double speedRatio) + { + var denseTrajectory = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + + Assert.Throws(() => + J519SendTrajectorySampler.SampleDenseJointTrajectory( + denseTrajectory, + durationSeconds: 0.0, + servoPeriodSeconds: 0.008, + speedRatio: speedRatio)); + } + + /// + /// 验证公共诊断行格式与既有 ActualSendTiming 文件保持一致。 + /// + [Fact] + public void BuildTimingRow_UsesLegacyActualSendColumnOrder() + { + var row = J519SendTrajectorySampler.BuildTimingRow(new J519SendSample( + sampleIndex: 2, + sendTime: 0.016, + trajectoryTime: 0.008, + speedRatio: 0.5, + jointsDegrees: [90.0, 0.0, 0.0, 0.0, 0.0, 0.0])); + + Assert.Equal([2.0, 0.016, 0.008, 0.5], row); + } + + /// + /// 验证倍率诊断行在保留旧 4 列的同时追加请求倍率和历史改写次数。 + /// + [Fact] + public void BuildTimingRow_WithRatioDiagnostics_AppendsRequestedRatioAndIterationCount() + { + var sample = new J519SendSample( + sampleIndex: 2, + sendTime: 0.016, + trajectoryTime: 0.0076, + speedRatio: 0.475, + jointsDegrees: [90.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + + var row = J519SendTrajectorySampler.BuildTimingRow( + sample, + requestedSpeedRatio: 0.5, + stretchIterationCount: 1); + + Assert.Equal([2.0, 0.016, 0.0076, 0.475, 0.5, 1.0], row); + } + + /// + /// 构造一个最小单关节规划轨迹,便于验证 speed_ratio 直接样条采样语义。 + /// + private static PlannedTrajectory CreateSingleJointTrajectory() + { + var robot = new RobotProfile( + name: "TestRobot", + modelPath: "Models/Test.robot", + degreesOfFreedom: 1, + jointLimits: [new JointLimit("J1", 100.0, 1000.0, 10000.0)], + jointCouplings: Array.Empty(), + servoPeriod: TimeSpan.FromMilliseconds(8), + triggerPeriod: TimeSpan.FromMilliseconds(8)); + var program = new FlyshotProgram( + name: "spline-sample", + waypoints: + [ + new JointWaypoint([0.0]), + new JointWaypoint([Math.PI / 2.0]), + new JointWaypoint([Math.PI]) + ], + shotFlags: [false, false, false], + offsetValues: [0, 0, 0], + addressGroups: + [ + new IoAddressGroup(Array.Empty()), + new IoAddressGroup(Array.Empty()), + new IoAddressGroup(Array.Empty()) + ]); + + return new PlannedTrajectory( + robot: robot, + originalProgram: program, + plannedWaypoints: program.Waypoints, + waypointTimes: [0.0, 0.008, 0.016], + segmentDurations: [0.008, 0.008], + segmentScales: [1.0, 1.0], + method: PlanningMethod.Icsp, + iterations: 1, + threshold: 0.0); + } + + /// + /// 构造统一拉长时间轴后的轨迹,模拟更低 planning_speed_scale 生成的等几何时间结果。 + /// + private static PlannedTrajectory StretchTrajectoryTime(PlannedTrajectory trajectory, double stretchFactor) + { + return new PlannedTrajectory( + robot: trajectory.Robot, + originalProgram: trajectory.OriginalProgram, + plannedWaypoints: trajectory.PlannedWaypoints, + waypointTimes: trajectory.WaypointTimes.Select(time => time * stretchFactor).ToArray(), + segmentDurations: trajectory.SegmentDurations.Select(duration => duration * stretchFactor).ToArray(), + segmentScales: trajectory.SegmentScales.Select(scale => scale / stretchFactor).ToArray(), + method: trajectory.Method, + iterations: trajectory.Iterations, + threshold: trajectory.Threshold); + } +} diff --git a/tests/Flyshot.Core.Tests/OfflinePlanTests.cs b/tests/Flyshot.Core.Tests/OfflinePlanTests.cs index a536cdd..97a1138 100644 --- a/tests/Flyshot.Core.Tests/OfflinePlanTests.cs +++ b/tests/Flyshot.Core.Tests/OfflinePlanTests.cs @@ -2,7 +2,6 @@ using Flyshot.Core.Config; using Flyshot.Core.Domain; using Flyshot.Core.Planning; using Flyshot.Core.Planning.Export; -using Flyshot.Core.Planning.Kinematics; using Flyshot.Core.Planning.Sampling; using Flyshot.Core.Triggering; using Xunit.Abstractions; @@ -24,26 +23,30 @@ public sealed class OfflinePlanTests /// 使用预设参数生成离线轨迹,输出到 analysis/output/dotnet/。 /// [Theory] - [InlineData("Rvbust/EOL9 EAU 90/eol9_eau_90.json", "FlyingShot/FlyingShot/Models/LR_Mate_200iD_7L.robot", "EOL9_EAU_90", false, 1.0)] - [InlineData("Rvbust/EOL9 EAU 90/eol9_eau_90.json", "FlyingShot/FlyingShot/Models/LR_Mate_200iD_7L.robot", "EOL9_EAU_90", true, 0.9)] - [InlineData("Rvbust/EOL10_EAU_0/EOL10_EAU_0.json", "FlyingShot/FlyingShot/Models/LR_Mate_200iD_7L.robot", "EOL10_EAU_0", false, 1.0)] + [InlineData("Rvbust/EOL9 EAU 90/eol9_eau_90.json", "EOL9_EAU_90", false, 1.0)] + [InlineData("Rvbust/EOL9 EAU 90/eol9_eau_90.json", "EOL9_EAU_90", true, 0.9)] + [InlineData("Rvbust/EOL10_EAU_0/EOL10_EAU_0.json", "EOL10_EAU_0", false, 1.0)] public void GenerateTrajectory_MatchesPythonDemo( string configPath, - string robotModelPath, string trajName, bool useSelfAdapt, double speedRatio) { var workspaceRoot = GetWorkspaceRoot(); + var resolvedConfigPath = Path.Combine(workspaceRoot, configPath); var outputDir = Path.Combine(workspaceRoot, "analysis", "output", "dotnet", $"{trajName}_sr{speedRatio:F2}_{(useSelfAdapt ? "adapt" : "icsp")}"); Directory.CreateDirectory(outputDir); // 1. 加载配置和模型。 - var loadedConfig = new RobotConfigLoader().Load(configPath, repoRoot: workspaceRoot); + var loadedConfig = new RobotConfigLoader().Load(resolvedConfigPath, repoRoot: workspaceRoot); var program = loadedConfig.Programs[trajName]; - var resolvedRobotModelPath = Path.Combine(workspaceRoot, robotModelPath); - var baseProfile = new RobotModelLoader().LoadProfile(resolvedRobotModelPath, loadedConfig.Robot.AccLimitScale, loadedConfig.Robot.JerkLimitScale); - var kinematicsModel = new RobotModelLoader().LoadKinematicsModel(resolvedRobotModelPath); + var resolvedRobotModelPath = Path.Combine(workspaceRoot, "flyshot-replacement", "Config", "Models", "LR_Mate_200iD_7L.json"); + var loadedModel = new RobotModelLoader().LoadProfileAndKinematics( + resolvedRobotModelPath, + loadedConfig.Robot.AccLimitScale, + loadedConfig.Robot.JerkLimitScale); + var baseProfile = loadedModel.Profile; + var kinematicsModel = loadedModel.KinematicsModel; // 2. 应用 speed_ratio 缩放。 var scaledProfile = ScaleRobotProfile(baseProfile, speedRatio); diff --git a/tests/Flyshot.Core.Tests/PlanningCompatibilityTests.cs b/tests/Flyshot.Core.Tests/PlanningCompatibilityTests.cs index 3c59a09..43df597 100644 --- a/tests/Flyshot.Core.Tests/PlanningCompatibilityTests.cs +++ b/tests/Flyshot.Core.Tests/PlanningCompatibilityTests.cs @@ -1,6 +1,7 @@ using Flyshot.Core.Config; using Flyshot.Core.Domain; using Flyshot.Core.Planning; +using Flyshot.Core.Planning.Sampling; using Flyshot.Core.Triggering; namespace Flyshot.Core.Tests; @@ -34,6 +35,30 @@ public sealed class PlanningCompatibilityTests Assert.All(trajectory.WaypointTimes.Zip(trajectory.WaypointTimes.Skip(1)), pair => Assert.True(pair.Second > pair.First)); } + /// + /// 验证普通 ICSP 在最终最优解仍超限时会显式失败,而不是返回不可执行轨迹。 + /// + [Fact] + public void ICspPlanner_Throws_WhenFinalGlobalScaleExceedsOne() + { + var request = new TrajectoryRequest( + robot: CreateRobotProfile([0.1], [0.1], [0.1]), + program: CreateProgram( + new[] + { + new[] { 0.0 }, + new[] { 10.0 }, + new[] { 20.0 }, + new[] { 30.0 } + }), + method: PlanningMethod.Icsp); + + var planner = new ICspPlanner(maxIterations: 0); + + var exception = Assert.Throws(() => planner.Plan(request)); + Assert.Contains("global_scale", exception.Message); + } + /// /// 验证 speed09 风格的大跳变样本在 self-adapt-icsp 下会通过补中点收敛。 /// @@ -42,7 +67,7 @@ public sealed class PlanningCompatibilityTests { var workspaceRoot = GetWorkspaceRoot(); var configPath = Path.Combine(workspaceRoot, "Rvbust", "EOL9 EAU 90", "eol9_eau_90.json"); - var modelPath = Path.Combine(workspaceRoot, "FlyingShot", "FlyingShot", "Models", "LR_Mate_200iD_7L.robot"); + var modelPath = Path.Combine(workspaceRoot, "flyshot-replacement", "Config", "Models", "LR_Mate_200iD_7L.json"); var config = new RobotConfigLoader().Load(configPath); var baseProfile = new RobotModelLoader().LoadProfile(modelPath, config.Robot.AccLimitScale, config.Robot.JerkLimitScale); @@ -145,6 +170,241 @@ public sealed class PlanningCompatibilityTests Assert.Equal(1, doEvent.WaypointIndex); Assert.Equal(1, doEvent.OffsetCycles); Assert.Equal(2, doEvent.HoldCycles); + Assert.NotNull(doEvent.ReferenceJointsDegrees); + Assert.Equal(RadiansToDegrees(1.0), doEvent.ReferenceJointsDegrees![0], precision: 6); + Assert.Equal(0.0, doEvent.ReferenceJointsDegrees![1], precision: 6); + } + + /// + /// 验证时间轴会把示教点关节角保存到 DO 事件里,供运行时在近时窗内做最小关节差绑定。 + /// + [Fact] + public void ShotTimelineBuilder_PopulatesReferenceJointsDegreesForRuntimeBinding() + { + var robot = CreateRobotProfile([1, 1], [1, 1], [1, 1]); + var program = new FlyshotProgram( + name: "demo", + waypoints: + [ + new JointWaypoint([0.0, 0.0]), + new JointWaypoint([Math.PI / 6.0, -Math.PI / 4.0]) + ], + shotFlags: [false, true], + offsetValues: [0, 0], + addressGroups: + [ + new IoAddressGroup(Array.Empty()), + new IoAddressGroup([1]) + ]); + + var trajectory = new PlannedTrajectory( + robot: robot, + originalProgram: program, + plannedWaypoints: program.Waypoints, + waypointTimes: [0.0, 0.5], + segmentDurations: [0.5], + segmentScales: [1.0], + method: PlanningMethod.Icsp, + iterations: 1, + threshold: 0.0); + + var timeline = new ShotTimelineBuilder(new WaypointTimestampResolver()) + .Build(trajectory, holdCycles: 1, samplePeriod: TimeSpan.FromMilliseconds(8)); + + var trigger = Assert.Single(timeline.TriggerTimeline); + Assert.NotNull(trigger.ReferenceJointsDegrees); + Assert.Equal(30.0, trigger.ReferenceJointsDegrees![0], precision: 6); + Assert.Equal(-45.0, trigger.ReferenceJointsDegrees![1], precision: 6); + } + + /// + /// 验证稠密轨迹生成后会复核离散加速度,避免采样结果绕过 ICSP 段 scale 校验。 + /// + [Fact] + public void TrajectoryLimitValidator_Throws_WhenDenseTrajectoryAccelerationExceedsLimit() + { + var robot = CreateRobotProfile([100.0], [10.0], [1000.0]); + + var exception = Assert.Throws(() => + TrajectoryLimitValidator.ValidateDenseJointTrajectory( + robot, + [ + [0.0, 0.0], + [0.008, 0.0], + [0.016, 0.01] + ], + trajectoryName: "dense-acceleration-check")); + + Assert.Contains("加速度", exception.Message); + Assert.Contains("dense-acceleration-check", exception.Message); + } + + /// + /// 验证飞拍链路临时关闭 jerk 复核时,仍保留速度/加速度校验,但不会再被 jerk 单独阻断。 + /// + [Fact] + public void TrajectoryLimitValidator_CanSkipJerkValidation_ForFlyshotPath() + { + var robot = CreateRobotProfile([100.0], [1000.0], [10.0]); + IReadOnlyList> rows = + [ + [0.0, 0.0], + [0.008, 0.0], + [0.016, 0.01], + [0.024, 0.02] + ]; + + var exception = Assert.Throws(() => + TrajectoryLimitValidator.ValidateDenseJointTrajectory( + robot, + rows, + trajectoryName: "dense-jerk-check")); + + Assert.Contains("Jerk", exception.Message); + + TrajectoryLimitValidator.ValidateDenseJointTrajectory( + robot, + rows, + trajectoryName: "dense-jerk-check-skip", + validateJerk: false); + } + + /// + /// 验证飞拍最终 J519 队列的 jerk 硬约束使用严格限值,不再沿用历史上放大 4 倍的宽容口径。 + /// + [Fact] + public void TrajectoryLimitValidator_ThrowsForJ519Jerk_WhenStrictFinalQueueValidationIsEnabled() + { + var robot = CreateRobotProfile([100.0], [1000.0], [100.0]); + var samples = new[] + { + new J519SendSample(0, 0.000, 0.000, 1.0, [0.0]), + new J519SendSample(1, 0.008, 0.008, 1.0, [0.0]), + new J519SendSample(2, 0.016, 0.016, 1.0, [0.003754]), + new J519SendSample(3, 0.024, 0.024, 1.0, [0.007508]) + }; + + var exception = Assert.Throws(() => + TrajectoryLimitValidator.ValidateJ519SendSamples( + robot, + samples, + trajectoryName: "strict-final-j519", + validateJerk: true)); + + Assert.Contains("Jerk", exception.Message); + Assert.Contains("strict-final-j519", exception.Message); + } + + /// + /// 验证飞拍最终发送队列在请求倍率下严格 jerk 超限时直接拒绝执行,而不是自动改写 speed_ratio。 + /// + [Fact] + public void FlyshotExecutionSendSequenceBuilder_ThrowsInsteadOfRewritingRequestedSpeedRatio() + { + var robot = CreateRobotProfile([100.0], [10000.0], [800.0]); + var program = CreateProgram( + [ + [0.0], + [0.001] + ]); + var trajectory = new PlannedTrajectory( + robot: robot, + originalProgram: program, + plannedWaypoints: program.Waypoints, + waypointTimes: [0.0, 0.024], + segmentDurations: [0.024], + segmentScales: [1.0], + method: PlanningMethod.Icsp, + iterations: 1, + threshold: 0.0); + var result = new TrajectoryResult( + programName: "strict-builder", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.024), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 2, + plannedWaypointCount: 2); + var initialSamples = J519SendTrajectorySampler.SamplePlannedTrajectory( + trajectory, + servoPeriodSeconds: 0.008, + speedRatio: 1.0); + + Assert.Throws(() => + TrajectoryLimitValidator.ValidateJ519SendSamples( + robot, + initialSamples, + trajectoryName: "strict-builder-initial", + validateJerk: true)); + + var exception = Assert.Throws(() => + FlyshotExecutionSendSequenceBuilder.Build( + robot, + trajectory, + result, + servoPeriodSeconds: 0.008, + requestedSpeedRatio: 1.0)); + + Assert.Contains("speed_ratio=1.000000", exception.Message); + Assert.Contains("planning_speed_scale", exception.Message); + } + + /// + /// 验证飞拍最终发送队列通过严格校验时,请求 speed_ratio 会原样成为最终倍率。 + /// + [Fact] + public void FlyshotExecutionSendSequenceBuilder_KeepsRequestedSpeedRatioWhenQueuePasses() + { + var robot = CreateRobotProfile([100.0], [10000.0], [10000.0]); + var program = CreateProgram( + [ + [0.0], + [0.001] + ]); + var trajectory = new PlannedTrajectory( + robot: robot, + originalProgram: program, + plannedWaypoints: program.Waypoints, + waypointTimes: [0.0, 0.024], + segmentDurations: [0.024], + segmentScales: [1.0], + method: PlanningMethod.Icsp, + iterations: 1, + threshold: 0.0); + var result = new TrajectoryResult( + programName: "rewrite-speedratio-builder", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(0.024), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 2, + plannedWaypointCount: 2); + + var prepared = FlyshotExecutionSendSequenceBuilder.Build( + robot, + trajectory, + result, + servoPeriodSeconds: 0.008, + requestedSpeedRatio: 0.8); + + Assert.Equal(0.8, prepared.RequestSpeedRatio, precision: 6); + Assert.Equal(0.8, prepared.FinalSpeedRatio, precision: 6); + Assert.Equal(0, prepared.StretchIterationCount); + Assert.All(prepared.Samples, sample => Assert.Equal(0.8, sample.SpeedRatio, precision: 6)); + Assert.All(prepared.TimingRows, row => + { + Assert.Equal(0.8, row[3], precision: 6); + Assert.Equal(0.8, row[4], precision: 6); + Assert.Equal(0.0, row[5], precision: 6); + }); } /// @@ -224,4 +484,9 @@ public sealed class PlanningCompatibilityTests throw new DirectoryNotFoundException("Unable to locate the flyshot workspace root."); } + + private static double RadiansToDegrees(double radians) + { + return radians * 180.0 / Math.PI; + } } diff --git a/tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs b/tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs new file mode 100644 index 0000000..ec8c673 --- /dev/null +++ b/tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs @@ -0,0 +1,2586 @@ +using Flyshot.ControllerClientCompat; +using Flyshot.Core.Config; +using Flyshot.Core.Domain; +using Flyshot.Core.Planning; +using Flyshot.Core.Planning.Sampling; +using Flyshot.Runtime.Common; +using Flyshot.Runtime.Fanuc; +using Flyshot.Runtime.Fanuc.Protocol; +using System.Globalization; +using System.Reflection; + +namespace Flyshot.Core.Tests; + +/// +/// 验证最小运行时编排链路会把规划结果交给控制器运行时,而不是停留在兼容层内存状态。 +/// +public sealed class RuntimeOrchestrationTests +{ + /// + /// 验证 FANUC 最小运行时执行轨迹后会更新状态快照与最终关节位置。 + /// + [Fact] + public void FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions() + { + var runtime = new FanucControllerRuntime(); + var robot = TestRobotFactory.CreateRobotProfile(); + runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD"); + runtime.SetActiveController(sim: true); + runtime.Connect("192.168.10.101"); + runtime.EnableRobot(bufferSize: 2); + + var result = new TrajectoryResult( + programName: "demo", + method: PlanningMethod.Icsp, + isValid: true, + duration: TimeSpan.FromSeconds(1.2), + shotEvents: Array.Empty(), + triggerTimeline: Array.Empty(), + artifacts: Array.Empty(), + failureReason: null, + usedCache: false, + originalWaypointCount: 4, + plannedWaypointCount: 4); + + runtime.ExecuteTrajectory(result, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + + var snapshot = runtime.GetSnapshot(); + Assert.Equal("Connected", snapshot.ConnectionState); + Assert.False(snapshot.IsInMotion); + Assert.Equal([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], snapshot.JointPositions); + } + + /// + /// 验证真机运行时会把 TCP 10010 状态通道健康度映射为可诊断连接状态。 + /// + [Theory] + [InlineData(FanucStateConnectionState.Connected, false, "Connected")] + [InlineData(FanucStateConnectionState.Connected, true, "StateTimeout")] + [InlineData(FanucStateConnectionState.TimedOut, true, "StateTimeout")] + [InlineData(FanucStateConnectionState.Reconnecting, true, "Reconnecting")] + [InlineData(FanucStateConnectionState.Disconnected, false, "Disconnected")] + public void FanucControllerRuntime_ResolveRealConnectionState_ReflectsStateChannelHealth( + FanucStateConnectionState state, + bool isFrameStale, + string expected) + { + var status = new FanucStateClientStatus( + state, + isFrameStale, + lastFrameAt: null, + reconnectAttemptCount: 0, + lastErrorMessage: null); + + var actual = FanucControllerRuntime.ResolveRealConnectionState(status); + + Assert.Equal(expected, actual); + } + + /// + /// 验证只有已连接且未陈旧的 TCP 10010 帧会被 runtime 当作当前机器人状态使用。 + /// + [Theory] + [InlineData(FanucStateConnectionState.Connected, false, true)] + [InlineData(FanucStateConnectionState.Connected, true, false)] + [InlineData(FanucStateConnectionState.Reconnecting, false, false)] + [InlineData(FanucStateConnectionState.TimedOut, false, false)] + [InlineData(FanucStateConnectionState.Disconnected, false, false)] + public void FanucControllerRuntime_ShouldUseStateFrame_RequiresConnectedFreshState( + FanucStateConnectionState state, + bool isFrameStale, + bool expected) + { + var status = new FanucStateClientStatus( + state, + isFrameStale, + lastFrameAt: null, + reconnectAttemptCount: 0, + lastErrorMessage: null); + + var actual = FanucControllerRuntime.ShouldUseStateFrame(status); + + Assert.Equal(expected, actual); + } + + /// + /// 验证普通轨迹会先进入 ICSP 规划,并沿用 ICSP 对示教点数量的约束。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_RejectsThreeTeachPoints() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + + void Act() => + orchestrator.PlanOrdinaryTrajectory( + robot, + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.5, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0] + ]); + + Assert.Throws(Act); + } + + /// + /// 验证已上传飞拍轨迹会经过 self-adapt-icsp 并生成拍照触发时间轴。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_BuildsShotTimeline() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + + var bundle = orchestrator.PlanUploadedFlyshot(robot, uploaded); + + Assert.True(bundle.Result.IsValid); + Assert.Single(bundle.Result.ShotEvents); + Assert.Single(bundle.Result.TriggerTimeline); + } + + /// + /// 验证飞拍规划会把规划限速倍率纳入速度/加速度/Jerk 限制,而不是复用运行时下发倍率。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_AppliesPlanningSpeedScaleToLimits() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + + var fullSpeed = orchestrator.PlanUploadedFlyshot(robot, uploaded, planningSpeedScale: 1.0); + var halfSpeed = orchestrator.PlanUploadedFlyshot(robot, uploaded, planningSpeedScale: 0.5); + + Assert.True( + halfSpeed.Result.Duration.TotalSeconds > fullSpeed.Result.Duration.TotalSeconds * 1.9, + $"半速规划时长应接近全速的 2 倍,实际 full={fullSpeed.Result.Duration.TotalSeconds}, half={halfSpeed.Result.Duration.TotalSeconds}"); + } + + /// + /// 验证飞拍生成轨迹时会把 speed_ratio 折算进有效规划倍率,使倍率乘积相同的配置生成等价轨迹。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_UsesSpeedRatioAsPlanningScaleFactor() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + var planningSideSlowdown = CreateFlyshotSettings(planningSpeedScale: 0.45, speedRatio: 1.0); + var runtimeSideSlowdown = CreateFlyshotSettings(planningSpeedScale: 0.9, speedRatio: 0.5); + + var planningSideBundle = orchestrator.PlanUploadedFlyshot(robot, uploaded, settings: planningSideSlowdown); + var runtimeSideBundle = orchestrator.PlanUploadedFlyshot(robot, uploaded, settings: runtimeSideSlowdown); + + Assert.Equal(planningSideBundle.Result.Duration.TotalSeconds, runtimeSideBundle.Result.Duration.TotalSeconds, precision: 6); + Assert.Equal(planningSideBundle.ExecutionTrajectory.WaypointTimes.Count, runtimeSideBundle.ExecutionTrajectory.WaypointTimes.Count); + for (var index = 0; index < planningSideBundle.ExecutionTrajectory.WaypointTimes.Count; index++) + { + Assert.Equal( + planningSideBundle.ExecutionTrajectory.WaypointTimes[index], + runtimeSideBundle.ExecutionTrajectory.WaypointTimes[index], + precision: 6); + } + + Assert.Equal(planningSideBundle.Result.DenseJointTrajectory!.Count, runtimeSideBundle.Result.DenseJointTrajectory!.Count); + for (var rowIndex = 0; rowIndex < planningSideBundle.Result.DenseJointTrajectory.Count; rowIndex++) + { + var planningSideRow = planningSideBundle.Result.DenseJointTrajectory[rowIndex]; + var runtimeSideRow = runtimeSideBundle.Result.DenseJointTrajectory[rowIndex]; + Assert.Equal(planningSideRow.Count, runtimeSideRow.Count); + for (var columnIndex = 0; columnIndex < planningSideRow.Count; columnIndex++) + { + Assert.Equal(planningSideRow[columnIndex], runtimeSideRow[columnIndex], precision: 6); + } + } + } + + /// + /// 验证普通轨迹生成也会把 speed_ratio 折算进规划倍率,避免把限速留到发送阶段处理。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_UsesSpeedRatioAsPlanningScaleFactor() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var waypoints = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.001, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.002, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.003, 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + + var planningSideBundle = orchestrator.PlanOrdinaryTrajectory( + robot, + waypoints, + planningSpeedScale: 0.45); + var runtimeSideBundle = orchestrator.PlanOrdinaryTrajectory( + robot, + waypoints, + planningSpeedScale: 0.9, + speedRatio: 0.5); + + Assert.Equal(planningSideBundle.Result.Duration.TotalSeconds, runtimeSideBundle.Result.Duration.TotalSeconds, precision: 6); + Assert.Equal(planningSideBundle.Result.DenseJointTrajectory!.Count, runtimeSideBundle.Result.DenseJointTrajectory!.Count); + for (var rowIndex = 0; rowIndex < planningSideBundle.Result.DenseJointTrajectory.Count; rowIndex++) + { + var planningSideRow = planningSideBundle.Result.DenseJointTrajectory[rowIndex]; + var runtimeSideRow = runtimeSideBundle.Result.DenseJointTrajectory[rowIndex]; + for (var columnIndex = 0; columnIndex < planningSideRow.Count; columnIndex++) + { + Assert.Equal(planningSideRow[columnIndex], runtimeSideRow[columnIndex], precision: 6); + } + } + } + + /// + /// 验证飞拍缓存键包含规划限速倍率,避免降速验证时误用 100% 速度下的规划结果。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_CacheKeyIncludesPlanningSpeedScale() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + var options = new FlyshotExecutionOptions(useCache: true); + + var fullSpeed = orchestrator.PlanUploadedFlyshot(robot, uploaded, options, planningSpeedScale: 1.0); + var halfSpeed = orchestrator.PlanUploadedFlyshot(robot, uploaded, options, planningSpeedScale: 0.5); + + Assert.False(halfSpeed.Result.UsedCache); + Assert.True(halfSpeed.Result.Duration > fullSpeed.Result.Duration); + } + + /// + /// 验证飞拍编排会使用 RobotConfig.json 中的 IO 保持周期。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_UsesRobotSettingsForHoldCycles() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + var settings = new CompatibilityRobotSettings( + useDo: true, + ioAddresses: [7, 8], + ioKeepCycles: 4, + triggerSampleIndexOffsetCycles: 7, + accLimitScale: 1.0, + jerkLimitScale: 1.0, + adaptIcspTryNum: 5); + + var bundle = orchestrator.PlanUploadedFlyshot(robot, uploaded, settings: settings); + + var doEvent = Assert.Single(bundle.Result.TriggerTimeline); + Assert.Equal(4, doEvent.HoldCycles); + } + + /// + /// 验证 RobotConfig.json 关闭 use_do 时仍保留 ShotEvent 诊断信息,但不生成伺服 DO 事件。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_SuppressesDoTimeline_WhenUseDoIsFalse() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + var settings = new CompatibilityRobotSettings( + useDo: false, + ioAddresses: [7, 8], + ioKeepCycles: 4, + triggerSampleIndexOffsetCycles: 7, + accLimitScale: 1.0, + jerkLimitScale: 1.0, + adaptIcspTryNum: 5); + + var bundle = orchestrator.PlanUploadedFlyshot(robot, uploaded, settings: settings); + + Assert.Single(bundle.Result.ShotEvents); + Assert.Empty(bundle.Result.TriggerTimeline); + } + + /// + /// 验证普通轨迹规划后会生成稠密关节采样序列。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_ReturnsDenseJointTrajectory() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + + var bundle = orchestrator.PlanOrdinaryTrajectory( + robot, + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.1, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.2, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.3, 0.0, 0.0, 0.0, 0.0, 0.0] + ]); + + Assert.NotNull(bundle.Result.DenseJointTrajectory); + Assert.NotEmpty(bundle.Result.DenseJointTrajectory); + + // 验证时间单调递增。 + var times = bundle.Result.DenseJointTrajectory.Select(static row => row[0]).ToArray(); + for (var i = 1; i < times.Length; i++) + { + Assert.True(times[i] > times[i - 1], $"采样时间点应在索引 {i} 处单调递增。"); + } + + // 验证每行包含时间 + 6 个关节值。 + Assert.All(bundle.Result.DenseJointTrajectory, row => Assert.Equal(7, row.Count)); + } + + /// + /// 验证飞拍轨迹规划后的稠密采样时间轴与伺服周期一致。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_DenseTrajectoryUsesServoPeriod() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + + var bundle = orchestrator.PlanUploadedFlyshot(robot, uploaded); + + Assert.NotNull(bundle.Result.DenseJointTrajectory); + Assert.True(bundle.Result.DenseJointTrajectory.Count > 1); + + // 采样周期应为 8ms(伺服周期)。 + var firstDt = bundle.Result.DenseJointTrajectory[1][0] - bundle.Result.DenseJointTrajectory[0][0]; + Assert.Equal(0.008, firstDt, precision: 3); + } + + /// + /// 验证可直接使用宿主输出目录 Config/RobotConfig.json 中的真实 UTTC_MS11 示教点, + /// 在 1x 规划速度下生成一条完整飞拍轨迹。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_FromHostRuntimeRobotConfig_GeneratesCompleteTrajectoryAtOneX() + { + var fixture = LoadUttcMs11RuntimeFixture(); + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + + var bundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: EnableSmoothStartStopTiming(fixture.Settings), + planningSpeedScale: 1.0); + + Assert.Equal("UTTC_MS11", bundle.Result.ProgramName); + Assert.NotNull(bundle.Result.DenseJointTrajectory); + Assert.True(bundle.Result.DenseJointTrajectory.Count > fixture.Uploaded.Waypoints.Count); + Assert.True(bundle.Result.Duration.TotalSeconds > 0.0); + Assert.Equal(fixture.Uploaded.ShotFlags.Count(static flag => flag), bundle.Result.ShotEvents.Count); + Assert.Equal(fixture.Uploaded.ShotFlags.Count(static flag => flag), bundle.Result.TriggerTimeline.Count); + + var firstDt = bundle.Result.DenseJointTrajectory[1][0] - bundle.Result.DenseJointTrajectory[0][0]; + Assert.Equal(fixture.Robot.ServoPeriod.TotalSeconds, firstDt, precision: 6); + + AssertJointRadiansEqual(fixture.Uploaded.Waypoints[0], bundle.Result.DenseJointTrajectory[0].Skip(1).ToArray()); + AssertJointRadiansEqual(fixture.Uploaded.Waypoints[^1], bundle.Result.DenseJointTrajectory[^1].Skip(1).ToArray()); + } + + /// + /// 验证现场 UTTC_MS11 配置使用通用规划速度倍率拉长时间轴,并关闭二次平滑时间重映射。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_FromRuntimeLegacyFitConfig_UsesPlanningScaleWithoutSecondRemap() + { + var fixture = LoadUttcMs11RuntimeFixture(); + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var legacyFitSettings = DisableSmoothStartStopTiming(fixture.Settings); + + var bundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: legacyFitSettings); + var baselineBundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: EnableSmoothStartStopTiming(fixture.Settings), + planningSpeedScale: 1.0); + var executionDense = TrajectorySampler.SampleJointTrajectory( + bundle.ExecutionTrajectory, + samplePeriod: fixture.Robot.ServoPeriod.TotalSeconds); + + Assert.Equal(0.74227, legacyFitSettings.PlanningSpeedScale, precision: 6); + Assert.False(legacyFitSettings.SmoothStartStopTiming); + Assert.Equal(fixture.Uploaded.Waypoints.Count, bundle.PlannedTrajectory.WaypointTimes.Count); + Assert.Equal( + baselineBundle.PlannedTrajectory.WaypointTimes[^1] / legacyFitSettings.PlanningSpeedScale, + bundle.PlannedTrajectory.WaypointTimes[^1], + precision: 6); + Assert.Equal(7.403046, bundle.PlannedTrajectory.WaypointTimes[^1], precision: 3); + + // 关闭二次时间重映射时,稠密点应和最终执行时间轴自洽;若离散限幅额外拉长,也要反映在 ExecutionTrajectory 上。 + Assert.Equal(executionDense.Count, bundle.Result.DenseJointTrajectory!.Count); + Assert.Equal(executionDense[1][1], bundle.Result.DenseJointTrajectory[1][1], precision: 12); + Assert.True(bundle.ExecutionTrajectory.WaypointTimes[^1] >= bundle.PlannedTrajectory.WaypointTimes[^1]); + Assert.True( + Math.Abs(bundle.ExecutionTrajectory.WaypointTimes[^1] - bundle.Result.Duration.TotalSeconds) < 1e-6, + $"执行时长应保留最终执行时间轴终点,execution={bundle.ExecutionTrajectory.WaypointTimes[^1]}, result={bundle.Result.Duration.TotalSeconds}"); + } + + /// + /// 使用运行时 RobotConfig.json 中的真实 UTTC_MS11 示教点,导出首尾 10 点整形前后的逐点对比数据, + /// 便于和现场报警时间段逐项核对关节、速度、加速度与跃度。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_FromHostRuntimeRobotConfig_ExportsEdgeComparisonAtOneX() + { + const int edgePointCount = 10; + var fixture = LoadUttcMs11RuntimeFixture(); + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var bundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: fixture.Settings, + planningSpeedScale: 1.0); + var rawDense = TrajectorySampler.SampleJointTrajectory( + bundle.PlannedTrajectory, + samplePeriod: fixture.Robot.ServoPeriod.TotalSeconds); + var shapedDense = bundle.Result.DenseJointTrajectory!; + + var outputDir = Path.Combine( + fixture.ConfigRoot, + "Data", + "UTTC_MS11_EdgeShapeDebug_1x", + DateTime.Now.ToString("yyyyMMdd_HHmmss_fff", CultureInfo.InvariantCulture)); + Directory.CreateDirectory(outputDir); + + var leadingPath = Path.Combine(outputDir, "Leading10Comparison.csv"); + var trailingPath = Path.Combine(outputDir, "Trailing10Comparison.csv"); + var summaryPath = Path.Combine(outputDir, "Summary.txt"); + + WriteEdgeComparisonCsv(leadingPath, rawDense, shapedDense, 0, Math.Min(edgePointCount, shapedDense.Count)); + WriteEdgeComparisonCsv( + trailingPath, + rawDense, + shapedDense, + Math.Max(0, shapedDense.Count - edgePointCount), + Math.Min(edgePointCount, shapedDense.Count)); + + var summaryLines = new List + { + $"program=UTTC_MS11", + $"planning_speed_scale=1.000000", + $"smooth_start_stop_timing=true", + $"servo_period_seconds={fixture.Robot.ServoPeriod.TotalSeconds.ToString("F6", CultureInfo.InvariantCulture)}", + $"dense_point_count={shapedDense.Count}", + $"leading_first_step_raw_deg={ComputeStepDegrees(rawDense, 1, 0).ToString("F6", CultureInfo.InvariantCulture)}", + $"leading_first_step_shaped_deg={ComputeStepDegrees(shapedDense, 1, 0).ToString("F6", CultureInfo.InvariantCulture)}", + $"trailing_last_step_raw_deg={ComputeStepDegrees(rawDense, rawDense.Count - 1, 0).ToString("F6", CultureInfo.InvariantCulture)}", + $"trailing_last_step_shaped_deg={ComputeStepDegrees(shapedDense, shapedDense.Count - 1, 0).ToString("F6", CultureInfo.InvariantCulture)}", + $"leading_window_max_abs_raw_jerk_deg_s3={ComputeWindowMaxAbsJerkDegrees(rawDense, 0, Math.Min(edgePointCount, rawDense.Count)).ToString("F6", CultureInfo.InvariantCulture)}", + $"leading_window_max_abs_shaped_jerk_deg_s3={ComputeWindowMaxAbsJerkDegrees(shapedDense, 0, Math.Min(edgePointCount, shapedDense.Count)).ToString("F6", CultureInfo.InvariantCulture)}", + $"trailing_window_max_abs_raw_jerk_deg_s3={ComputeWindowMaxAbsJerkDegrees(rawDense, Math.Max(0, rawDense.Count - edgePointCount), Math.Min(edgePointCount, rawDense.Count)).ToString("F6", CultureInfo.InvariantCulture)}", + $"trailing_window_max_abs_shaped_jerk_deg_s3={ComputeWindowMaxAbsJerkDegrees(shapedDense, Math.Max(0, shapedDense.Count - edgePointCount), Math.Min(edgePointCount, shapedDense.Count)).ToString("F6", CultureInfo.InvariantCulture)}", + $"leading_csv={leadingPath}", + $"trailing_csv={trailingPath}" + }; + + File.WriteAllLines(summaryPath, summaryLines); + + Assert.True(File.Exists(leadingPath)); + Assert.True(File.Exists(trailingPath)); + Assert.True(File.Exists(summaryPath)); + } + + /// + /// 验证运行时 UTTC_MS11 在 1x 下的平滑起停会降低首尾窗口最大跃度,而不是只把第一步压小。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_FromHostRuntimeRobotConfig_ReducesEdgeWindowJerkAtOneX() + { + const int edgePointCount = 10; + var fixture = LoadUttcMs11RuntimeFixture(); + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var bundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: EnableSmoothStartStopTiming(fixture.Settings), + planningSpeedScale: 1.0); + var rawDense = TrajectorySampler.SampleJointTrajectory( + bundle.PlannedTrajectory, + samplePeriod: fixture.Robot.ServoPeriod.TotalSeconds); + var shapedDense = bundle.Result.DenseJointTrajectory!; + + var rawLeadingMaxJerk = ComputeWindowMaxAbsJerkDegrees(rawDense, 0, Math.Min(edgePointCount, rawDense.Count)); + var shapedLeadingMaxJerk = ComputeWindowMaxAbsJerkDegrees(shapedDense, 0, Math.Min(edgePointCount, shapedDense.Count)); + var rawTrailingMaxJerk = ComputeWindowMaxAbsJerkDegrees(rawDense, Math.Max(0, rawDense.Count - edgePointCount), Math.Min(edgePointCount, rawDense.Count)); + var shapedTrailingMaxJerk = ComputeWindowMaxAbsJerkDegrees(shapedDense, Math.Max(0, shapedDense.Count - edgePointCount), Math.Min(edgePointCount, shapedDense.Count)); + + Assert.True( + shapedLeadingMaxJerk < rawLeadingMaxJerk, + $"首段最大跃度应下降,raw={rawLeadingMaxJerk:F6}, shaped={shapedLeadingMaxJerk:F6}"); + Assert.True( + shapedTrailingMaxJerk < rawTrailingMaxJerk, + $"尾段最大跃度应下降,raw={rawTrailingMaxJerk:F6}, shaped={shapedTrailingMaxJerk:F6}"); + } + + /// + /// 验证运行时 UTTC_MS11 在 1x 下的平滑起停会缩小首尾第一步, + /// 但不能把首尾直接压成静止平台,否则现场执行会变成先停住再起步。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_FromHostRuntimeRobotConfig_ReducesEdgeStepWithoutFlatteningToZeroAtOneX() + { + var fixture = LoadUttcMs11RuntimeFixture(); + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var bundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: EnableSmoothStartStopTiming(fixture.Settings), + planningSpeedScale: 1.0); + var rawDense = TrajectorySampler.SampleJointTrajectory( + bundle.PlannedTrajectory, + samplePeriod: fixture.Robot.ServoPeriod.TotalSeconds); + var shapedDense = bundle.Result.DenseJointTrajectory!; + + var rawLeadingFirstStep = Math.Abs(ComputeStepDegrees(rawDense, 1, 0)); + var shapedLeadingFirstStep = Math.Abs(ComputeStepDegrees(shapedDense, 1, 0)); + var rawTrailingLastStep = Math.Abs(ComputeStepDegrees(rawDense, rawDense.Count - 1, 0)); + var shapedTrailingLastStep = Math.Abs(ComputeStepDegrees(shapedDense, shapedDense.Count - 1, 0)); + + Assert.True( + shapedLeadingFirstStep < rawLeadingFirstStep, + $"首段第一步应变小,raw={rawLeadingFirstStep:F6}, shaped={shapedLeadingFirstStep:F6}"); + Assert.True( + shapedTrailingLastStep < rawTrailingLastStep, + $"尾段最后一步应变小,raw={rawTrailingLastStep:F6}, shaped={shapedTrailingLastStep:F6}"); + Assert.True( + shapedLeadingFirstStep > 1e-6, + $"首段第一步不应被压成 0,actual={shapedLeadingFirstStep:F9}"); + Assert.True( + shapedTrailingLastStep > 1e-6, + $"尾段最后一步不应被压成 0,actual={shapedTrailingLastStep:F9}"); + } + + /// + /// 对比真实可运行轨迹前 0.04s 反推出的局部三次边界特征与当前首段样条系数, + /// 用于判断当前 clamped-zero 边界是否已经和现场可运行轨迹明显偏离。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_FromHostRuntimeRobotConfig_ExportsLeadingBoundaryComparison() + { + var fixture = LoadUttcMs11RuntimeFixture(); + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var bundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: fixture.Settings, + planningSpeedScale: 1.0); + var applyMethod = typeof(ControllerClientTrajectoryOrchestrator).GetMethod( + "ApplySmoothStartStopTiming", + BindingFlags.Static | BindingFlags.NonPublic); + Assert.NotNull(applyMethod); + var executionTrajectory = Assert.IsType(applyMethod!.Invoke(null, [bundle.PlannedTrajectory])); + var spline = new CubicSplineInterpolator( + executionTrajectory.WaypointTimes.ToArray(), + executionTrajectory.PlannedWaypoints.Select(static waypoint => waypoint.Positions.ToArray()).ToArray()); + + var comparisonPath = Path.Combine( + fixture.ConfigRoot, + "Data", + "UTTC_MS11_EdgeShapeDebug_1x", + DateTime.Now.ToString("yyyyMMdd_HHmmss_fff", CultureInfo.InvariantCulture), + "LeadingBoundaryComparison.txt"); + Directory.CreateDirectory(Path.GetDirectoryName(comparisonPath)!); + + var realPath = Path.Combine( + TestRobotFactory.GetReplacementRoot(), + "..", + "Rvbust", + "前两个点正常 飞拍失败的运行", + "真实可运行的飞拍轨迹点位JointDetialTraj.txt"); + var realRows = File.ReadLines(realPath) + .TakeWhile(static line => !string.IsNullOrWhiteSpace(line)) + .Select(ParseSpaceSeparatedDoubles) + .Take(4) + .ToArray(); + Assert.Equal(4, realRows.Length); + + var fieldH = typeof(CubicSplineInterpolator).GetField("_h", BindingFlags.Instance | BindingFlags.NonPublic); + var fieldA = typeof(CubicSplineInterpolator).GetField("_a", BindingFlags.Instance | BindingFlags.NonPublic); + var fieldB = typeof(CubicSplineInterpolator).GetField("_b", BindingFlags.Instance | BindingFlags.NonPublic); + var fieldC = typeof(CubicSplineInterpolator).GetField("_c", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(fieldH); + Assert.NotNull(fieldA); + Assert.NotNull(fieldB); + Assert.NotNull(fieldC); + + var segmentDurations = Assert.IsType(fieldH!.GetValue(spline)); + var coeffA = Assert.IsType(fieldA!.GetValue(spline)); + var coeffB = Assert.IsType(fieldB!.GetValue(spline)); + var coeffC = Assert.IsType(fieldC!.GetValue(spline)); + + var lines = new List + { + $"real_path={Path.GetFullPath(realPath)}", + $"current_segment0_h_seconds={segmentDurations[0].ToString("F9", CultureInfo.InvariantCulture)}", + $"real_window_t0={realRows[0][0].ToString("F9", CultureInfo.InvariantCulture)}", + $"real_window_t1={realRows[1][0].ToString("F9", CultureInfo.InvariantCulture)}", + $"real_window_t2={realRows[2][0].ToString("F9", CultureInfo.InvariantCulture)}", + $"real_window_t3={realRows[3][0].ToString("F9", CultureInfo.InvariantCulture)}" + }; + + for (var jointIndex = 0; jointIndex < 6; jointIndex++) + { + var currentStartVelocityDeg = RadiansToDegrees(coeffC[0][jointIndex]); + var currentStartAccelerationDeg = RadiansToDegrees(2.0 * coeffB[0][jointIndex]); + var currentJerkDeg = RadiansToDegrees(6.0 * coeffA[0][jointIndex]); + var currentEndVelocityDeg = RadiansToDegrees( + (3.0 * coeffA[0][jointIndex] * segmentDurations[0] * segmentDurations[0]) + + (2.0 * coeffB[0][jointIndex] * segmentDurations[0]) + + coeffC[0][jointIndex]); + + var localRealFit = FitCubicFromFirstFourRows(realRows, jointIndex + 1); + var realStartVelocityDeg = localRealFit.Coefficient1; + var realStartAccelerationDeg = 2.0 * localRealFit.Coefficient2; + var realJerkDeg = 6.0 * localRealFit.Coefficient3; + var realEndVelocityDeg = (3.0 * localRealFit.Coefficient3 * localRealFit.LastLocalTime * localRealFit.LastLocalTime) + + (2.0 * localRealFit.Coefficient2 * localRealFit.LastLocalTime) + + localRealFit.Coefficient1; + + lines.Add( + $"J{jointIndex + 1}: current_start_v_deg_s={currentStartVelocityDeg:F9}, real_start_v_deg_s={realStartVelocityDeg:F9}, current_start_a_deg_s2={currentStartAccelerationDeg:F9}, real_start_a_deg_s2={realStartAccelerationDeg:F9}, current_jerk_deg_s3={currentJerkDeg:F9}, real_jerk_deg_s3={realJerkDeg:F9}, current_end_v_deg_s={currentEndVelocityDeg:F9}, real_end_v_deg_s={realEndVelocityDeg:F9}"); + } + + File.WriteAllLines(comparisonPath, lines); + Assert.True(File.Exists(comparisonPath)); + } + + /// + /// 验证飞拍规划结果会应用平滑起停时间轴,而不是直接复用原始线性时间轴采样。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_ShapesDenseTrajectoryEdges() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + + var bundle = orchestrator.PlanUploadedFlyshot(robot, uploaded); + var rawDense = TrajectorySampler.SampleJointTrajectory( + bundle.PlannedTrajectory, + samplePeriod: robot.ServoPeriod.TotalSeconds); + + Assert.NotNull(bundle.Result.DenseJointTrajectory); + Assert.True(bundle.Result.DenseJointTrajectory.Count > 4); + + var dense = bundle.Result.DenseJointTrajectory; + Assert.NotEqual(rawDense[1][1], dense[1][1]); + Assert.NotEqual(rawDense[^2][1], dense[^2][1]); + Assert.Equal(rawDense[0][1], dense[0][1], precision: 6); + Assert.Equal(rawDense[^1][1], dense[^1][1], precision: 6); + Assert.Equal(rawDense.Count, dense.Count); + } + + /// + /// 验证旧的首尾整形工具在独立调用时仍会改动首尾窗口,供对比和回归保留。 + /// + [Fact] + public void FlyshotTrajectoryEdgeShaper_ShapesLeadingAndTrailingStepsWithoutFlatteningMiddleSection() + { + var denseTrajectory = new IReadOnlyList[] + { + [0.000, DegreesToRadians(60.546226), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.008, DegreesToRadians(60.541482), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.016, DegreesToRadians(60.536738), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.024, DegreesToRadians(60.531994), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.032, DegreesToRadians(60.527250), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.040, DegreesToRadians(60.522506), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.048, DegreesToRadians(60.517762), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.056, DegreesToRadians(60.513018), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.064, DegreesToRadians(60.508274), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.072, DegreesToRadians(60.503530), 0.0, 0.0, 0.0, 0.0, 0.0], + [0.080, DegreesToRadians(60.498786), 0.0, 0.0, 0.0, 0.0, 0.0] + }; + + var shaped = FlyshotTrajectoryEdgeShaper.ShapeDenseJointTrajectory(denseTrajectory); + + Assert.Equal(denseTrajectory.Length, shaped.Count); + Assert.Equal(denseTrajectory[0][0], shaped[0][0], precision: 6); + Assert.Equal(denseTrajectory[^1][0], shaped[^1][0], precision: 6); + Assert.Equal(denseTrajectory[0][1], shaped[0][1], precision: 12); + Assert.Equal(denseTrajectory[^1][1], shaped[^1][1], precision: 12); + + var firstStepDegrees = Math.Abs(RadiansToDegrees(shaped[1][1] - shaped[0][1])); + var lastStepDegrees = Math.Abs(RadiansToDegrees(shaped[^1][1] - shaped[^2][1])); + var originalFirstStepDegrees = Math.Abs(RadiansToDegrees(denseTrajectory[1][1] - denseTrajectory[0][1])); + var originalLastStepDegrees = Math.Abs(RadiansToDegrees(denseTrajectory[^1][1] - denseTrajectory[^2][1])); + Assert.NotEqual(originalFirstStepDegrees, firstStepDegrees); + Assert.NotEqual(originalLastStepDegrees, lastStepDegrees); + + var middleStepDegrees = Math.Abs(RadiansToDegrees(shaped[5][1] - shaped[4][1])); + Assert.True(middleStepDegrees > 0.002, $"中段单步变化不应被整体压平,actual={middleStepDegrees:F6}deg"); + } + + /// + /// 验证飞拍首尾整形只覆盖首尾各 10 个点,锚点之外的中段采样保持不变。 + /// + [Fact] + public void FlyshotTrajectoryEdgeShaper_ShapesOnlyLeadingAndTrailingTenPoints() + { + var denseTrajectory = Enumerable.Range(0, 31) + .Select(index => (IReadOnlyList) + [ + index * 0.008, + DegreesToRadians(60.0 - index), + DegreesToRadians(index * 0.1), + DegreesToRadians(-index * 0.5), + DegreesToRadians(index * 0.02), + DegreesToRadians(index * 0.2), + DegreesToRadians(index * 0.05) + ]) + .ToArray(); + + var shaped = FlyshotTrajectoryEdgeShaper.ShapeDenseJointTrajectory(denseTrajectory); + + Assert.Equal(denseTrajectory.Length, shaped.Count); + + Assert.NotEqual(denseTrajectory[1][1], shaped[1][1]); + Assert.NotEqual(denseTrajectory[9][1], shaped[9][1]); + Assert.Equal(denseTrajectory[10][1], shaped[10][1], precision: 12); + Assert.Equal(denseTrajectory[11][1], shaped[11][1], precision: 12); + Assert.Equal(denseTrajectory[19][1], shaped[19][1], precision: 12); + Assert.Equal(denseTrajectory[20][1], shaped[20][1], precision: 12); + Assert.NotEqual(denseTrajectory[21][1], shaped[21][1]); + Assert.NotEqual(denseTrajectory[29][1], shaped[29][1]); + Assert.Equal(denseTrajectory[30][1], shaped[30][1], precision: 12); + } + + /// + /// 验证兼容服务执行普通轨迹时会进入规划链路,而不是直接把最后一个路点写入状态。 + /// + [Fact] + public void ControllerClientCompatService_ExecuteTrajectory_RejectsThreeTeachPointsAfterPlanningIsIntroduced() + { + var service = TestRobotFactory.CreateCompatService(); + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SetActiveController(sim: true); + service.Connect("192.168.10.101"); + service.EnableRobot(2); + + void Act() => + service.ExecuteTrajectory( + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.5, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 0.0, 0.0, 0.0] + ]); + + Assert.Throws(Act); + } + + /// + /// 验证兼容服务执行普通轨迹时会把运行时 speed_ratio 前移到规划阶段。 + /// + [Fact] + public void ControllerClientCompatService_ExecuteTrajectory_PassesSpeedRatioIntoOrdinaryPlanning() + { + var configRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(configRoot, planningSpeedScale: 0.9, speedRatio: 1.0); + var options = new ControllerClientCompatOptions { ConfigRoot = configRoot }; + var runtime = new RecordingControllerRuntime(); + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + var waypoints = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.001, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.002, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.003, 0.0, 0.0, 0.0, 0.0, 0.0 } + }; + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SetActiveController(sim: false); + service.Connect("192.168.10.101"); + service.EnableRobot(2); + runtime.SetSpeedRatio(0.5); + + service.ExecuteTrajectory(waypoints); + + var robot = new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()) + .LoadProfile("FANUC_LR_Mate_200iD", accLimitScale: 1.0, jerkLimitScale: 1.0); + var expected = new ControllerClientTrajectoryOrchestrator() + .PlanOrdinaryTrajectory(robot, waypoints, planningSpeedScale: 0.45); + var actual = Assert.IsType(runtime.LastExecutedResult); + Assert.Equal(expected.Result.Duration.TotalSeconds, actual.Duration.TotalSeconds, precision: 6); + Assert.Equal(expected.Result.DenseJointTrajectory!.Count, actual.DenseJointTrajectory!.Count); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证 ExecuteFlyShotTraj(move_to_start=true) 会先执行稠密 PTP 到起点,并等待该段运动完成后再启动飞拍轨迹。 + /// + [Fact] + public void ControllerClientCompatService_ExecuteTrajectoryByName_MoveToStartWaitsBeforeFlyshot() + { + var configRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(configRoot); + var options = new ControllerClientCompatOptions + { + ConfigRoot = configRoot + }; + var runtime = new DelayedCompletionControllerRuntime( + initialJointPositions: [0.4, 0.0, 0.0, 0.0, 0.0, 0.0], + firstMotionCompletionDelay: TimeSpan.FromMilliseconds(80)); + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SetActiveController(sim: false); + service.Connect("192.168.10.101"); + service.EnableRobot(2); + service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot()); + + service.ExecuteTrajectoryByName( + "demo-flyshot", + new FlyshotExecutionOptions(moveToStart: true, method: "icsp", saveTrajectory: false, useCache: false)); + + Assert.True(runtime.ExecuteCalls.Count >= 2); + Assert.NotNull(runtime.ExecuteCalls[0].Result.DenseJointTrajectory); + Assert.True(runtime.ExecuteCalls[0].Result.DenseJointTrajectory!.Count > 1); + Assert.False(runtime.SecondTrajectoryStartedBeforeFirstMotionCompleted); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证正式飞拍前若真实反馈仍未接近起点,则兼容层会拒绝继续执行。 + /// + [Fact] + public void ControllerClientCompatService_ExecuteTrajectoryByName_RejectsFlyshotWhenFeedbackIsNotNearStartAfterMoveToStart() + { + var configRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(configRoot); + var options = new ControllerClientCompatOptions + { + ConfigRoot = configRoot + }; + var runtime = new StickyFeedbackControllerRuntime( + initialJointPositions: [0.4, 0.0, 0.0, 0.0, 0.0, 0.0], + firstMotionCompletionDelay: TimeSpan.FromMilliseconds(20)); + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SetActiveController(sim: false); + service.Connect("192.168.10.101"); + service.EnableRobot(2); + service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot()); + + var exception = Assert.Throws(() => + service.ExecuteTrajectoryByName( + "demo-flyshot", + new FlyshotExecutionOptions(moveToStart: true, method: "icsp", saveTrajectory: false, useCache: false))); + + Assert.Contains("not near flyshot start", exception.Message, StringComparison.OrdinalIgnoreCase); + Assert.Single(runtime.ExecuteCalls); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证 ExecuteFlyShotTraj(wait=true) 会等待正式飞拍轨迹完成后再返回。 + /// + [Fact] + public void ControllerClientCompatService_ExecuteTrajectoryByName_WaitTrueWaitsForFlyshotCompletion() + { + var configRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(configRoot); + var options = new ControllerClientCompatOptions + { + ConfigRoot = configRoot + }; + var runtime = new DelayedCompletionControllerRuntime( + initialJointPositions: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + firstMotionCompletionDelay: TimeSpan.FromMilliseconds(80)); + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SetActiveController(sim: false); + service.Connect("192.168.10.101"); + service.EnableRobot(2); + service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot()); + + service.ExecuteTrajectoryByName( + "demo-flyshot", + new FlyshotExecutionOptions(moveToStart: false, method: "icsp", saveTrajectory: false, useCache: false, wait: true)); + + Assert.Single(runtime.ExecuteCalls); + Assert.False(runtime.GetSnapshot().IsInMotion); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证飞拍执行阻塞在运行时时,状态页元数据快照仍能通过短锁快速返回。 + /// + [Fact] + public async Task ControllerClientCompatService_GetStatusSnapshotMetadata_DoesNotWaitForRunningFlyshot() + { + var configRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(configRoot); + var options = new ControllerClientCompatOptions + { + ConfigRoot = configRoot + }; + var runtime = new BlockingExecutionControllerRuntime([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SetActiveController(sim: false); + service.Connect("192.168.10.101"); + service.EnableRobot(2); + service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot()); + + var executing = Task.Run(() => service.ExecuteTrajectoryByName( + "demo-flyshot", + new FlyshotExecutionOptions(moveToStart: false, method: "icsp", saveTrajectory: false, useCache: false, wait: true))); + Assert.True(runtime.WaitForExecutionStarted(TimeSpan.FromSeconds(2))); + + var metadataTask = Task.Run(() => service.GetStatusSnapshotMetadata()); + var completed = await Task.WhenAny(metadataTask, Task.Delay(TimeSpan.FromMilliseconds(150))); + + runtime.ReleaseExecution(); + await executing; + + Assert.Same(metadataTask, completed); + var metadata = await metadataTask; + Assert.True(metadata.IsSetup); + Assert.Equal("FANUC_LR_Mate_200iD", metadata.RobotName); + Assert.Equal(["demo-flyshot"], metadata.UploadedTrajectories); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证两个飞拍执行命令必须串行进入 runtime,避免 J519 队列被并发执行覆盖。 + /// + [Fact] + public async Task ControllerClientCompatService_ExecuteTrajectoryByName_SerializesConcurrentExecutionCommands() + { + var configRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(configRoot); + var options = new ControllerClientCompatOptions + { + ConfigRoot = configRoot + }; + var runtime = new BlockingExecutionControllerRuntime([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SetActiveController(sim: false); + service.Connect("192.168.10.101"); + service.EnableRobot(2); + service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot()); + + var first = Task.Run(() => service.ExecuteTrajectoryByName( + "demo-flyshot", + new FlyshotExecutionOptions(moveToStart: false, method: "icsp", saveTrajectory: false, useCache: false, wait: true))); + Assert.True(runtime.WaitForExecutionStarted(TimeSpan.FromSeconds(2))); + + var second = Task.Run(() => service.ExecuteTrajectoryByName( + "demo-flyshot", + new FlyshotExecutionOptions(moveToStart: false, method: "icsp", saveTrajectory: false, useCache: false, wait: true))); + await Task.Delay(TimeSpan.FromMilliseconds(100)); + + Assert.Equal(1, runtime.ExecuteCallCount); + + runtime.ReleaseExecution(); + await first; + Assert.True(runtime.WaitForExecutionStarted(TimeSpan.FromSeconds(2), expectedCallCount: 2)); + runtime.ReleaseExecution(); + await second; + + Assert.Equal(2, runtime.ExecuteCallCount); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证飞拍链路在进入运行时前就会准备最终发送队列,而不是把 speedRatio 重采样留给运行时临场处理。 + /// + [Fact] + public void ControllerClientCompatService_ExecuteTrajectoryByName_PreparesFinalSendQueueBeforeRuntime() + { + var configRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(configRoot); + var options = new ControllerClientCompatOptions { ConfigRoot = configRoot }; + var runtime = new RecordingControllerRuntime(); + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SetActiveController(sim: false); + service.Connect("192.168.10.101"); + service.EnableRobot(2); + service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot()); + runtime.SetSpeedRatio(0.5); + + service.ExecuteTrajectoryByName( + "demo-flyshot", + new FlyshotExecutionOptions(moveToStart: false, method: "icsp", saveTrajectory: false, useCache: false)); + + var result = Assert.IsType(runtime.LastExecutedResult); + var preparedExecution = Assert.IsType(result.PreparedFlyshotExecution); + Assert.NotEmpty(preparedExecution.Samples); + Assert.Equal(preparedExecution.Samples.Count, preparedExecution.TimingRows.Count); + Assert.Equal(result.TriggerTimeline.Count, preparedExecution.TriggerBindings.Count); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证兼容服务初始化机器人时会把 RobotConfig.json 中的 acc_limit / jerk_limit 传给模型加载器。 + /// + [Fact] + public void ControllerClientCompatService_SetUpRobot_AppliesRobotConfigLimitScales() + { + var configRoot = CreateTempConfigRoot(); + try + { + File.WriteAllText( + Path.Combine(configRoot, "RobotConfig.json"), + """ + { + "robot": { + "use_do": true, + "io_addr": [7, 8], + "io_keep_cycles": 4, + "acc_limit": 0.5, + "jerk_limit": 0.25, + "adapt_icsp_try_num": 3, + "speed_ratio": 0.65 + }, + "flying_shots": {} + } + """); + + var options = new ControllerClientCompatOptions { ConfigRoot = configRoot }; + var runtime = new RecordingControllerRuntime(); + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + + var profile = Assert.IsType(runtime.LastRobotProfile); + Assert.Equal(14.905, profile.JointLimits[2].AccelerationLimit, precision: 3); + Assert.Equal(62.115, profile.JointLimits[2].JerkLimit, precision: 3); + Assert.Equal(0.65, runtime.GetSpeedRatio(), precision: 6); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证 IsFlyshotTrajectoryValid(saveTrajectory=true) 会把规划后的结果点位导出到 Config/Data/name。 + /// + [Fact] + public void ControllerClientCompatService_IsFlyshotTrajectoryValid_SaveTrajectoryExportsPlannedData() + { + var configRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(configRoot); + var options = new ControllerClientCompatOptions { ConfigRoot = configRoot }; + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + new RecordingControllerRuntime(), + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + + var valid = service.IsFlyshotTrajectoryValid( + out var duration, + "demo-flyshot", + method: "icsp", + saveTrajectory: true); + + var outputDir = Path.Combine(configRoot, "Data", "demo-flyshot"); + Assert.True(valid); + Assert.True(duration > TimeSpan.Zero); + Assert.True(File.Exists(Path.Combine(outputDir, "JointTraj.txt"))); + Assert.True(File.Exists(Path.Combine(outputDir, "JointDetialTraj.txt"))); + Assert.True(File.Exists(Path.Combine(outputDir, "CartTraj.txt"))); + Assert.True(File.Exists(Path.Combine(outputDir, "CartDetialTraj.txt"))); + Assert.True(File.Exists(Path.Combine(outputDir, "ShotEvents.json"))); + Assert.NotEmpty(File.ReadAllLines(Path.Combine(outputDir, "JointDetialTraj.txt"))); + Assert.NotEmpty(File.ReadAllLines(Path.Combine(outputDir, "CartDetialTraj.txt"))); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证 SaveTrajectoryInfo 会导出按 J519 8ms 实发周期重采样的点位,并保留当前 speed_ratio 诊断信息。 + /// + [Fact] + public void ControllerClientCompatService_SaveTrajectoryInfo_ExportsActualSendRowsWithSpeedRatio() + { + var configRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(configRoot); + var options = new ControllerClientCompatOptions { ConfigRoot = configRoot }; + var runtime = new RecordingControllerRuntime(); + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + runtime, + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + runtime.SetSpeedRatio(0.5); + + service.SaveTrajectoryInfo("demo-flyshot"); + + var outputDir = Path.Combine(configRoot, "Data", "demo-flyshot"); + var pointsPath = Path.Combine(outputDir, "ActualSendJointTraj.txt"); + var timingPath = Path.Combine(outputDir, "ActualSendTiming.txt"); + var shotEventsPath = Path.Combine(outputDir, "ShotEvents.json"); + Assert.True(File.Exists(pointsPath)); + Assert.True(File.Exists(timingPath)); + Assert.True(File.Exists(shotEventsPath)); + + var pointRows = File.ReadAllLines(pointsPath).Select(ParseSpaceSeparatedDoubles).ToArray(); + var timingRows = File.ReadAllLines(timingPath).Select(ParseSpaceSeparatedDoubles).ToArray(); + var shotEventsJson = File.ReadAllText(shotEventsPath); + var planningExportDuration = double.Parse( + File.ReadLines(Path.Combine(outputDir, "JointDetialTraj.txt")).Last().Split(' ')[0], + CultureInfo.InvariantCulture); + var planningExportRowsAtServoPeriod = (int)Math.Ceiling(Math.Max(0.0, (planningExportDuration / 0.008) - 1e-9)) + 1; + + Assert.Equal(pointRows.Length, timingRows.Length); + Assert.True( + pointRows.Length > planningExportRowsAtServoPeriod, + $"ActualSend 应按当前 speedRatio 的执行视图导出,不能再用旧 JointDetialTraj 规划时长约束点数。actual={pointRows.Length}, planningRows={planningExportRowsAtServoPeriod}"); + Assert.Equal(0.0, pointRows[0][0], precision: 6); + Assert.Equal(0.008, pointRows[1][0], precision: 6); + Assert.Equal(0.008, timingRows[1][2], precision: 6); + Assert.Equal(1.0, timingRows[1][3], precision: 6); + Assert.Equal(0.5, timingRows[1][4], precision: 6); + Assert.Equal(0.0, timingRows[1][5], precision: 6); + Assert.Contains("\"trigger_window_seconds\": 0.1", shotEventsJson); + Assert.Contains("\"selected_sample_index\"", shotEventsJson); + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 验证 SaveTrajectoryInfo 的旧格式规划导出只受 planning_speed_scale 影响,不跟随运行时 speed_ratio 改变。 + /// + [Fact] + public void ControllerClientCompatService_SaveTrajectoryInfo_KeepsLegacyPlanningExportsIndependentFromSpeedRatio() + { + var fullSpeedRoot = CreateTempConfigRoot(); + var halfSpeedRoot = CreateTempConfigRoot(); + try + { + WriteRobotConfigWithDemoTrajectory(fullSpeedRoot, planningSpeedScale: 0.45, speedRatio: 1.0); + WriteRobotConfigWithDemoTrajectory(halfSpeedRoot, planningSpeedScale: 0.45, speedRatio: 0.5); + + SaveDemoTrajectoryInfo(fullSpeedRoot); + SaveDemoTrajectoryInfo(halfSpeedRoot); + + var fullSpeedOutput = Path.Combine(fullSpeedRoot, "Data", "demo-flyshot"); + var halfSpeedOutput = Path.Combine(halfSpeedRoot, "Data", "demo-flyshot"); + AssertNumericFilesEqual( + Path.Combine(fullSpeedOutput, "JointTraj.txt"), + Path.Combine(halfSpeedOutput, "JointTraj.txt")); + AssertNumericFilesEqual( + Path.Combine(fullSpeedOutput, "JointDetialTraj.txt"), + Path.Combine(halfSpeedOutput, "JointDetialTraj.txt")); + AssertNumericFilesEqual( + Path.Combine(fullSpeedOutput, "CartTraj.txt"), + Path.Combine(halfSpeedOutput, "CartTraj.txt")); + AssertNumericFilesEqual( + Path.Combine(fullSpeedOutput, "CartDetialTraj.txt"), + Path.Combine(halfSpeedOutput, "CartDetialTraj.txt")); + + var fullSpeedActualSend = File.ReadAllLines(Path.Combine(fullSpeedOutput, "ActualSendJointTraj.txt")); + var halfSpeedActualSend = File.ReadAllLines(Path.Combine(halfSpeedOutput, "ActualSendJointTraj.txt")); + var fullSpeedTiming = File.ReadAllLines(Path.Combine(fullSpeedOutput, "ActualSendTiming.txt")); + var halfSpeedTiming = File.ReadAllLines(Path.Combine(halfSpeedOutput, "ActualSendTiming.txt")); + Assert.NotEqual(fullSpeedActualSend.Length, halfSpeedActualSend.Length); + Assert.NotEqual(fullSpeedTiming.Length, halfSpeedTiming.Length); + } + finally + { + Directory.Delete(fullSpeedRoot, recursive: true); + Directory.Delete(halfSpeedRoot, recursive: true); + } + } + + /// + /// 验证 saveTrajectory 导出的 JointDetialTraj.txt 来自旧规划导出视图的 16ms 明细轨迹, + /// 而不是来自 speed_ratio 后的实发执行视图。 + /// + [Fact] + public void FlyshotTrajectoryArtifactWriter_WriteUploadedFlyshot_JointDetailUsesExportPlanningDenseDownsample() + { + var fixture = LoadUttcMs11RuntimeFixture(); + var configRoot = CreateTempConfigRoot(); + try + { + var options = new ControllerClientCompatOptions { ConfigRoot = configRoot }; + var writer = new FlyshotTrajectoryArtifactWriter(options, new RobotModelLoader()); + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var exportBundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: EnableSmoothStartStopTiming(fixture.Settings), + planningSpeedScale: 1.0); + var executionBundle = orchestrator.PlanUploadedFlyshot( + fixture.Robot, + fixture.Uploaded, + settings: EnableSmoothStartStopTiming(fixture.Settings), + planningSpeedScale: 1.0, + speedRatio: 0.5); + + writer.WriteUploadedFlyshot("UTTC_MS11", fixture.Robot, exportBundle, executionBundle, speedRatio: 0.5); + + var outputDir = Path.Combine(configRoot, "Data", "UTTC_MS11"); + var exportedRows = File.ReadAllLines(Path.Combine(outputDir, "JointDetialTraj.txt")) + .Select(ParseSpaceSeparatedDoubles) + .ToArray(); + var expectedRows = DownsampleDenseRows( + exportBundle.Result.DenseJointTrajectory!, + samplePeriodSeconds: 0.016) + .Select(static row => row.ToArray()) + .ToArray(); + + Assert.Equal(expectedRows.Length, exportedRows.Length); + for (var rowIndex = 0; rowIndex < expectedRows.Length; rowIndex++) + { + Assert.Equal(expectedRows[rowIndex].Length, exportedRows[rowIndex].Length); + for (var columnIndex = 0; columnIndex < expectedRows[rowIndex].Length; columnIndex++) + { + Assert.Equal(expectedRows[rowIndex][columnIndex], exportedRows[rowIndex][columnIndex], precision: 6); + } + } + } + finally + { + Directory.Delete(configRoot, recursive: true); + } + } + + /// + /// 创建只包含当前支持机器人 JSON 模型和 RobotConfig.json 的临时运行配置根。 + /// + private static string CreateTempConfigRoot() + { + var configRoot = Path.Combine(Path.GetTempPath(), "flyshot-runtime-tests", Guid.NewGuid().ToString("N"), "Config"); + var modelDir = Path.Combine(configRoot, "Models"); + Directory.CreateDirectory(modelDir); + + var sourceModel = Path.Combine( + TestRobotFactory.GetReplacementRoot(), + "Config", + "Models", + "LR_Mate_200iD_7L.json"); + File.Copy(sourceModel, Path.Combine(modelDir, "LR_Mate_200iD_7L.json")); + + return configRoot; + } + + /// + /// 写入包含一条飞拍轨迹的最小 RobotConfig.json,供兼容服务从统一配置恢复轨迹。 + /// + /// 测试运行配置根。 + private static void WriteRobotConfigWithDemoTrajectory( + string configRoot, + double planningSpeedScale = 0.45, + double speedRatio = 1.0) + { + File.WriteAllText( + Path.Combine(configRoot, "RobotConfig.json"), + $$""" + { + "robot": { + "use_do": true, + "io_addr": [7, 8], + "io_keep_cycles": 2, + "speed_ratio": {{speedRatio.ToString(CultureInfo.InvariantCulture)}}, + "planning_speed_scale": {{planningSpeedScale.ToString(CultureInfo.InvariantCulture)}}, + "acc_limit": 1.0, + "jerk_limit": 1.0, + "adapt_icsp_try_num": 5 + }, + "flying_shots": { + "demo-flyshot": { + "traj_waypoints": [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.001, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.002, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.003, 0.0, 0.0, 0.0, 0.0, 0.0] + ], + "shot_flags": [false, true, false, false], + "offset_values": [0, 1, 0, 0], + "addr": [[], [7, 8], [], []] + } + } + } + """); + } + + /// + /// 使用指定运行配置根执行 demo-flyshot 的 SaveTrajectoryInfo。 + /// + private static void SaveDemoTrajectoryInfo(string configRoot) + { + var options = new ControllerClientCompatOptions { ConfigRoot = configRoot }; + var service = new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + new RecordingControllerRuntime(), + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + + service.SetUpRobot("FANUC_LR_Mate_200iD"); + service.SaveTrajectoryInfo("demo-flyshot"); + } + + /// + /// 按数值逐行比较两个轨迹文本文件,避免纯文本格式差异影响判断。 + /// + private static void AssertNumericFilesEqual(string expectedPath, string actualPath) + { + var expectedRows = File.ReadAllLines(expectedPath).Select(ParseSpaceSeparatedDoubles).ToArray(); + var actualRows = File.ReadAllLines(actualPath).Select(ParseSpaceSeparatedDoubles).ToArray(); + + Assert.Equal(expectedRows.Length, actualRows.Length); + for (var rowIndex = 0; rowIndex < expectedRows.Length; rowIndex++) + { + Assert.Equal(expectedRows[rowIndex].Length, actualRows[rowIndex].Length); + for (var columnIndex = 0; columnIndex < expectedRows[rowIndex].Length; columnIndex++) + { + Assert.Equal(expectedRows[rowIndex][columnIndex], actualRows[rowIndex][columnIndex], precision: 6); + } + } + } + + /// + /// 角度转弧度,供轨迹整形测试构造输入。 + /// + private static double DegreesToRadians(double degrees) + { + return degrees * Math.PI / 180.0; + } + + /// + /// 弧度转角度,供轨迹整形测试验证单步变化量。 + /// + private static double RadiansToDegrees(double radians) + { + return radians * 180.0 / Math.PI; + } + + /// + /// 验证稠密轨迹端点仍与原始示教点一致。 + /// + private static void AssertJointRadiansEqual(IReadOnlyList expectedRadians, IReadOnlyList actualRadians) + { + Assert.Equal(expectedRadians.Count, actualRadians.Count); + for (var index = 0; index < expectedRadians.Count; index++) + { + Assert.Equal(expectedRadians[index], actualRadians[index], precision: 6); + } + } + + /// + /// 加载运行时 Config/RobotConfig.json 中的 UTTC_MS11 轨迹和对应机器人配置。 + /// + private static UttcMs11RuntimeFixture LoadUttcMs11RuntimeFixture() + { + var configRoot = TestRobotFactory.GetConfigRoot(); + var configPath = Path.Combine(configRoot, "RobotConfig.json"); + var loaded = new RobotConfigLoader().Load(configPath, configRoot); + var program = loaded.Programs["UTTC_MS11"]; + var uploaded = new ControllerClientCompatUploadedTrajectory( + name: program.Name, + waypoints: program.Waypoints.Select(static waypoint => waypoint.Positions), + shotFlags: program.ShotFlags, + offsetValues: program.OffsetValues, + addressGroups: program.AddressGroups.Select(static group => group.Addresses)); + var options = new ControllerClientCompatOptions + { + ConfigRoot = configRoot + }; + var robot = new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()) + .LoadProfile("FANUC_LR_Mate_200iD", loaded.Robot.AccLimitScale, loaded.Robot.JerkLimitScale); + + return new UttcMs11RuntimeFixture(configRoot, loaded.Robot, uploaded, robot); + } + + /// + /// 为首尾平滑对比测试显式打开二次时间重映射,避免受现场 legacy-fit 配置影响。 + /// + private static CompatibilityRobotSettings EnableSmoothStartStopTiming(CompatibilityRobotSettings settings) + { + return new CompatibilityRobotSettings( + useDo: settings.UseDo, + ioAddresses: settings.IoAddresses, + ioKeepCycles: settings.IoKeepCycles, + triggerSampleIndexOffsetCycles: settings.TriggerSampleIndexOffsetCycles, + accLimitScale: settings.AccLimitScale, + jerkLimitScale: settings.JerkLimitScale, + adaptIcspTryNum: settings.AdaptIcspTryNum, + planningSpeedScale: settings.PlanningSpeedScale, + speedRatio: settings.SpeedRatio, + smoothStartStopTiming: true); + } + + /// + /// 为 legacy-fit 对比测试显式关闭二次时间重映射。 + /// + private static CompatibilityRobotSettings DisableSmoothStartStopTiming(CompatibilityRobotSettings settings) + { + return new CompatibilityRobotSettings( + useDo: settings.UseDo, + ioAddresses: settings.IoAddresses, + ioKeepCycles: settings.IoKeepCycles, + triggerSampleIndexOffsetCycles: settings.TriggerSampleIndexOffsetCycles, + accLimitScale: settings.AccLimitScale, + jerkLimitScale: settings.JerkLimitScale, + adaptIcspTryNum: settings.AdaptIcspTryNum, + planningSpeedScale: settings.PlanningSpeedScale, + speedRatio: settings.SpeedRatio, + smoothStartStopTiming: false); + } + + /// + /// 构造飞拍倍率测试使用的最小兼容设置。 + /// + private static CompatibilityRobotSettings CreateFlyshotSettings(double planningSpeedScale, double speedRatio) + { + return new CompatibilityRobotSettings( + useDo: true, + ioAddresses: [7, 8], + ioKeepCycles: 2, + triggerSampleIndexOffsetCycles: 7, + accLimitScale: 1.0, + jerkLimitScale: 1.0, + adaptIcspTryNum: 5, + planningSpeedScale: planningSpeedScale, + speedRatio: speedRatio, + smoothStartStopTiming: true); + } + + /// + /// 把指定窗口内的整形前后逐点数据导出为 CSV,包含关节、步长、速度、加速度与跃度。 + /// + private static void WriteEdgeComparisonCsv( + string path, + IReadOnlyList> rawDense, + IReadOnlyList> shapedDense, + int startIndex, + int count) + { + var lines = new List + { + BuildEdgeComparisonHeader() + }; + + for (var index = startIndex; index < startIndex + count && index < rawDense.Count && index < shapedDense.Count; index++) + { + var columns = new List + { + index.ToString(CultureInfo.InvariantCulture), + rawDense[index][0].ToString("F6", CultureInfo.InvariantCulture) + }; + + AppendJointColumns(columns, "raw_j", index, rawDense, static (rows, rowIndex, jointIndex) => RadiansToDegrees(rows[rowIndex][jointIndex + 1])); + AppendJointColumns(columns, "shaped_j", index, shapedDense, static (rows, rowIndex, jointIndex) => RadiansToDegrees(rows[rowIndex][jointIndex + 1])); + AppendJointColumns(columns, "raw_step", index, rawDense, ComputeStepDegrees); + AppendJointColumns(columns, "shaped_step", index, shapedDense, ComputeStepDegrees); + AppendJointColumns(columns, "raw_velocity", index, rawDense, ComputeVelocityDegrees); + AppendJointColumns(columns, "shaped_velocity", index, shapedDense, ComputeVelocityDegrees); + AppendJointColumns(columns, "raw_acceleration", index, rawDense, ComputeAccelerationDegrees); + AppendJointColumns(columns, "shaped_acceleration", index, shapedDense, ComputeAccelerationDegrees); + AppendJointColumns(columns, "raw_jerk", index, rawDense, ComputeJerkDegrees); + AppendJointColumns(columns, "shaped_jerk", index, shapedDense, ComputeJerkDegrees); + lines.Add(string.Join(",", columns)); + } + + File.WriteAllLines(path, lines); + } + + /// + /// 构造首尾整形逐点对比 CSV 表头。 + /// + private static string BuildEdgeComparisonHeader() + { + var columns = new List + { + "sample_index", + "time_seconds" + }; + + AppendJointHeader(columns, "raw_j"); + AppendJointHeader(columns, "shaped_j"); + AppendJointHeader(columns, "raw_step"); + AppendJointHeader(columns, "shaped_step"); + AppendJointHeader(columns, "raw_velocity"); + AppendJointHeader(columns, "shaped_velocity"); + AppendJointHeader(columns, "raw_acceleration"); + AppendJointHeader(columns, "shaped_acceleration"); + AppendJointHeader(columns, "raw_jerk"); + AppendJointHeader(columns, "shaped_jerk"); + + return string.Join(",", columns); + } + + /// + /// 追加 J1..J6 表头列名。 + /// + private static void AppendJointHeader(List columns, string prefix) + { + for (var jointIndex = 1; jointIndex <= 6; jointIndex++) + { + columns.Add($"{prefix}_j{jointIndex}"); + } + } + + /// + /// 追加某一行对应的六轴数值列。 + /// + private static void AppendJointColumns( + List columns, + string prefix, + int rowIndex, + IReadOnlyList> rows, + Func>, int, int, double> selector) + { + for (var jointIndex = 0; jointIndex < 6; jointIndex++) + { + columns.Add(selector(rows, rowIndex, jointIndex).ToString("F6", CultureInfo.InvariantCulture)); + } + } + + /// + /// 计算某一行相对上一行的单步角度变化量。 + /// + private static double ComputeStepDegrees(IReadOnlyList> rows, int rowIndex, int jointIndex) + { + if (rowIndex <= 0) + { + return 0.0; + } + + return RadiansToDegrees(rows[rowIndex][jointIndex + 1] - rows[rowIndex - 1][jointIndex + 1]); + } + + /// + /// 计算某一行的角速度,单位 deg/s。 + /// + private static double ComputeVelocityDegrees(IReadOnlyList> rows, int rowIndex, int jointIndex) + { + if (rowIndex <= 0) + { + return 0.0; + } + + var dt = rows[rowIndex][0] - rows[rowIndex - 1][0]; + if (dt <= 0.0) + { + return 0.0; + } + + return ComputeStepDegrees(rows, rowIndex, jointIndex) / dt; + } + + /// + /// 计算某一行的角加速度,单位 deg/s^2。 + /// + private static double ComputeAccelerationDegrees(IReadOnlyList> rows, int rowIndex, int jointIndex) + { + if (rowIndex <= 1) + { + return 0.0; + } + + var dt = rows[rowIndex][0] - rows[rowIndex - 1][0]; + if (dt <= 0.0) + { + return 0.0; + } + + return (ComputeVelocityDegrees(rows, rowIndex, jointIndex) - ComputeVelocityDegrees(rows, rowIndex - 1, jointIndex)) / dt; + } + + /// + /// 计算某一行的角跃度,单位 deg/s^3。 + /// + private static double ComputeJerkDegrees(IReadOnlyList> rows, int rowIndex, int jointIndex) + { + if (rowIndex <= 2) + { + return 0.0; + } + + var dt = rows[rowIndex][0] - rows[rowIndex - 1][0]; + if (dt <= 0.0) + { + return 0.0; + } + + return (ComputeAccelerationDegrees(rows, rowIndex, jointIndex) - ComputeAccelerationDegrees(rows, rowIndex - 1, jointIndex)) / dt; + } + + /// + /// 统计给定窗口内所有关节的最大绝对值跃度,便于快速看首尾尖峰是否被压低。 + /// + private static double ComputeWindowMaxAbsJerkDegrees( + IReadOnlyList> rows, + int startIndex, + int count) + { + var maxAbsJerk = 0.0; + for (var rowIndex = startIndex; rowIndex < startIndex + count && rowIndex < rows.Count; rowIndex++) + { + for (var jointIndex = 0; jointIndex < 6; jointIndex++) + { + maxAbsJerk = Math.Max(maxAbsJerk, Math.Abs(ComputeJerkDegrees(rows, rowIndex, jointIndex))); + } + } + + return maxAbsJerk; + } + + /// + /// 解析空格分隔的关节轨迹行。 + /// + private static double[] ParseSpaceSeparatedDoubles(string line) + { + return line + .Split(' ', StringSplitOptions.RemoveEmptyEntries) + .Select(static value => double.Parse(value, CultureInfo.InvariantCulture)) + .ToArray(); + } + + /// + /// 将 8ms 执行稠密轨迹按目标周期抽稀为低频视图,并始终保留终点。 + /// + private static IReadOnlyList> DownsampleDenseRows( + IReadOnlyList> denseRows, + double samplePeriodSeconds) + { + var result = new List>(); + var epsilon = 1e-6; + var nextSampleTime = 0.0; + + foreach (var row in denseRows) + { + var sampleTime = row[0]; + if (sampleTime + epsilon < nextSampleTime) + { + continue; + } + + if (Math.Abs(sampleTime - nextSampleTime) <= epsilon || sampleTime.Equals(0.0)) + { + result.Add(row); + nextSampleTime += samplePeriodSeconds; + } + } + + if (result.Count == 0 || !ReferenceEquals(result[^1], denseRows[^1])) + { + result.Add(denseRows[^1]); + } + + return result; + } + + /// + /// 用真实可运行轨迹前 4 个采样点拟合局部三次曲线,返回相对首点时间的系数。 + /// 曲线形式为 p(t)=c3*t^3+c2*t^2+c1*t+c0,单位保持输入文件的角度制。 + /// + private static LocalCubicFit FitCubicFromFirstFourRows(IReadOnlyList rows, int valueColumn) + { + var t0 = rows[0][0]; + var matrix = new double[4][]; + var rhs = new double[4]; + for (var index = 0; index < 4; index++) + { + var localTime = rows[index][0] - t0; + matrix[index] = + [ + localTime * localTime * localTime, + localTime * localTime, + localTime, + 1.0 + ]; + rhs[index] = rows[index][valueColumn]; + } + + var solution = SolveLinearSystem4x4(matrix, rhs); + return new LocalCubicFit( + Coefficient3: solution[0], + Coefficient2: solution[1], + Coefficient1: solution[2], + Coefficient0: solution[3], + LastLocalTime: rows[3][0] - t0); + } + + /// + /// 用高斯消元求解 4x4 线性方程组。 + /// + private static double[] SolveLinearSystem4x4(double[][] matrix, double[] rhs) + { + var a = matrix.Select(static row => row.ToArray()).ToArray(); + var b = rhs.ToArray(); + + for (var pivot = 0; pivot < 4; pivot++) + { + var bestRow = pivot; + for (var row = pivot + 1; row < 4; row++) + { + if (Math.Abs(a[row][pivot]) > Math.Abs(a[bestRow][pivot])) + { + bestRow = row; + } + } + + if (Math.Abs(a[bestRow][pivot]) <= 1e-12) + { + throw new InvalidOperationException("真实轨迹前 4 点无法稳定拟合局部三次曲线。"); + } + + if (bestRow != pivot) + { + (a[pivot], a[bestRow]) = (a[bestRow], a[pivot]); + (b[pivot], b[bestRow]) = (b[bestRow], b[pivot]); + } + + var divisor = a[pivot][pivot]; + for (var column = pivot; column < 4; column++) + { + a[pivot][column] /= divisor; + } + + b[pivot] /= divisor; + + for (var row = 0; row < 4; row++) + { + if (row == pivot) + { + continue; + } + + var factor = a[row][pivot]; + for (var column = pivot; column < 4; column++) + { + a[row][column] -= factor * a[pivot][column]; + } + + b[row] -= factor * b[pivot]; + } + } + + return b; + } +} + +/// +/// 封装运行时 UTTC_MS11 轨迹、配置和机器人模型,避免测试反复拼装。 +/// +internal sealed record UttcMs11RuntimeFixture( + string ConfigRoot, + CompatibilityRobotSettings Settings, + ControllerClientCompatUploadedTrajectory Uploaded, + RobotProfile Robot); + +/// +/// 记录基于真实轨迹前 4 点拟合得到的局部三次曲线系数。 +/// +internal sealed record LocalCubicFit( + double Coefficient3, + double Coefficient2, + double Coefficient1, + double Coefficient0, + double LastLocalTime); + +/// +/// 为运行时编排测试构造稳定的最小领域对象。 +/// +internal static class TestRobotFactory +{ + /// + /// 构造六轴测试机器人配置,避免运行时测试依赖真实 .robot 文件。 + /// + /// 可用于规划和运行时状态校验的机器人配置。 + public static RobotProfile CreateRobotProfile() + { + return new RobotProfile( + name: "TestRobot", + modelPath: "Models/Test.robot", + degreesOfFreedom: 6, + jointLimits: Enumerable.Range(1, 6) + .Select(static index => new JointLimit($"J{index}", 10.0, 20.0, 100.0)) + .ToArray(), + jointCouplings: Array.Empty(), + servoPeriod: TimeSpan.FromMilliseconds(8), + triggerPeriod: TimeSpan.FromMilliseconds(8)); + } + + /// + /// 构造一条含单个拍照点的上传飞拍轨迹。 + /// + /// 可用于触发时间轴测试的上传轨迹。 + public static ControllerClientCompatUploadedTrajectory CreateUploadedTrajectoryWithSingleShot() + { + return new ControllerClientCompatUploadedTrajectory( + name: "demo-flyshot", + waypoints: + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.001, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.002, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.003, 0.0, 0.0, 0.0, 0.0, 0.0] + ], + shotFlags: [false, true, false, false], + offsetValues: [0, 1, 0, 0], + addressGroups: + [ + Array.Empty(), + [7, 8], + Array.Empty(), + Array.Empty() + ]); + } + + /// + /// 构造一份真实依赖注入等价的兼容服务,覆盖运行时和编排器协作。 + /// + /// 可执行 ControllerClient 兼容语义的服务实例。 + public static ControllerClientCompatService CreateCompatService() + { + var options = new ControllerClientCompatOptions + { + ConfigRoot = GetConfigRoot() + }; + + return new ControllerClientCompatService( + options, + new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), + new FanucControllerRuntime(), + new ControllerClientTrajectoryOrchestrator(), + new RobotConfigLoader()); + } + + /// + /// 定位 replacement 仓库内的运行配置根目录。 + /// + /// 当前仓库 Config 目录。 + public static string GetConfigRoot() + { + return Path.Combine(GetReplacementRoot(), "Config"); + } + + /// + /// 定位 replacement 仓库根目录,供测试读取仓库内固化配置。 + /// + /// replacement 仓库根目录。 + public static string GetReplacementRoot() + { + var current = new DirectoryInfo(AppContext.BaseDirectory); + while (current is not null) + { + if (File.Exists(Path.Combine(current.FullName, "FlyshotReplacement.sln"))) + { + return current.FullName; + } + + current = current.Parent; + } + + throw new DirectoryNotFoundException("Unable to locate the flyshot replacement root."); + } + + /// + /// 定位父工作区根目录,供兼容服务加载真实机器人模型。 + /// + /// 父工作区根目录。 + public static string GetWorkspaceRoot() + { + var current = new DirectoryInfo(AppContext.BaseDirectory); + while (current is not null) + { + if (File.Exists(Path.Combine(current.FullName, "FlyshotReplacement.sln"))) + { + return Path.GetFullPath(Path.Combine(current.FullName, "..")); + } + + current = current.Parent; + } + + throw new DirectoryNotFoundException("Unable to locate the flyshot workspace root."); + } +} + +/// +/// 记录 ResetRobot 入参的测试运行时,用于验证兼容服务传递的机器人配置。 +/// +internal sealed class RecordingControllerRuntime : IControllerRuntime +{ + private double _speedRatio = 1.0; + + /// + /// 获取最近一次 ResetRobot 收到的机器人配置。 + /// + public RobotProfile? LastRobotProfile { get; private set; } + + /// + /// 获取最近一次 ExecuteTrajectory 收到的结果对象。 + /// + public TrajectoryResult? LastExecutedResult { get; private set; } + + /// + public void ResetRobot(RobotProfile robot, string robotName) + { + LastRobotProfile = robot; + } + + /// + public void SetActiveController(bool sim) + { + } + + /// + public void Connect(string robotIp) + { + } + + /// + public void Disconnect() + { + } + + /// + public void EnableRobot(int bufferSize) + { + } + + /// + public void DisableRobot() + { + } + + /// + public void StopMove() + { + } + + /// + public double GetSpeedRatio() => _speedRatio; + + /// + public void SetSpeedRatio(double ratio) + { + _speedRatio = ratio; + } + + /// + public IReadOnlyList GetTcp() => [0.0, 0.0, 0.0]; + + /// + public void SetTcp(double x, double y, double z) + { + } + + /// + public bool GetIo(int port, string ioType) => false; + + /// + public void SetIo(int port, bool value, string ioType) + { + } + + /// + public IReadOnlyList GetJointPositions() => Array.Empty(); + + /// + public IReadOnlyList GetPose() => Array.Empty(); + + /// + public ControllerStateSnapshot GetSnapshot() + { + return new ControllerStateSnapshot( + capturedAt: DateTimeOffset.UtcNow, + connectionState: "Connected", + isEnabled: true, + isInMotion: false, + speedRatio: _speedRatio, + jointPositions: Array.Empty(), + cartesianPose: Array.Empty(), + activeAlarms: Array.Empty()); + } + + /// + public void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList finalJointPositions) + { + LastExecutedResult = result; + } + + /// + public void ExecuteCartesianTrajectory(TrajectoryResult result, IReadOnlyList finalPose) + { + LastExecutedResult = result; + } +} + +/// +/// 模拟第一段运动异步完成的测试运行时,用于验证兼容层是否等待 move_to_start 完成。 +/// +internal sealed class DelayedCompletionControllerRuntime : IControllerRuntime +{ + private readonly object _lock = new(); + private readonly TimeSpan _firstMotionCompletionDelay; + private double[] _jointPositions; + private bool _isEnabled; + private bool _isInMotion; + private bool _firstMotionCompleted; + + /// + /// 初始化可延迟完成第一段运动的测试运行时。 + /// + /// 运行时报告的初始关节位置。 + /// 第一段运动完成前保持忙碌的时间。 + public DelayedCompletionControllerRuntime( + IReadOnlyList initialJointPositions, + TimeSpan firstMotionCompletionDelay) + { + _jointPositions = initialJointPositions.ToArray(); + _firstMotionCompletionDelay = firstMotionCompletionDelay; + } + + /// + /// 获取所有 ExecuteTrajectory 调用记录。 + /// + public List<(TrajectoryResult Result, IReadOnlyList FinalJointPositions)> ExecuteCalls { get; } = []; + + /// + /// 获取第二条轨迹是否在第一段 move_to_start 完成前启动。 + /// + public bool SecondTrajectoryStartedBeforeFirstMotionCompleted { get; private set; } + + /// + public void ResetRobot(RobotProfile robot, string robotName) + { + } + + /// + public void SetActiveController(bool sim) + { + } + + /// + public void Connect(string robotIp) + { + } + + /// + public void Disconnect() + { + } + + /// + public void EnableRobot(int bufferSize) + { + _isEnabled = true; + } + + /// + public void DisableRobot() + { + _isEnabled = false; + } + + /// + public void StopMove() + { + lock (_lock) + { + _isInMotion = false; + } + } + + /// + public double GetSpeedRatio() => 1.0; + + /// + public void SetSpeedRatio(double ratio) + { + } + + /// + public IReadOnlyList GetTcp() => [0.0, 0.0, 0.0]; + + /// + public void SetTcp(double x, double y, double z) + { + } + + /// + public bool GetIo(int port, string ioType) => false; + + /// + public void SetIo(int port, bool value, string ioType) + { + } + + /// + public IReadOnlyList GetJointPositions() + { + lock (_lock) + { + return _jointPositions.ToArray(); + } + } + + /// + public IReadOnlyList GetPose() => Array.Empty(); + + /// + public ControllerStateSnapshot GetSnapshot() + { + lock (_lock) + { + return new ControllerStateSnapshot( + capturedAt: DateTimeOffset.UtcNow, + connectionState: "Connected", + isEnabled: _isEnabled, + isInMotion: _isInMotion, + speedRatio: 1.0, + jointPositions: _jointPositions.ToArray(), + cartesianPose: Array.Empty(), + activeAlarms: Array.Empty()); + } + } + + /// + public void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList finalJointPositions) + { + lock (_lock) + { + ExecuteCalls.Add((result, finalJointPositions.ToArray())); + if (ExecuteCalls.Count == 1) + { + _isInMotion = true; + _ = Task.Run(async () => + { + await Task.Delay(_firstMotionCompletionDelay).ConfigureAwait(false); + lock (_lock) + { + _jointPositions = finalJointPositions.ToArray(); + _isInMotion = false; + _firstMotionCompleted = true; + } + }); + return; + } + + if (!_firstMotionCompleted) + { + SecondTrajectoryStartedBeforeFirstMotionCompleted = true; + } + + _jointPositions = finalJointPositions.ToArray(); + } + } + + /// + public void ExecuteCartesianTrajectory(TrajectoryResult result, IReadOnlyList finalPose) + { + } +} + +/// +/// 模拟 runtime 执行入口长期占用的测试运行时,用于验证兼容层锁边界。 +/// +internal sealed class BlockingExecutionControllerRuntime : IControllerRuntime +{ + private readonly object _lock = new(); + private readonly ManualResetEventSlim _executionStarted = new(false); + private readonly ManualResetEventSlim _releaseExecution = new(false); + private readonly double[] _jointPositions; + private bool _isEnabled; + private bool _isInMotion; + private int _executeCallCount; + + /// + /// 初始化一份会阻塞 ExecuteTrajectory 的测试运行时。 + /// + /// 初始关节反馈。 + public BlockingExecutionControllerRuntime(IReadOnlyList initialJointPositions) + { + _jointPositions = initialJointPositions.ToArray(); + } + + /// + /// 获取 runtime 执行入口被调用的次数。 + /// + public int ExecuteCallCount + { + get + { + lock (_lock) + { + return _executeCallCount; + } + } + } + + /// + /// 等待指定序号的执行调用进入 runtime。 + /// + /// 最长等待时间。 + /// 期望已经进入的执行次数。 + /// 是否在超时前等到。 + public bool WaitForExecutionStarted(TimeSpan timeout, int expectedCallCount = 1) + { + var deadline = DateTimeOffset.UtcNow.Add(timeout); + while (DateTimeOffset.UtcNow < deadline) + { + lock (_lock) + { + if (_executeCallCount >= expectedCallCount) + { + return true; + } + } + + _executionStarted.Wait(TimeSpan.FromMilliseconds(10)); + } + + return false; + } + + /// + /// 释放当前阻塞的执行调用。 + /// + public void ReleaseExecution() + { + _releaseExecution.Set(); + } + + public void ResetRobot(RobotProfile robot, string robotName) + { + } + + public void SetActiveController(bool sim) + { + } + + public void Connect(string robotIp) + { + } + + public void Disconnect() + { + } + + public void EnableRobot(int bufferSize) + { + _isEnabled = true; + } + + public void DisableRobot() + { + _isEnabled = false; + } + + public void StopMove() + { + lock (_lock) + { + _isInMotion = false; + } + + ReleaseExecution(); + } + + public double GetSpeedRatio() => 1.0; + + public void SetSpeedRatio(double ratio) + { + } + + public IReadOnlyList GetTcp() => [0.0, 0.0, 0.0]; + + public void SetTcp(double x, double y, double z) + { + } + + public bool GetIo(int port, string ioType) => false; + + public void SetIo(int port, bool value, string ioType) + { + } + + public IReadOnlyList GetJointPositions() + { + return _jointPositions.ToArray(); + } + + public IReadOnlyList GetPose() => [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; + + public ControllerStateSnapshot GetSnapshot() + { + lock (_lock) + { + return new ControllerStateSnapshot( + capturedAt: DateTimeOffset.UtcNow, + connectionState: "Connected", + isEnabled: _isEnabled, + isInMotion: _isInMotion, + speedRatio: 1.0, + jointPositions: _jointPositions.ToArray(), + cartesianPose: Array.Empty(), + activeAlarms: Array.Empty()); + } + } + + public void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList finalJointPositions) + { + lock (_lock) + { + _executeCallCount++; + _isInMotion = true; + _executionStarted.Set(); + _releaseExecution.Reset(); + } + + _releaseExecution.Wait(); + + lock (_lock) + { + _isInMotion = false; + _executionStarted.Reset(); + } + } + + public void ExecuteCartesianTrajectory(TrajectoryResult result, IReadOnlyList finalPose) + { + ExecuteTrajectory(result, _jointPositions); + } +} + +/// +/// 模拟 move_to_start 逻辑完成后,真实反馈仍停留在旧位置的测试运行时。 +/// +internal sealed class StickyFeedbackControllerRuntime : IControllerRuntime +{ + private readonly object _lock = new(); + private readonly TimeSpan _firstMotionCompletionDelay; + private double[] _jointPositions; + private bool _isEnabled; + private bool _isInMotion; + private bool _firstMotionCompleted; + + public StickyFeedbackControllerRuntime( + IReadOnlyList initialJointPositions, + TimeSpan firstMotionCompletionDelay) + { + _jointPositions = initialJointPositions.ToArray(); + _firstMotionCompletionDelay = firstMotionCompletionDelay; + } + + public List<(TrajectoryResult Result, IReadOnlyList FinalJointPositions)> ExecuteCalls { get; } = []; + + public void ResetRobot(RobotProfile robot, string robotName) + { + } + + public void SetActiveController(bool sim) + { + } + + public void Connect(string robotIp) + { + } + + public void Disconnect() + { + } + + public void EnableRobot(int bufferSize) + { + _isEnabled = true; + } + + public void DisableRobot() + { + _isEnabled = false; + } + + public void StopMove() + { + lock (_lock) + { + _isInMotion = false; + } + } + + public double GetSpeedRatio() => 1.0; + + public void SetSpeedRatio(double ratio) + { + } + + public IReadOnlyList GetTcp() => [0.0, 0.0, 0.0]; + + public void SetTcp(double x, double y, double z) + { + } + + public bool GetIo(int port, string ioType) => false; + + public void SetIo(int port, bool value, string ioType) + { + } + + public IReadOnlyList GetJointPositions() + { + lock (_lock) + { + return _jointPositions.ToArray(); + } + } + + public IReadOnlyList GetPose() => Array.Empty(); + + public ControllerStateSnapshot GetSnapshot() + { + lock (_lock) + { + return new ControllerStateSnapshot( + capturedAt: DateTimeOffset.UtcNow, + connectionState: "Connected", + isEnabled: _isEnabled, + isInMotion: _isInMotion, + speedRatio: 1.0, + jointPositions: _jointPositions.ToArray(), + cartesianPose: Array.Empty(), + activeAlarms: Array.Empty()); + } + } + + public void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList finalJointPositions) + { + lock (_lock) + { + ExecuteCalls.Add((result, finalJointPositions.ToArray())); + if (!_firstMotionCompleted) + { + _isInMotion = true; + _ = Task.Run(async () => + { + await Task.Delay(_firstMotionCompletionDelay).ConfigureAwait(false); + lock (_lock) + { + // 模拟控制链逻辑上结束,但真实反馈仍卡在旧位置。 + _isInMotion = false; + _firstMotionCompleted = true; + } + }); + return; + } + + _jointPositions = finalJointPositions.ToArray(); + } + } + + public void ExecuteCartesianTrajectory(TrajectoryResult result, IReadOnlyList finalPose) + { + } +} diff --git a/tests/Flyshot.Core.Tests/UttcJ519GoldenTests.cs b/tests/Flyshot.Core.Tests/UttcJ519GoldenTests.cs new file mode 100644 index 0000000..a634269 --- /dev/null +++ b/tests/Flyshot.Core.Tests/UttcJ519GoldenTests.cs @@ -0,0 +1,526 @@ +using System.Buffers.Binary; +using System.Globalization; +using Flyshot.Runtime.Fanuc.Protocol; + +namespace Flyshot.Core.Tests; + +/// +/// 使用 2026-04-28 UTTC 真实抓包验证 J519 主运行点位与 JointDetialTraj 重采样规则一致。 +/// +public sealed class UttcJ519GoldenTests +{ + private const int JointCount = 6; + private const int RobotJ519Port = 60015; + private const double ServoPeriodSeconds = 0.008; + + public static IEnumerable SpeedSweepCases() + { + yield return ["2026042802-0.5.pcap", 0.5, 1851, 14.800309]; + yield return ["2026042802-0.7.pcap", 0.7, 1322, 10.568313]; + yield return ["2026042802-1.pcap", 1.0, 926, 7.400125]; + } + + /// + /// 验证 speed=0.5/0.7/1.0 三份真实抓包都符合当前运行时采用的发送点生成规则。 + /// + [Theory] + [MemberData(nameof(SpeedSweepCases))] + public void CapturedJ519Run_ReplaysJointDetailTrajectoryWithSpeedRatio( + string pcapFileName, + double speedRatio, + int expectedPointCount, + double expectedSendDurationSeconds) + { + var repositoryRoot = FindRepositoryRoot(); + var pcapPath = Path.Combine(repositoryRoot, "Rvbust", "uttc-20260428", pcapFileName); + var jointDetailPath = Path.Combine(repositoryRoot, "Rvbust", "uttc-20260428", "Data", "JointDetialTraj.txt"); + + var packets = ParsePcapUdp(pcapPath); + var hostPort = DetectHostJ519Port(packets); + var commands = ParseJ519Commands(packets, hostPort); + var responses = ParseJ519Responses(packets, hostPort); + var responseSegment = LongestStatusSegment(responses, status: 15); + var jointRows = ReadJointDetail(jointDetailPath); + var expected = GenerateExpectedPoints(jointRows, speedRatio); + + var commandBySequence = new Dictionary(); + var duplicateSequenceCount = 0; + foreach (var command in commands) + { + if (!commandBySequence.TryAdd(command.Sequence, command)) + { + duplicateSequenceCount++; + } + } + + var (startSequence, windowRms) = FindBestCommandWindow(commandBySequence, expected, responseSegment, searchRadius: 32); + var actualRun = Enumerable + .Range(0, expected.Length) + .Select(index => commandBySequence[startSequence + (uint)index]) + .ToArray(); + var comparison = Compare(actualRun, expected); + + Assert.Equal(464, jointRows.Length); + Assert.Equal(expectedPointCount, expected.Length); + Assert.Equal(expectedPointCount, actualRun.Length); + Assert.Equal(0, duplicateSequenceCount); + Assert.Equal(17, comparison.IoSetPulses); + Assert.Equal(17, comparison.IoClearFrames); + Assert.Equal( + new ushort[] { 10, 12, 14 }, + actualRun + .Where(static command => command.WriteIoMask != 0) + .Select(static command => command.WriteIoMask) + .Distinct() + .Order() + .ToArray()); + Assert.True(responseSegment.Length >= expectedPointCount - 1, $"status=15 segment too short: {responseSegment.Length}"); + Assert.InRange((long)responseSegment[0].Sequence - startSequence, 2, 8); + Assert.All(actualRun, static command => Assert.Equal(0, command.LastData)); + for (var index = 0; index < actualRun.Length; index++) + { + Assert.Equal(startSequence + (uint)index, actualRun[index].Sequence); + } + + Assert.True(windowRms < 0.012, $"J519 global RMS {windowRms:F9} deg exceeds tolerance."); + Assert.True(comparison.GlobalMaxAbsDeg < 0.07, $"J519 max abs diff {comparison.GlobalMaxAbsDeg:F9} deg exceeds tolerance."); + + var sendDuration = actualRun[^1].TimeRelativeSeconds - actualRun[0].TimeRelativeSeconds; + Assert.InRange(sendDuration, expectedSendDurationSeconds - 0.04, expectedSendDurationSeconds + 0.04); + } + + private static string FindRepositoryRoot() + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory is not null) + { + var jointDetailPath = Path.Combine(directory.FullName, "Rvbust", "uttc-20260428", "Data", "JointDetialTraj.txt"); + if (File.Exists(jointDetailPath)) + { + return directory.FullName; + } + + directory = directory.Parent; + } + + throw new DirectoryNotFoundException("Cannot locate repository root containing Rvbust/uttc-20260428/Data/JointDetialTraj.txt."); + } + + private static IReadOnlyList ParsePcapUdp(string path) + { + using var stream = File.OpenRead(path); + var header = new byte[24]; + stream.ReadExactly(header); + + var magic = header.AsSpan(0, 4); + var bigEndian = false; + var timestampScale = 1_000_000.0; + if (magic.SequenceEqual(new byte[] { 0xd4, 0xc3, 0xb2, 0xa1 })) + { + bigEndian = false; + } + else if (magic.SequenceEqual(new byte[] { 0xa1, 0xb2, 0xc3, 0xd4 })) + { + bigEndian = true; + } + else if (magic.SequenceEqual(new byte[] { 0x4d, 0x3c, 0xb2, 0xa1 })) + { + bigEndian = false; + timestampScale = 1_000_000_000.0; + } + else if (magic.SequenceEqual(new byte[] { 0xa1, 0xb2, 0x3c, 0x4d })) + { + bigEndian = true; + timestampScale = 1_000_000_000.0; + } + else + { + throw new InvalidDataException($"Unsupported pcap magic: {Convert.ToHexString(header.AsSpan(0, 4))}"); + } + + var linkType = ReadUInt32(header.AsSpan(20, 4), bigEndian); + if (linkType != 1) + { + throw new InvalidDataException($"Only Ethernet pcap is supported, got linktype {linkType}."); + } + + var packets = new List(); + var recordHeader = new byte[16]; + double? firstTimestamp = null; + var frameNumber = 0; + while (ReadFullOrEnd(stream, recordHeader)) + { + frameNumber++; + var tsSec = ReadUInt32(recordHeader.AsSpan(0, 4), bigEndian); + var tsFraction = ReadUInt32(recordHeader.AsSpan(4, 4), bigEndian); + var includedLength = ReadUInt32(recordHeader.AsSpan(8, 4), bigEndian); + var packet = new byte[includedLength]; + stream.ReadExactly(packet); + + var timestamp = tsSec + (tsFraction / timestampScale); + firstTimestamp ??= timestamp; + var udp = ParseEthernetIpv4Udp(packet, frameNumber, timestamp - firstTimestamp.Value); + if (udp is not null) + { + packets.Add(udp); + } + } + + return packets; + } + + private static bool ReadFullOrEnd(Stream stream, byte[] buffer) + { + var offset = 0; + while (offset < buffer.Length) + { + var read = stream.Read(buffer, offset, buffer.Length - offset); + if (read == 0) + { + if (offset == 0) + { + return false; + } + + throw new EndOfStreamException("Truncated pcap record header."); + } + + offset += read; + } + + return true; + } + + private static UdpPacket? ParseEthernetIpv4Udp(byte[] packet, int frameNumber, double timeRelativeSeconds) + { + if (packet.Length < 14) + { + return null; + } + + var offset = 14; + var etherType = BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(12, 2)); + if (etherType == 0x8100) + { + if (packet.Length < 18) + { + return null; + } + + etherType = BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(16, 2)); + offset = 18; + } + + if (etherType != 0x0800 || packet.Length < offset + 20) + { + return null; + } + + var versionIhl = packet[offset]; + var version = versionIhl >> 4; + var ihl = (versionIhl & 0x0f) * 4; + if (version != 4 || ihl < 20 || packet.Length < offset + ihl) + { + return null; + } + + var protocol = packet[offset + 9]; + if (protocol != 17) + { + return null; + } + + var totalLength = BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(offset + 2, 2)); + var udpOffset = offset + ihl; + if (packet.Length < udpOffset + 8) + { + return null; + } + + var sourcePort = BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(udpOffset, 2)); + var destinationPort = BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(udpOffset + 2, 2)); + var udpLength = BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(udpOffset + 4, 2)); + var payloadOffset = udpOffset + 8; + var payloadLength = Math.Max(0, Math.Min(udpLength - 8, totalLength - ihl - 8)); + if (packet.Length < payloadOffset + payloadLength) + { + return null; + } + + return new UdpPacket( + frameNumber, + timeRelativeSeconds, + sourcePort, + destinationPort, + packet.AsSpan(payloadOffset, payloadLength).ToArray()); + } + + private static ushort DetectHostJ519Port(IEnumerable packets) + { + return packets + .Where(static packet => packet.DestinationPort == RobotJ519Port && packet.Payload.Length == FanucJ519Protocol.CommandPacketLength) + .GroupBy(static packet => packet.SourcePort) + .OrderByDescending(static group => group.Count()) + .Select(static group => group.Key) + .First(); + } + + private static CapturedJ519Command[] ParseJ519Commands(IEnumerable packets, ushort hostPort) + { + return packets + .Where(packet => + packet.SourcePort == hostPort + && packet.DestinationPort == RobotJ519Port + && packet.Payload.Length == FanucJ519Protocol.CommandPacketLength) + .Select(ParseCommand) + .Where(static command => command is not null) + .Cast() + .ToArray(); + } + + private static CapturedJ519Command? ParseCommand(UdpPacket packet) + { + var payload = packet.Payload; + var messageType = BinaryPrimitives.ReadUInt32BigEndian(payload.AsSpan(0x00, 4)); + var version = BinaryPrimitives.ReadUInt32BigEndian(payload.AsSpan(0x04, 4)); + if (messageType != 1 || version != 1) + { + return null; + } + + var targets = new double[9]; + for (var index = 0; index < targets.Length; index++) + { + targets[index] = BinaryPrimitives.ReadSingleBigEndian(payload.AsSpan(0x1c + (index * 4), 4)); + } + + return new CapturedJ519Command( + packet.FrameNumber, + packet.TimeRelativeSeconds, + BinaryPrimitives.ReadUInt32BigEndian(payload.AsSpan(0x08, 4)), + payload[0x0c], + BinaryPrimitives.ReadUInt16BigEndian(payload.AsSpan(0x16, 2)), + BinaryPrimitives.ReadUInt16BigEndian(payload.AsSpan(0x18, 2)), + targets); + } + + private static FanucJ519Response[] ParseJ519Responses(IEnumerable packets, ushort hostPort) + { + return packets + .Where(packet => + packet.SourcePort == RobotJ519Port + && packet.DestinationPort == hostPort + && packet.Payload.Length == FanucJ519Protocol.ResponsePacketLength) + .Select(packet => FanucJ519Protocol.ParseResponse(packet.Payload)) + .ToArray(); + } + + private static FanucJ519Response[] LongestStatusSegment(IEnumerable responses, byte status) + { + var best = new List(); + var current = new List(); + foreach (var response in responses) + { + if (response.Status == status) + { + current.Add(response); + continue; + } + + if (current.Count > best.Count) + { + best = current; + } + + current = []; + } + + return (current.Count > best.Count ? current : best).ToArray(); + } + + private static JointRow[] ReadJointDetail(string path) + { + return File.ReadLines(path) + .Where(static line => !string.IsNullOrWhiteSpace(line) && !line.TrimStart().StartsWith('#')) + .Select(static line => + { + var values = line + .Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries) + .Select(value => double.Parse(value, CultureInfo.InvariantCulture)) + .ToArray(); + return new JointRow(values[0], values.Skip(1).Take(JointCount).ToArray()); + }) + .ToArray(); + } + + private static ExpectedPoint[] GenerateExpectedPoints(IReadOnlyList rows, double speedRatio) + { + var durationSeconds = rows[^1].TimeSeconds; + var trajectoryStepSeconds = ServoPeriodSeconds * speedRatio; + var sampleCount = (int)Math.Floor((durationSeconds / trajectoryStepSeconds) + 1e-9) + 1; + var points = new ExpectedPoint[sampleCount]; + var segmentIndex = 0; + + for (var index = 0; index < sampleCount; index++) + { + var trajectoryTime = Math.Min(index * trajectoryStepSeconds, durationSeconds); + var jointsRad = Interpolate(rows, trajectoryTime, ref segmentIndex); + points[index] = new ExpectedPoint( + index, + trajectoryTime, + jointsRad.Select(static value => value * 180.0 / Math.PI).ToArray()); + } + + return points; + } + + private static double[] Interpolate(IReadOnlyList rows, double trajectoryTime, ref int segmentIndex) + { + if (rows.Count == 1 || trajectoryTime <= rows[0].TimeSeconds) + { + return rows[0].JointsRad.ToArray(); + } + + var lastIndex = rows.Count - 1; + if (trajectoryTime >= rows[lastIndex].TimeSeconds) + { + return rows[lastIndex].JointsRad.ToArray(); + } + + while (segmentIndex < lastIndex - 1 && rows[segmentIndex + 1].TimeSeconds < trajectoryTime) + { + segmentIndex++; + } + + var start = rows[segmentIndex]; + var end = rows[segmentIndex + 1]; + var duration = end.TimeSeconds - start.TimeSeconds; + var alpha = duration <= 0.0 ? 0.0 : (trajectoryTime - start.TimeSeconds) / duration; + var joints = new double[JointCount]; + for (var index = 0; index < joints.Length; index++) + { + joints[index] = start.JointsRad[index] + ((end.JointsRad[index] - start.JointsRad[index]) * alpha); + } + + return joints; + } + + private static (uint StartSequence, double RmsDeg) FindBestCommandWindow( + IReadOnlyDictionary commandBySequence, + IReadOnlyList expected, + IReadOnlyList responseSegment, + int searchRadius) + { + if (responseSegment.Count == 0) + { + throw new InvalidDataException("No status=15 response segment found."); + } + + var responseStartSequence = (long)responseSegment[0].Sequence; + uint? bestStartSequence = null; + var bestRms = double.PositiveInfinity; + + for (var startSequence = responseStartSequence - searchRadius; startSequence <= responseStartSequence + searchRadius; startSequence++) + { + if (startSequence < 0) + { + continue; + } + + var differences = new List(expected.Count * JointCount); + var completeWindow = true; + for (var index = 0; index < expected.Count; index++) + { + var sequence = (uint)(startSequence + index); + if (!commandBySequence.TryGetValue(sequence, out var command)) + { + completeWindow = false; + break; + } + + for (var joint = 0; joint < JointCount; joint++) + { + differences.Add(command.TargetDegrees[joint] - expected[index].JointsDeg[joint]); + } + } + + if (!completeWindow) + { + continue; + } + + var rms = Rms(differences); + if (rms < bestRms) + { + bestRms = rms; + bestStartSequence = (uint)startSequence; + } + } + + if (bestStartSequence is null) + { + throw new InvalidDataException("No complete command window found near the status=15 response segment."); + } + + return (bestStartSequence.Value, bestRms); + } + + private static ComparisonSummary Compare(IReadOnlyList actual, IReadOnlyList expected) + { + var differences = new List(actual.Count * JointCount); + var maxAbs = 0.0; + for (var index = 0; index < actual.Count; index++) + { + for (var joint = 0; joint < JointCount; joint++) + { + var difference = actual[index].TargetDegrees[joint] - expected[index].JointsDeg[joint]; + differences.Add(difference); + maxAbs = Math.Max(maxAbs, Math.Abs(difference)); + } + } + + var ioSetPulses = actual.Count(command => command.WriteIoMask != 0 && command.WriteIoValue != 0); + var ioClearFrames = actual.Count(command => command.WriteIoMask != 0 && command.WriteIoValue == 0); + return new ComparisonSummary(Rms(differences), maxAbs, ioSetPulses, ioClearFrames); + } + + private static double Rms(IEnumerable values) + { + var sum = 0.0; + var count = 0; + foreach (var value in values) + { + sum += value * value; + count++; + } + + return count == 0 ? 0.0 : Math.Sqrt(sum / count); + } + + private static uint ReadUInt32(ReadOnlySpan value, bool bigEndian) + { + return bigEndian ? BinaryPrimitives.ReadUInt32BigEndian(value) : BinaryPrimitives.ReadUInt32LittleEndian(value); + } + + private sealed record UdpPacket( + int FrameNumber, + double TimeRelativeSeconds, + ushort SourcePort, + ushort DestinationPort, + byte[] Payload); + + private sealed record CapturedJ519Command( + int FrameNumber, + double TimeRelativeSeconds, + uint Sequence, + byte LastData, + ushort WriteIoMask, + ushort WriteIoValue, + IReadOnlyList TargetDegrees); + + private sealed record JointRow(double TimeSeconds, IReadOnlyList JointsRad); + + private sealed record ExpectedPoint(int Index, double TrajectoryTimeSeconds, IReadOnlyList JointsDeg); + + private sealed record ComparisonSummary(double GlobalRmsDeg, double GlobalMaxAbsDeg, int IoSetPulses, int IoClearFrames); +} diff --git a/tests/Flyshot.Server.IntegrationTests/ControllerClientCompatRegistrationTests.cs b/tests/Flyshot.Server.IntegrationTests/ControllerClientCompatRegistrationTests.cs new file mode 100644 index 0000000..7daf0cc --- /dev/null +++ b/tests/Flyshot.Server.IntegrationTests/ControllerClientCompatRegistrationTests.cs @@ -0,0 +1,21 @@ +using Flyshot.ControllerClientCompat; +using Microsoft.Extensions.DependencyInjection; + +namespace Flyshot.Server.IntegrationTests; + +/// +/// 锁定宿主当前应注册 HTTP-only 的 ControllerClient 兼容服务,而不是旧 TCP 网关入口。 +/// +public sealed class ControllerClientCompatRegistrationTests(FlyshotServerFactory factory) : IClassFixture +{ + /// + /// 验证宿主能从 DI 中解析新的兼容服务。 + /// + [Fact] + public void Host_Registers_ControllerClientCompat_Service() + { + var service = factory.Services.GetService(); + + Assert.NotNull(service); + } +} diff --git a/tests/Flyshot.Server.IntegrationTests/DebugConsoleEndpointTests.cs b/tests/Flyshot.Server.IntegrationTests/DebugConsoleEndpointTests.cs new file mode 100644 index 0000000..0fcf321 --- /dev/null +++ b/tests/Flyshot.Server.IntegrationTests/DebugConsoleEndpointTests.cs @@ -0,0 +1,182 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Configuration; + +namespace Flyshot.Server.IntegrationTests; + +/// +/// 验证 `wwwroot` 静态调试页和调试配置 API 的基础内容契约。 +/// +/// +/// 调试页自身是静态 HTML,真正的 Swagger 地址由配置 API 下发; +/// 当 Swagger 关闭时,配置 API 返回 404,前端据此显示不可用状态。 +/// +public sealed class DebugConsoleEndpointTests(FlyshotServerFactory factory) : IClassFixture +{ + private readonly FlyshotServerFactory _factory = factory; + + /// + /// `debug.html` 应当作为可直接调试的静态页面暴露。 + /// + [Fact] + public async Task GetDebugHtml_ReturnsConsoleStaticPage() + { + using var configuredFactory = CreateFactoryWithSwaggerEnabled(true); + using var client = configuredFactory.CreateClient(); + + using var response = await client.GetAsync("/debug.html"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.StartsWith("text/html", response.Content.Headers.ContentType?.MediaType); + + var html = await response.Content.ReadAsStringAsync(); + + Assert.Contains("Flyshot Replacement 接口调试", html, StringComparison.Ordinal); + Assert.Contains("id=\"debug-console-app\"", html, StringComparison.Ordinal); + Assert.DoesNotContain("__SWAGGER_JSON_URL__", html, StringComparison.Ordinal); + Assert.Contains("/assets/debug.css", html, StringComparison.Ordinal); + Assert.Contains("/assets/debug.js", html, StringComparison.Ordinal); + + using var scriptResponse = await client.GetAsync("/assets/debug.js"); + Assert.Equal(HttpStatusCode.OK, scriptResponse.StatusCode); + + var script = await scriptResponse.Content.ReadAsStringAsync(); + Assert.Contains("/api/debug/config", script, StringComparison.Ordinal); + } + + /// + /// 调试页应当为 MovePose 提供可直接发送的六字段请求体模板。 + /// + [Fact] + public async Task GetDebugScript_ContainsMovePoseRequestSample() + { + using var configuredFactory = CreateFactoryWithSwaggerEnabled(true); + using var client = configuredFactory.CreateClient(); + + using var scriptResponse = await client.GetAsync("/assets/debug.js"); + Assert.Equal(HttpStatusCode.OK, scriptResponse.StatusCode); + + var script = await scriptResponse.Content.ReadAsStringAsync(); + Assert.Contains("/move_pose/", script, StringComparison.Ordinal); + Assert.Contains("x: 100.0", script, StringComparison.Ordinal); + Assert.Contains("y: 200.0", script, StringComparison.Ordinal); + Assert.Contains("z: 300.0", script, StringComparison.Ordinal); + Assert.Contains("w: 0.0", script, StringComparison.Ordinal); + Assert.Contains("p: 45.0", script, StringComparison.Ordinal); + Assert.Contains("r: 0.0", script, StringComparison.Ordinal); + } + + /// + /// 当 Swagger 启用时,调试配置 API 应当返回实际 Swagger JSON 地址。 + /// + [Fact] + public async Task GetDebugConfig_WhenSwaggerEnabled_ReturnsSwaggerJsonUrl() + { + using var configuredFactory = CreateFactoryWithSwaggerEnabled(true); + using var client = configuredFactory.CreateClient(); + + using var response = await client.GetAsync("/api/debug/config"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + await using var responseStream = await response.Content.ReadAsStreamAsync(); + using var document = await System.Text.Json.JsonDocument.ParseAsync(responseStream); + + Assert.Equal("/swagger/v1/swagger.json", document.RootElement.GetProperty("swaggerJsonUrl").GetString()); + } + + /// + /// 当 Swagger 关闭时,调试配置 API 应当与 Swagger UI 同步下线(404)。 + /// + [Fact] + public async Task GetDebugConfig_WhenSwaggerDisabled_ReturnsNotFound() + { + using var configuredFactory = CreateFactoryWithSwaggerEnabled(false); + using var client = configuredFactory.CreateClient(); + + using var response = await client.GetAsync("/api/debug/config"); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + /// + /// 调试页需要从 Swagger JSON 中读取所有端点,因此 Swagger 文档必须包含基础和兼容层的代表性路由。 + /// + [Fact] + public async Task SwaggerDocument_ContainsRepresentativeRoutesForDebugConsole() + { + using var configuredFactory = CreateFactoryWithSwaggerEnabled(true); + using var client = configuredFactory.CreateClient(); + + using var response = await client.GetAsync("/swagger/v1/swagger.json"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + await using var responseStream = await response.Content.ReadAsStreamAsync(); + using var document = await System.Text.Json.JsonDocument.ParseAsync(responseStream); + var paths = document.RootElement.GetProperty("paths"); + + // 这些路径分别覆盖:基础探活、状态快照、版本查询、上传飞拍轨迹四种典型形态。 + AssertPathExists(paths, "/healthz"); + AssertPathExists(paths, "/api/status/snapshot"); + AssertPathExists(paths, "/get_server_version"); + AssertPathExists(paths, "/upload_flyshot"); + } + + /// + /// 状态页应当提供跳转到静态调试页的入口,便于现场顺手跳转。 + /// + [Fact] + public async Task GetStatusHtml_LinksToDebugConsole() + { + using var configuredFactory = CreateFactoryWithSwaggerEnabled(true); + using var client = configuredFactory.CreateClient(); + + using var response = await client.GetAsync("/status.html"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var html = await response.Content.ReadAsStringAsync(); + + Assert.Contains("href=\"/debug.html\"", html, StringComparison.Ordinal); + } + + /// + /// 检查 Swagger 文档中是否存在指定路径,兼容尾斜杠归一化两种形态。 + /// + /// OpenAPI 文档中的 paths 节点。 + /// 期望存在的路由字符串。 + private static void AssertPathExists(System.Text.Json.JsonElement paths, string route) + { + // OpenAPI 生成器会把部分尾斜杠路径规范化,这里同时接受两种形态。 + var withSlash = route.EndsWith('/') ? route : route + "/"; + var withoutSlash = route.EndsWith('/') ? route.TrimEnd('/') : route; + + Assert.True( + paths.TryGetProperty(withSlash, out _) || paths.TryGetProperty(withoutSlash, out _), + $"Swagger 文档应当包含路径 {route}"); + } + + /// + /// 构造覆盖了 Swagger:Enabled 配置项的测试宿主工厂。 + /// + /// 期望的 Swagger 启用状态。 + /// 已应用配置覆盖的测试工厂。 + private WebApplicationFactory CreateFactoryWithSwaggerEnabled(bool enabled) + { + return _factory.WithWebHostBuilder(builder => + { + builder.ConfigureAppConfiguration((_, configurationBuilder) => + { + // 通过 InMemory 配置覆盖 appsettings.json 中的 Swagger 开关,避免修改磁盘文件。 + configurationBuilder.AddInMemoryCollection(new Dictionary + { + ["Swagger:Enabled"] = enabled ? "true" : "false", + ["Swagger:DocumentName"] = "v1", + ["Swagger:Title"] = "Flyshot Replacement HTTP API", + ["Swagger:Version"] = "v1", + ["Swagger:JsonRouteTemplate"] = "swagger/{documentName}/swagger.json", + ["Swagger:RoutePrefix"] = "swagger" + }); + }); + }); + } +} diff --git a/tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj b/tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj index 0c8181e..4a34fda 100644 --- a/tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj +++ b/tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj @@ -24,6 +24,7 @@ + diff --git a/tests/Flyshot.Server.IntegrationTests/HostMvcConfigurationTests.cs b/tests/Flyshot.Server.IntegrationTests/HostMvcConfigurationTests.cs new file mode 100644 index 0000000..f21b9fe --- /dev/null +++ b/tests/Flyshot.Server.IntegrationTests/HostMvcConfigurationTests.cs @@ -0,0 +1,84 @@ +using System.Net; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Configuration; + +namespace Flyshot.Server.IntegrationTests; + +/// +/// 锁定标准 MVC 宿主需要提供的 Swagger 与 CORS 行为,避免后续回退成只够跑通的最小配置。 +/// +public sealed class HostMvcConfigurationTests(FlyshotServerFactory factory) : IClassFixture +{ + /// + /// 验证宿主会公开标准 Swagger JSON,并且文档标题和旧 HTTP 兼容路径都能从配置和控制器路由中导出。 + /// + [Fact] + public async Task SwaggerDocument_ExposesConfiguredMetadataAndLegacyRoutes() + { + using var configuredFactory = CreateConfiguredFactory(factory); + using var client = configuredFactory.CreateClient(); + + using var response = await client.GetAsync("/swagger/v1/swagger.json"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + await using var responseStream = await response.Content.ReadAsStreamAsync(); + using var document = await JsonDocument.ParseAsync(responseStream); + var root = document.RootElement; + var paths = root.GetProperty("paths"); + + Assert.Equal("3.0.1", root.GetProperty("openapi").GetString()); + Assert.Equal("Flyshot Replacement HTTP API", root.GetProperty("info").GetProperty("title").GetString()); + // OpenAPI 文档会把部分带尾斜杠的路由规范化为无尾斜杠形式,这里同时接受两种键。 + Assert.True(paths.TryGetProperty("/robot_info/", out _) || paths.TryGetProperty("/robot_info", out _)); + Assert.True(paths.TryGetProperty("/healthz", out _) || paths.TryGetProperty("/healthz/", out _)); + } + + /// + /// 验证宿主会按配置对旧 HTTP API 路由返回标准 CORS 预检响应。 + /// + [Fact] + public async Task CorsPreflight_ReturnsConfiguredAllowOriginHeaders() + { + using var configuredFactory = CreateConfiguredFactory(factory); + using var client = configuredFactory.CreateClient(); + + using var request = new HttpRequestMessage(HttpMethod.Options, "/robot_info/"); + request.Headers.Add("Origin", "http://localhost:3000"); + request.Headers.Add("Access-Control-Request-Method", "GET"); + request.Headers.Add("Access-Control-Request-Headers", "content-type"); + + using var response = await client.SendAsync(request); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + Assert.True(response.Headers.TryGetValues("Access-Control-Allow-Origin", out var allowedOrigins)); + Assert.Contains("http://localhost:3000", allowedOrigins); + Assert.True(response.Headers.TryGetValues("Access-Control-Allow-Methods", out var allowedMethods)); + Assert.Contains("GET", string.Join(",", allowedMethods)); + } + + /// + /// 为测试宿主注入标准 Swagger 与 CORS 配置,避免依赖开发机本地环境。 + /// + private static WebApplicationFactory CreateConfiguredFactory(FlyshotServerFactory factory) + { + return factory.WithWebHostBuilder(builder => + { + builder.ConfigureAppConfiguration((_, configurationBuilder) => + { + configurationBuilder.AddInMemoryCollection(new Dictionary + { + ["Swagger:Enabled"] = "true", + ["Swagger:DocumentName"] = "v1", + ["Swagger:Title"] = "Flyshot Replacement HTTP API", + ["Swagger:Version"] = "v1", + ["Cors:PolicyName"] = "LegacyHttpApi", + ["Cors:AllowedOrigins:0"] = "http://localhost:3000", + ["Cors:AllowedMethods:0"] = "GET", + ["Cors:AllowedMethods:1"] = "POST", + ["Cors:AllowedMethods:2"] = "OPTIONS", + ["Cors:AllowedHeaders:0"] = "content-type" + }); + }); + }); + } +} diff --git a/tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs b/tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs new file mode 100644 index 0000000..0777072 --- /dev/null +++ b/tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs @@ -0,0 +1,324 @@ +using System.Net; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; + +namespace Flyshot.Server.IntegrationTests; + +/// +/// 锁定 flyshot-uaes-interface 现有 FastAPI 层的 HTTP 路径、参数绑定和返回 JSON 外形。 +/// +public sealed class LegacyHttpApiCompatibilityTests(FlyshotServerFactory factory) : IClassFixture +{ + /// + /// 验证根路径会返回旧 HTTP 服务使用的 Hello World JSON,而不是跳转到健康检查页。 + /// + [Fact] + public async Task Root_ReturnsLegacyHelloWorldPayload() + { + using var client = factory.CreateClient(); + + using var response = await client.GetAsync("/"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + using var json = await ReadJsonAsync(response); + Assert.Equal("Hello World", json.RootElement.GetProperty("message").GetString()); + } + + /// + /// 验证初始化链路和机器人信息接口会保持旧 FastAPI 服务的路径与返回字段风格。 + /// + [Fact] + public async Task InitEndpoints_ExposeLegacyRobotInfoAndSpeedRatioShape() + { + using var client = factory.CreateClient(); + + using (var connectServerResponse = await client.PostAsync("/connect_server/?server_ip=127.0.0.1&port=50001", content: null)) + { + Assert.Equal(HttpStatusCode.OK, connectServerResponse.StatusCode); + using var connectServerJson = await ReadJsonAsync(connectServerResponse); + Assert.Equal("connected", connectServerJson.RootElement.GetProperty("status").GetString()); + } + + using (var setupResponse = await client.PostAsync("/setup_robot/?robot_name=FANUC_LR_Mate_200iD", content: null)) + { + Assert.Equal(HttpStatusCode.OK, setupResponse.StatusCode); + using var setupJson = await ReadJsonAsync(setupResponse); + Assert.Equal("robot setup", setupJson.RootElement.GetProperty("status").GetString()); + } + + using (var isSetupResponse = await client.GetAsync("/is_setup/")) + { + Assert.Equal(HttpStatusCode.OK, isSetupResponse.StatusCode); + using var isSetupJson = await ReadJsonAsync(isSetupResponse); + Assert.True(isSetupJson.RootElement.GetProperty("is_setup").GetBoolean()); + } + + using (var activeControllerResponse = await client.PostAsync("/set_active_controller/?sim=true", content: null)) + { + Assert.Equal(HttpStatusCode.OK, activeControllerResponse.StatusCode); + using var activeControllerJson = await ReadJsonAsync(activeControllerResponse); + Assert.Equal("active controller set", activeControllerJson.RootElement.GetProperty("status").GetString()); + } + + using (var connectRobotResponse = await client.PostAsync("/connect_robot/?ip=192.168.10.101", content: null)) + { + Assert.Equal(HttpStatusCode.OK, connectRobotResponse.StatusCode); + using var connectRobotJson = await ReadJsonAsync(connectRobotResponse); + Assert.Equal("robot connected", connectRobotJson.RootElement.GetProperty("status").GetString()); + } + + using (var enableRobotResponse = await client.GetAsync("/enable_robot/")) + { + Assert.Equal(HttpStatusCode.OK, enableRobotResponse.StatusCode); + using var enableRobotJson = await ReadJsonAsync(enableRobotResponse); + Assert.True(enableRobotJson.RootElement.GetProperty("enable_robot").GetBoolean()); + } + + using (var robotInfoResponse = await client.GetAsync("/robot_info/")) + { + Assert.Equal(HttpStatusCode.OK, robotInfoResponse.StatusCode); + using var robotInfoJson = await ReadJsonAsync(robotInfoResponse); + var robotInfoRoot = robotInfoJson.RootElement; + + Assert.Equal("FANUC_LR_Mate_200iD", robotInfoRoot.GetProperty("name").GetString()); + Assert.Equal("flyshot-replacement-controller-client-compat/0.1.0", robotInfoRoot.GetProperty("server_version").GetString()); + Assert.Equal(6, robotInfoRoot.GetProperty("dof").GetInt32()); + Assert.Equal(1.0, robotInfoRoot.GetProperty("speed_ratio").GetDouble(), precision: 6); + } + + using (var setSpeedRatioResponse = await client.PostAsJsonAsync("/set_speedRatio/", new { speed = 0.8 })) + { + Assert.Equal(HttpStatusCode.OK, setSpeedRatioResponse.StatusCode); + using var setSpeedRatioJson = await ReadJsonAsync(setSpeedRatioResponse); + Assert.Equal("set_speedRatio executed", setSpeedRatioJson.RootElement.GetProperty("message").GetString()); + Assert.Equal(0, setSpeedRatioJson.RootElement.GetProperty("returnCode").GetInt32()); + } + + using var updatedRobotInfoResponse = await client.GetAsync("/robot_info/"); + Assert.Equal(HttpStatusCode.OK, updatedRobotInfoResponse.StatusCode); + using var updatedRobotInfoJson = await ReadJsonAsync(updatedRobotInfoResponse); + Assert.Equal(0.8, updatedRobotInfoJson.RootElement.GetProperty("speed_ratio").GetDouble(), precision: 6); + } + + /// + /// 验证 TCP、关节位置和位姿相关 HTTP 接口会保持旧服务的请求体与响应体结构。 + /// + [Fact] + public async Task MotionStateEndpoints_RoundTripLegacyPayloadShapes() + { + using var client = factory.CreateClient(); + await InitializeRobotAsync(client); + + using (var setTcpResponse = await client.PostAsJsonAsync("/set_tcp/", new { x = 1.0, y = 2.0, z = 3.0 })) + { + Assert.Equal(HttpStatusCode.OK, setTcpResponse.StatusCode); + using var setTcpJson = await ReadJsonAsync(setTcpResponse); + Assert.Equal("TCP set", setTcpJson.RootElement.GetProperty("status").GetString()); + } + + using (var getTcpResponse = await client.GetAsync("/get_tcp/")) + { + Assert.Equal(HttpStatusCode.OK, getTcpResponse.StatusCode); + using var getTcpJson = await ReadJsonAsync(getTcpResponse); + var tcpValues = getTcpJson.RootElement.GetProperty("tcp").EnumerateArray().Select(static value => value.GetDouble()).ToArray(); + Assert.Equal([1.0, 2.0, 3.0], tcpValues); + } + + using (var moveJointResponse = await client.PostAsJsonAsync("/move_joint/", new { joints = new[] { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 } })) + { + Assert.Equal(HttpStatusCode.OK, moveJointResponse.StatusCode); + using var moveJointJson = await ReadJsonAsync(moveJointResponse); + Assert.Equal("robot moved", moveJointJson.RootElement.GetProperty("status").GetString()); + } + + using (var movePoseResponse = await client.PostAsJsonAsync("/move_pose/", new { x = 100.0, y = 200.0, z = 300.0, w = 1.0, p = 2.0, r = 3.0 })) + { + Assert.Equal(HttpStatusCode.OK, movePoseResponse.StatusCode); + using var movePoseJson = await ReadJsonAsync(movePoseResponse); + Assert.Equal("robot moved", movePoseJson.RootElement.GetProperty("status").GetString()); + } + + using (var getJointPositionResponse = await client.GetAsync("/get_joint_position/")) + { + Assert.Equal(HttpStatusCode.OK, getJointPositionResponse.StatusCode); + using var getJointPositionJson = await ReadJsonAsync(getJointPositionResponse); + var root = getJointPositionJson.RootElement; + Assert.True(root.GetProperty("success").GetBoolean()); + var jointValues = root.GetProperty("points").EnumerateArray().Select(static value => value.GetDouble()).ToArray(); + Assert.Equal([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], jointValues); + } + + using var getPoseResponse = await client.GetAsync("/get_pose"); + Assert.Equal(HttpStatusCode.OK, getPoseResponse.StatusCode); + using var getPoseJson = await ReadJsonAsync(getPoseResponse); + Assert.Equal(7, getPoseJson.RootElement.GetProperty("pose").GetArrayLength()); + + using (var executeTrajectoryResponse = await client.PostAsJsonAsync("/execute_trajectory/", new + { + method = "icsp", + save_traj = true, + waypoints = new[] + { + new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.1, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.2, 0.0, 0.0, 0.0, 0.0, 0.0 }, + new[] { 0.3, 0.0, 0.0, 0.0, 0.0, 0.0 } + } + })) + { + Assert.Equal(HttpStatusCode.OK, executeTrajectoryResponse.StatusCode); + using var executeTrajectoryJson = await ReadJsonAsync(executeTrajectoryResponse); + Assert.Equal("trajectory executed", executeTrajectoryJson.RootElement.GetProperty("status").GetString()); + } + } + + /// + /// 验证 MovePose 请求必须显式提供六个有限直角坐标字段,避免缺字段被模型绑定静默补 0。 + /// + [Theory] + [InlineData("""{"x":100.0,"y":200.0,"z":300.0,"w":1.0,"p":2.0}""")] + [InlineData("null")] + [InlineData("""{"x":1e999,"y":200.0,"z":300.0,"w":1.0,"p":2.0,"r":3.0}""")] + [InlineData("""{"x":1000.1,"y":200.0,"z":300.0,"w":1.0,"p":2.0,"r":3.0}""")] + [InlineData("""{"x":100.0,"y":-1000.1,"z":300.0,"w":1.0,"p":2.0,"r":3.0}""")] + [InlineData("""{"x":100.0,"y":200.0,"z":-0.1,"w":1.0,"p":2.0,"r":3.0}""")] + [InlineData("""{"x":100.0,"y":200.0,"z":300.0,"w":-180.1,"p":2.0,"r":3.0}""")] + [InlineData("""{"x":100.0,"y":200.0,"z":300.0,"w":1.0,"p":90.1,"r":3.0}""")] + [InlineData("""{"x":100.0,"y":200.0,"z":300.0,"w":1.0,"p":2.0,"r":180.1}""")] + public async Task MovePose_InvalidPayload_ReturnsLegacyBadRequest(string payload) + { + using var client = factory.CreateClient(); + await InitializeRobotAsync(client); + + using var content = new StringContent(payload, Encoding.UTF8, "application/json"); + using var response = await client.PostAsync("/move_pose/", content); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + using var json = await ReadJsonAsync(response); + Assert.Equal("MovePose failed", json.RootElement.GetProperty("detail").GetString()); + } + + /// + /// 验证飞拍 HTTP 接口可以按旧 API 层的路径和字段完成上传、列出、执行与删除。 + /// + [Fact] + public async Task FlyshotEndpoints_RoundTripLegacyUploadExecuteAndDeleteFlow() + { + using var client = factory.CreateClient(); + await InitializeRobotAsync(client); + + var uploadPayload = new + { + addrs = new[] + { + new[] { 7, 8 }, + new[] { 7, 8 }, + Array.Empty(), + Array.Empty() + }, + name = "demo-http-flyshot", + offset_values = new[] { 0.0, 1.0, 0.0, 0.0 }, + shot_flags = new[] { false, true, false, false }, + waypoints = new[] + { + new[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6 }, + new[] { 0.2, 0.2, 0.3, 0.4, 0.5, 0.6 }, + new[] { 0.3, 0.2, 0.3, 0.4, 0.5, 0.6 }, + new[] { 0.4, 0.2, 0.3, 0.4, 0.5, 0.6 } + } + }; + + using (var uploadResponse = await client.PostAsJsonAsync("/upload_flyshot/", uploadPayload)) + { + Assert.Equal(HttpStatusCode.OK, uploadResponse.StatusCode); + using var uploadJson = await ReadJsonAsync(uploadResponse); + Assert.Equal("FlyShot uploaded", uploadJson.RootElement.GetProperty("status").GetString()); + } + + using (var listResponse = await client.GetAsync("/list_flyShotTraj/")) + { + Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); + using var listJson = await ReadJsonAsync(listResponse); + var names = listJson.RootElement.GetProperty("flyshot_trajs").EnumerateArray().Select(static value => value.GetString()).ToArray(); + Assert.Contains("demo-http-flyshot", names); + } + + using (var validResponse = await client.PostAsJsonAsync("/is_flyShotTrajValid/", new + { + name = "demo-http-flyshot", + method = "icsp", + save_traj = false + })) + { + Assert.Equal(HttpStatusCode.OK, validResponse.StatusCode); + using var validJson = await ReadJsonAsync(validResponse); + Assert.True(validJson.RootElement.GetProperty("valid").GetBoolean()); + Assert.True(validJson.RootElement.GetProperty("time").GetDouble() > 0.0); + } + + using (var executeResponse = await client.PostAsJsonAsync("/execute_flyshot/", new + { + name = "demo-http-flyshot", + move_to_start = true, + method = "icsp", + save_traj = true, + use_cache = true, + wait = true + })) + { + Assert.Equal(HttpStatusCode.OK, executeResponse.StatusCode); + using var executeJson = await ReadJsonAsync(executeResponse); + var executeRoot = executeJson.RootElement; + Assert.Equal("FlyShot executed", executeRoot.GetProperty("status").GetString()); + Assert.True(executeRoot.GetProperty("success").GetBoolean()); + } + + using (var saveInfoResponse = await client.PostAsJsonAsync("/save_traj_info/", new + { + name = "demo-http-flyshot", + method = "icsp" + })) + { + Assert.Equal(HttpStatusCode.OK, saveInfoResponse.StatusCode); + using var saveInfoJson = await ReadJsonAsync(saveInfoResponse); + Assert.True(saveInfoJson.RootElement.GetProperty("success").GetBoolean()); + } + + using (var deleteResponse = await client.PostAsJsonAsync("/delete_flyshot/", new { name = "demo-http-flyshot" })) + { + Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); + using var deleteJson = await ReadJsonAsync(deleteResponse); + Assert.Equal("FlyShot deleted", deleteJson.RootElement.GetProperty("status").GetString()); + } + } + + /// + /// 复用旧 API 层常见的初始化顺序,把当前宿主推进到可执行动作的最小状态。 + /// + private static async Task InitializeRobotAsync(HttpClient client) + { + using var initResponse = await client.PostAsJsonAsync("/init_mpc_robt", new + { + server_ip = "127.0.0.1", + port = 50001, + robot_name = "FANUC_LR_Mate_200iD", + robot_ip = "192.168.10.101", + sim = true + }); + + Assert.Equal(HttpStatusCode.OK, initResponse.StatusCode); + using var initJson = await ReadJsonAsync(initResponse); + Assert.Equal("init_Success", initJson.RootElement.GetProperty("message").GetString()); + Assert.Equal(0, initJson.RootElement.GetProperty("returnCode").GetInt32()); + } + + /// + /// 统一把 HTTP 响应体解析成 JsonDocument,便于对旧接口的字段形状做精确断言。 + /// + private static async Task ReadJsonAsync(HttpResponseMessage response) + { + await using var responseStream = await response.Content.ReadAsStreamAsync(); + return await JsonDocument.ParseAsync(responseStream); + } +} diff --git a/tests/Flyshot.Server.IntegrationTests/RequestResponseLoggingMiddlewareTests.cs b/tests/Flyshot.Server.IntegrationTests/RequestResponseLoggingMiddlewareTests.cs new file mode 100644 index 0000000..ce027b7 --- /dev/null +++ b/tests/Flyshot.Server.IntegrationTests/RequestResponseLoggingMiddlewareTests.cs @@ -0,0 +1,80 @@ +using Flyshot.Server.Host.Middleware; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Flyshot.Server.IntegrationTests; + +/// +/// HTTP 请求响应日志中间件测试。 +/// +public sealed class RequestResponseLoggingMiddlewareTests +{ + /// + /// 高频状态快照路径命中忽略前缀时,不应写入请求和响应日志。 + /// + [Fact] + public async Task InvokeAsync_WhenPathMatchesIgnoredPrefix_DoesNotWriteRequestResponseLogs() + { + var logger = new CapturingLogger(); + var nextWasCalled = false; + var middleware = new RequestResponseLoggingMiddleware( + async context => + { + nextWasCalled = true; + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsync("ok"); + }, + logger); + var context = new DefaultHttpContext(); + context.Request.Method = HttpMethods.Get; + context.Request.Path = "/api/status/snapshot/current"; + context.Response.Body = new MemoryStream(); + + await middleware.InvokeAsync(context); + + Assert.True(nextWasCalled); + Assert.Empty(logger.Entries); + } + + /// + /// 捕获中间件写出的日志条目,避免测试依赖真实 NLog 目标。 + /// + private sealed class CapturingLogger : ILogger + { + /// + /// 已捕获的日志条目。 + /// + public List Entries { get; } = new(); + + /// + public IDisposable? BeginScope(TState state) + where TState : notnull + { + return null; + } + + /// + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + /// + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) + { + Entries.Add(new LogEntry(logLevel, formatter(state, exception))); + } + } + + /// + /// 测试用日志条目。 + /// + /// 日志级别。 + /// 格式化后的日志消息。 + private sealed record LogEntry(LogLevel Level, string Message); +} diff --git a/tests/Flyshot.Server.IntegrationTests/StatusEndpointTests.cs b/tests/Flyshot.Server.IntegrationTests/StatusEndpointTests.cs new file mode 100644 index 0000000..a8986fc --- /dev/null +++ b/tests/Flyshot.Server.IntegrationTests/StatusEndpointTests.cs @@ -0,0 +1,157 @@ +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; + +namespace Flyshot.Server.IntegrationTests; + +/// +/// 验证状态监控页面和状态快照 API 能读取当前 ControllerClient 兼容层状态。 +/// +public sealed class StatusEndpointTests(FlyshotServerFactory factory) : IClassFixture +{ + /// + /// 验证状态页作为 wwwroot 静态 HTML 暴露,并引用状态快照 API。 + /// + [Fact] + public async Task GetStatusHtml_ReturnsMonitoringStaticPage() + { + using var client = factory.CreateClient(); + + using var response = await client.GetAsync("/status.html"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.StartsWith("text/html", response.Content.Headers.ContentType?.MediaType); + + var html = await response.Content.ReadAsStringAsync(); + Assert.Contains("Flyshot Replacement 状态监控", html, StringComparison.Ordinal); + Assert.Contains("/assets/status.css", html, StringComparison.Ordinal); + Assert.Contains("/assets/status.js", html, StringComparison.Ordinal); + + using var scriptResponse = await client.GetAsync("/assets/status.js"); + Assert.Equal(HttpStatusCode.OK, scriptResponse.StatusCode); + + var script = await scriptResponse.Content.ReadAsStringAsync(); + Assert.Contains("/api/status/snapshot", script, StringComparison.Ordinal); + } + + /// + /// 状态页应当提供双摇杆直角坐标点动面板,并复用现有 MovePose HTTP 接口。 + /// + [Fact] + public async Task GetStatusPageAssets_ExposeCartesianJogControls() + { + using var client = factory.CreateClient(); + + using var htmlResponse = await client.GetAsync("/status.html"); + Assert.Equal(HttpStatusCode.OK, htmlResponse.StatusCode); + + var html = await htmlResponse.Content.ReadAsStringAsync(); + Assert.Contains("直角坐标点动", html, StringComparison.Ordinal); + Assert.Contains("XYZ 平移摇杆", html, StringComparison.Ordinal); + Assert.Contains("WPR 姿态摇杆", html, StringComparison.Ordinal); + Assert.Contains("id=\"linear-step\"", html, StringComparison.Ordinal); + Assert.Contains("id=\"angular-step\"", html, StringComparison.Ordinal); + Assert.Contains("max=\"20\"", html, StringComparison.Ordinal); + Assert.Contains("请求日志", html, StringComparison.Ordinal); + Assert.Contains("id=\"request-log\"", html, StringComparison.Ordinal); + Assert.Contains("id=\"clear-log\"", html, StringComparison.Ordinal); + + foreach (var (axis, negative, positive) in new[] + { + ("x", "X-", "X+"), + ("y", "Y-", "Y+"), + ("z", "Z-", "Z+"), + ("w", "W-", "W+"), + ("p", "P-", "P+"), + ("r", "R-", "R+") + }) + { + Assert.Contains($"data-axis=\"{axis}\"", html, StringComparison.Ordinal); + Assert.Contains($"data-axis=\"{axis}\" data-direction=\"1\"", html, StringComparison.Ordinal); + Assert.Contains($"data-axis=\"{axis}\" data-direction=\"-1\"", html, StringComparison.Ordinal); + Assert.Contains(negative, html, StringComparison.Ordinal); + Assert.Contains(positive, html, StringComparison.Ordinal); + } + + using var scriptResponse = await client.GetAsync("/assets/status.js"); + Assert.Equal(HttpStatusCode.OK, scriptResponse.StatusCode); + + var script = await scriptResponse.Content.ReadAsStringAsync(); + Assert.Contains("/api/status/snapshot", script, StringComparison.Ordinal); + Assert.Contains("/move_pose/", script, StringComparison.Ordinal); + Assert.Contains("cartesianPose", script, StringComparison.Ordinal); + Assert.Contains("pointerdown", script, StringComparison.Ordinal); + Assert.Contains("pointerup", script, StringComparison.Ordinal); + Assert.Contains("pointerleave", script, StringComparison.Ordinal); + Assert.Contains("blur", script, StringComparison.Ordinal); + Assert.Contains("request-log", script, StringComparison.Ordinal); + Assert.Contains("clear-log", script, StringComparison.Ordinal); + } + + /// + /// 验证状态快照 API 会返回运行时连接、使能、速度和机器人元数据。 + /// + [Fact] + public async Task GetStatusSnapshot_ReturnsRuntimeStateAfterLegacyInitialization() + { + using var client = factory.CreateClient(); + await InitializeRobotAsync(client); + + using (var speedResponse = await client.PostAsJsonAsync("/set_speedRatio/", new { speed = 0.75 })) + { + Assert.Equal(HttpStatusCode.OK, speedResponse.StatusCode); + } + + using var response = await client.GetAsync("/api/status/snapshot"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + await using var responseStream = await response.Content.ReadAsStreamAsync(); + using var json = await JsonDocument.ParseAsync(responseStream); + var root = json.RootElement; + var snapshot = root.GetProperty("snapshot"); + + Assert.Equal("ok", root.GetProperty("status").GetString()); + Assert.True(root.GetProperty("isSetup").GetBoolean()); + Assert.Equal("FANUC_LR_Mate_200iD", root.GetProperty("robotName").GetString()); + Assert.Equal(6, root.GetProperty("degreesOfFreedom").GetInt32()); + var uploadedTrajectories = root.GetProperty("uploadedTrajectories") + .EnumerateArray() + .Select(static value => value.GetString()) + .ToArray(); + Assert.Contains("20251015", uploadedTrajectories); + Assert.Contains("UTTC_MS11", uploadedTrajectories); + Assert.Equal("Connected", snapshot.GetProperty("connectionState").GetString()); + Assert.True(snapshot.GetProperty("isEnabled").GetBoolean()); + Assert.False(snapshot.GetProperty("isInMotion").GetBoolean()); + Assert.Equal(0.75, snapshot.GetProperty("speedRatio").GetDouble(), precision: 6); + Assert.Equal(6, snapshot.GetProperty("jointPositions").GetArrayLength()); + } + + /// + /// 初始化旧 HTTP 兼容链路,使状态页可以读取一个完整的已连接状态。 + /// + /// 测试 HTTP 客户端。 + private static async Task InitializeRobotAsync(HttpClient client) + { + using (var setupResponse = await client.PostAsync("/setup_robot/?robot_name=FANUC_LR_Mate_200iD", content: null)) + { + Assert.Equal(HttpStatusCode.OK, setupResponse.StatusCode); + } + + using (var activeControllerResponse = await client.PostAsync("/set_active_controller/?sim=true", content: null)) + { + Assert.Equal(HttpStatusCode.OK, activeControllerResponse.StatusCode); + } + + using (var connectRobotResponse = await client.PostAsync("/connect_robot/?ip=192.168.10.101", content: null)) + { + Assert.Equal(HttpStatusCode.OK, connectRobotResponse.StatusCode); + } + + using (var enableRobotResponse = await client.GetAsync("/enable_robot/")) + { + Assert.Equal(HttpStatusCode.OK, enableRobotResponse.StatusCode); + } + } +}