Compare commits
10 Commits
3a26aea082
...
c3d9fb3708
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3d9fb3708 | ||
|
|
9db5cf4504 | ||
|
|
4083df04fb | ||
|
|
d95973ab68 | ||
|
|
253f4b3cf7 | ||
|
|
cdb90c3e9a | ||
|
|
aabfe31526 | ||
|
|
3c4b6e6d54 | ||
|
|
f0163d8d70 | ||
|
|
8c1f519526 |
5
.cargo/config.toml
Normal file
5
.cargo/config.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
[target.x86_64-unknown-linux-musl]
|
||||||
|
linker = "rust-lld"
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-musl]
|
||||||
|
linker = "rust-lld"
|
||||||
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -382,6 +382,7 @@ dependencies = [
|
||||||
"ethercrab-wire",
|
"ethercrab-wire",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"heapless",
|
"heapless",
|
||||||
|
"io-uring",
|
||||||
"libc",
|
"libc",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -508,6 +509,17 @@ version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-uring"
|
||||||
|
version = "0.7.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d64d8ca234d152948ceaede1f419b6a83983a5ecccaac05fb337a809c96d3aa6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnetwork"
|
name = "ipnetwork"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
|
||||||
description = "J1900 <-> TwinCAT 1 kHz communication verification over an EL6695 EtherCAT bridge"
|
description = "J1900 <-> TwinCAT 1 kHz communication verification over an EL6695 EtherCAT bridge"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethercrab = { version = "0.7", default-features = false, features = ["std"] }
|
ethercrab = { version = "0.7", default-features = false, features = ["std", "io-uring"] }
|
||||||
futures-lite = "2"
|
futures-lite = "2"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
crc32fast = "1.4"
|
crc32fast = "1.4"
|
||||||
|
|
|
||||||
BIN
deploy_800b_dc.log
Normal file
BIN
deploy_800b_dc.log
Normal file
Binary file not shown.
BIN
deploy_800b_dc2.log
Normal file
BIN
deploy_800b_dc2.log
Normal file
Binary file not shown.
BIN
deploy_800b_elevated.log
Normal file
BIN
deploy_800b_elevated.log
Normal file
Binary file not shown.
BIN
deploy_800b_regress.log
Normal file
BIN
deploy_800b_regress.log
Normal file
Binary file not shown.
BIN
deploy_800b_regress2.log
Normal file
BIN
deploy_800b_regress2.log
Normal file
Binary file not shown.
BIN
deploy_800b_regress3.log
Normal file
BIN
deploy_800b_regress3.log
Normal file
Binary file not shown.
124
docs/800b-pdo-plan.md
Normal file
124
docs/800b-pdo-plan.md
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
# EL6695 PDO 扩展至 800 字节 — 方案文档
|
||||||
|
|
||||||
|
> **版本**: 1.0
|
||||||
|
> **日期**: 2026-07-27
|
||||||
|
> **状态**: 待评审
|
||||||
|
> **前置文档**: `8b-bridge-design.md`(8B 实现)、`500us-latency-plan.md`(延迟优化)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 目标
|
||||||
|
|
||||||
|
在现有 8B 工程基础上,新建 **800 字节 PDO** 变体工程 `TwinCAT_EL6695_Primary_800B`,实现 TwinCAT ↔ J1900 双向 800B/周期数据交换,并保持逐字节可验证性。
|
||||||
|
|
||||||
|
选型说明:800B 介于 8B(验证通路)与 1024B(ESI 默认上限)之间,可作为实际应用的带宽档位;同时验证任意尺寸的映射 blob 生成规则。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 关键技术点(复用既有经验)
|
||||||
|
|
||||||
|
| # | 经验 | 应用到 800B |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | 链接必须整数组(`LinkVariables`) | 同 8B,PLC 数组与 PDO 条目同类型同尺寸 |
|
||||||
|
| 2 | 类型 GUID 必须复用 PLC 构建生成的随机 GUID | 先建 PLC `ARRAY[0..799]`,构建取 GUID 填 PDO 条目 |
|
||||||
|
| 3 | SM3 不挂 0x1A01 诊断 PDO,否则设备报 `size not allowed` | SM3 纯 800B,不含诊断(若含诊断则需 802B) |
|
||||||
|
| 4 | EL6695 固件按 PDO 赋值总和校验 SM 长度 | SM2=SM3=800(0x0320),两侧一致 |
|
||||||
|
| 5 | 映射 blob 须为 TwinCAT 行格式(240bit 条目 + 余量) | 见 §3 数学 |
|
||||||
|
| 6 | 先激活 TwinCAT,再启动 J1900 ticker(同一会话) | 部署顺序不变 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. PDO 映射 blob 数学(800B)
|
||||||
|
|
||||||
|
EL6695 固件要求映射为 TwinCAT 行格式:真实对象条目 @240bit + 若干 0x0000:00 延续条目,总位数 = payload 位数。
|
||||||
|
|
||||||
|
```
|
||||||
|
800 B = 6400 bit = 26 × 240 bit + 160 bit
|
||||||
|
blob = [条目数=27 (u16 LE)]
|
||||||
|
+ [240, 1, obj_lo, obj_hi] (真实对象 0x6000/0x7000 子索引1)
|
||||||
|
+ 25 × [240, 0, 0, 0] (延续)
|
||||||
|
+ 1 × [160, 0, 0, 0] (余量延续)
|
||||||
|
```
|
||||||
|
|
||||||
|
`bridge.rs` 的 `twin_layout_blob_sized` 需泛化:`payload_bytes ≤ 30` 走单条目;否则按 `ceil` 生成多条目(1 真实 + (n-2) 延续 + 1 余量延续),1024B 时退化为现有 35 条目格式(字节级兼容已验证)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 工程结构
|
||||||
|
|
||||||
|
```
|
||||||
|
twincat3-auto-cli/
|
||||||
|
├─ TwinCAT_EL6695_Primary\ (8B, 不动)
|
||||||
|
├─ TwinCAT_EL6695_Primary_2kHz\ (2kHz, 不动)
|
||||||
|
├─ TwinCAT_EL6695_Primary_4kHz\ (4kHz, 不动)
|
||||||
|
└─ TwinCAT_EL6695_Primary_800B\ (新建, 本方案)
|
||||||
|
```
|
||||||
|
|
||||||
|
J1900 二进制: `/root/gateway/el6695_rt_800b`(新构建,旧的全部保留)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 实施步骤
|
||||||
|
|
||||||
|
### 5.1 TwinCAT 侧(副本工程)
|
||||||
|
|
||||||
|
| 步骤 | 内容 |
|
||||||
|
|---|---|
|
||||||
|
| 1 | robocopy 复制 `TwinCAT_EL6695_Primary` → `_800B`;修 sln 为相对路径(防绝对路径 bug) |
|
||||||
|
| 2 | PLC: `GVL_Sync.aIn1/aOut1` → `ARRAY[0..799] OF BYTE`;MAIN 协议改 800B 布局(§6) |
|
||||||
|
| 3 | 构建 PLC → 从 tsproj 取 `ARRAY [0..799]` 的随机 GUID |
|
||||||
|
| 4 | tsproj PDO 条目(0x1608/0x1A08)类型替换为该 GUID 的 `ARRAY [0..799] OF BYTE` |
|
||||||
|
| 5 | SM2 记录: `001c200364...`(len=0x0320=800);SM3 记录: `008e200320...`(len=800),0x1A01 仍不挂 SM3 |
|
||||||
|
| 6 | `tc-relink.ps1` 整数组重建链接;部署激活;验证 _Boot tszip 中 SM2/SM3=800 |
|
||||||
|
|
||||||
|
### 5.2 J1900 侧
|
||||||
|
|
||||||
|
| 步骤 | 内容 |
|
||||||
|
|---|---|
|
||||||
|
| 1 | `twin_layout_blob_sized` 泛化为多条目(§3) |
|
||||||
|
| 2 | 常量: `PAYLOAD_LEN=SM_TX_LEN=SM_RX_LEN=SM_RX_PLD_LEN=800` |
|
||||||
|
| 3 | SM 长度写 800;FMMU len 写 800 |
|
||||||
|
| 4 | ticker 走 1KB 式协议路径(f(x)=x·7+3 + 图案回显 + 时间戳字段),现有编译期守卫 `PAYLOAD_LEN>=256/280` 自动生效 |
|
||||||
|
| 5 | 交叉编译(rust-lld)→ scp 为 `el6695_rt_800b` |
|
||||||
|
|
||||||
|
### 5.3 数据协议(800B 布局)
|
||||||
|
|
||||||
|
| 偏移 | 内容 | 方向 |
|
||||||
|
|---|---|---|
|
||||||
|
| [0..7] | TwinCAT `nCycles`(ULINT) → J1900 回 `nCycles*7+3` | 双向 |
|
||||||
|
| [8..15] | J1900 本地周期号 | J→T |
|
||||||
|
| [16..799] | 逐字节图案 `(nCycles + i) mod 256`,J1900 原样回显 | 双向 |
|
||||||
|
| [264..267] | J1900 周期抖动 ns | J→T |
|
||||||
|
| [268..271] | J1900 图案失配计数 | J→T |
|
||||||
|
| [272..279] | J1900 回存的原始 tc_seq | J→T |
|
||||||
|
|
||||||
|
TwinCAT 校验(复用 1KB 逻辑):从 `nProcessedSeq` 反推原始 seq → 按 seq 重建预期图案逐字节比对 [16..799] → `bSync`;RTT = nCycles − nReturnedSeq。
|
||||||
|
|
||||||
|
### 5.4 验证
|
||||||
|
|
||||||
|
1. J1900 日志:SM2/SM3 len=800、OP、stale=0%、mism=0;
|
||||||
|
2. TwinCAT ADS: `bSync=True`、`aIn1` 非零字节数 ≈ 784(图案区)、RTT 稳定;
|
||||||
|
3. 逐字节证据:抽 [16..799] 中多个偏移核对 `(seq+i) mod 256`;
|
||||||
|
4. 连续 10 分钟:nMismatch 零增长、tx/rx 零错误;
|
||||||
|
5. 频率:保持 1kHz(带宽目标,不引入 2k/4k 变量)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 风险与注意点
|
||||||
|
|
||||||
|
| # | 风险 | 缓解 |
|
||||||
|
|---|---|---|
|
||||||
|
| R1 | 多条目 blob 被固件拒绝(余量条目不认) | 先用 verify 模式观察 AL 状态码;备选:全部按 240bit 进位到 27×240(6480bit,多 80bit 填 0) |
|
||||||
|
| R2 | 800B 时 SM 长度设备校验失败 | 事件日志看 `min/max size`;按报告值对齐 |
|
||||||
|
| R3 | PLC 构建后 tsproj 类型 GUID 抓取失败 | 构建后 rg 校验 GUID 存在再替换 |
|
||||||
|
| R4 | 1kHz 下 800B 帧对 E-bus 其他端子无影响 | 既有端子已在 1kHz 验证过,800B 增量小 |
|
||||||
|
|
||||||
|
## 7. 工时估计
|
||||||
|
|
||||||
|
约 2–3 小时(含一次失败重试余量):tsproj 手术 30min、PLC 30min、J1900 blob 泛化+构建 40min、联调验证 40min。
|
||||||
|
|
||||||
|
## 8. 修订历史
|
||||||
|
|
||||||
|
| 日期 | 内容 |
|
||||||
|
|---|---|
|
||||||
|
| 2026-07-27 | 初版方案 |
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# EL6695 桥接 8 字节双向实时通讯 — 设计文档
|
# EL6695 桥接 8 字节双向实时通讯 — 设计文档
|
||||||
|
|
||||||
> **版本**: 1.0
|
> **版本**: 1.1(新增 §8 800B 变体)
|
||||||
> **日期**: 2026-07-27
|
> **日期**: 2026-07-27
|
||||||
> **状态**: 已实现并验证通过
|
> **状态**: 已实现并验证通过
|
||||||
> **前置文档**: `1kb-verification-progress.md`(1KB 版本演进史)
|
> **前置文档**: `1kb-verification-progress.md`(1KB 版本演进史)
|
||||||
|
|
@ -224,3 +224,65 @@ scp -J tonycao@192.168.58.8 target/x86_64-unknown-linux-musl/release/el6695-rt `
|
||||||
1. **无诊断 PDO**: 8B 配置不含 "External device not connected" 等状态位。若需要,须在 SM3 同时赋值 0x1A01+0x1A08 并保持 SM3=10(TwinCAT 侧重算逻辑需相应修正)。
|
1. **无诊断 PDO**: 8B 配置不含 "External device not connected" 等状态位。若需要,须在 SM3 同时赋值 0x1A01+0x1A08 并保持 SM3=10(TwinCAT 侧重算逻辑需相应修正)。
|
||||||
2. **延迟**: 往返固定 4 个 PLC 周期(≈4ms @1kHz),未做 DC 同步优化。
|
2. **延迟**: 往返固定 4 个 PLC 周期(≈4ms @1kHz),未做 DC 同步优化。
|
||||||
3. **尺寸扩展**: 本设计的修改方法(GUID 类型 + SM 记录 + J1900 常量)适用于任意 ≤240bit 的单条目尺寸;更大尺寸需在 `twin_layout_blob_sized` 中恢复多条目延续格式。
|
3. **尺寸扩展**: 本设计的修改方法(GUID 类型 + SM 记录 + J1900 常量)适用于任意 ≤240bit 的单条目尺寸;更大尺寸需在 `twin_layout_blob_sized` 中恢复多条目延续格式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 800B 变体(已实现并验证)
|
||||||
|
|
||||||
|
在 8B 验证通路基础上,扩展出 800 字节 PDO 工程 `TwinCAT_EL6695_Primary_800B`,作为实际应用的带宽档位,并验证了任意尺寸的映射 blob 生成规则。
|
||||||
|
|
||||||
|
### 8.1 与 8B 设计的差异
|
||||||
|
|
||||||
|
| 项 | 8B | 800B |
|
||||||
|
|---|---|---|
|
||||||
|
| PDO 条目类型 | `ARRAY [0..7] OF BYTE` | `ARRAY [0..799] OF BYTE`(GUID `{7D10FDFE-13E3-A012-D822-BE30F7D36D23}`,PLC 构建生成) |
|
||||||
|
| SM2/SM3 长度 | 8 / 8 | **800 / 800**(0x0320) |
|
||||||
|
| 0x1A01 诊断 PDO | 不挂 SM3 | 同左(沿用,否则设备报 `size not allowed`) |
|
||||||
|
| 映射 blob | 单条目 64bit | **27 条目**(见 §8.2) |
|
||||||
|
| 数据协议 | 每字节 XOR 0x5A + 历史环 | seq + f(x)=x·7+3 + 逐字节图案 + 反推校验(类 1KB) |
|
||||||
|
| 周期 | 1kHz(基线) | 1kHz |
|
||||||
|
|
||||||
|
### 8.2 多条目映射 blob 生成规则
|
||||||
|
|
||||||
|
EL6695 固件接受 TwinCAT 行格式:1 真实对象条目 @240bit + N 延续条目(0x0000:00)@240bit + 1 余量延续条目。
|
||||||
|
|
||||||
|
```
|
||||||
|
800 B = 6400 bit = 26 × 240 bit + 160 bit
|
||||||
|
blob = [27 (u16 LE)]
|
||||||
|
+ [240, 1, obj_lo, obj_hi] 真实对象 (0x6000:01 / 0x7000:01)
|
||||||
|
+ 25 × [240, 0, 0, 0] 延续
|
||||||
|
+ 1 × [160, 0, 0, 0] 余量延续
|
||||||
|
```
|
||||||
|
|
||||||
|
`twin_layout_blob_sized(obj, n)` 泛化实现:≤240bit 单条目;>240bit 按上式多条目;1024B 特判保持原 35 条目格式(字节级兼容)。
|
||||||
|
|
||||||
|
### 8.3 800B 数据布局
|
||||||
|
|
||||||
|
| 偏移 | 内容 | 方向 |
|
||||||
|
|---|---|---|
|
||||||
|
| [0..7] | TwinCAT `nCycles` → J1900 回 `nCycles*7+3` | 双向 |
|
||||||
|
| [8..15] | J1900 本地周期号 | J→T |
|
||||||
|
| [16..263] | 逐字节图案 `(nCycles+i) mod 256`,J1900 原样回显 | 双向 |
|
||||||
|
| [264..267] | J1900 周期抖动 ns | J→T |
|
||||||
|
| [268..271] | J1900 图案失配计数 | J→T |
|
||||||
|
| [272..279] | J1900 回存的原始 tc_seq | J→T |
|
||||||
|
| [280..799] | 逐字节图案(同上) | 双向 |
|
||||||
|
|
||||||
|
**设计要点:图案区必须避开 [264..279] 统计字段**——首版实现按 [16..799] 连续图案,J1900 统计字段覆盖 16 字节导致 PLC 每周期 16 字节失配。
|
||||||
|
|
||||||
|
### 8.4 新增陷阱(800B 实测)
|
||||||
|
|
||||||
|
| # | 现象 | 根因 | 解决 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 8 | 激活时 EL6695 SAFEOP 失败,AL 状态码 0x0003 | **激活与 J1900 ticker 会话并发**:relink 激活时 ticker 正在跑,桥两侧主站会话冲突 | 激活必须在 J1900 空闲时进行;失败后空载重新激活即恢复 |
|
||||||
|
| 9 | 数据通但 PLC bSync=False,每周期固定 16 字节失配 | 图案区 [16..799] 与 J1900 统计字段 [264..279] 重叠 | 图案区改为 [16..263]+[280..799](见 §8.3) |
|
||||||
|
| 10 | 手写 `<DcData>` 十六进制被 TwinCAT 归一化为全零 | DC 模式选择不接受 legacy hex 直写 | **用 XSD 结构化形式 `<Dc><Data AssignActivate="#x320"/></Dc>`**,TwinCAT 读取后自动生成正确 DcData(`2003...`,即 0x0320)并启用 DC-Synchron(2026-08-09 已验证) |
|
||||||
|
|
||||||
|
### 8.5 800B 实测性能
|
||||||
|
|
||||||
|
| 指标 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| J1900 exec(800B,同步写) | 均值 384µs / p99 400µs |
|
||||||
|
| FPRD(读 800B) | 均值 139µs / p99 156µs(8B 时为 71µs) |
|
||||||
|
| FPWR(写 800B) | 均值 244µs / p99 260µs(与 8B 基本持平) |
|
||||||
|
| 桥接 RTT(1kHz) | 4 周期(lag=4,同 8B/1KB 结构) |
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# EL6695 8B 双向通讯 — 测试文档
|
# EL6695 8B 双向通讯 — 测试文档
|
||||||
|
|
||||||
> **版本**: 1.0
|
> **版本**: 1.1(新增 §8 800B 测试)
|
||||||
> **日期**: 2026-07-27
|
> **日期**: 2026-07-27
|
||||||
> **结论**: ✅ 全部通过
|
> **结论**: ✅ 全部通过
|
||||||
> **配套文档**: `8b-bridge-design.md`、`8b-verification-methodology.md`
|
> **配套文档**: `8b-bridge-design.md`、`8b-verification-methodology.md`
|
||||||
|
|
@ -172,3 +172,60 @@ Copy-Item "C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary\TwinCA
|
||||||
| 日期 | 内容 |
|
| 日期 | 内容 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 2026-07-27 | 初版:8B 双向交换全部测试通过 |
|
| 2026-07-27 | 初版:8B 双向交换全部测试通过 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 800B 变体测试(2026-07-28)
|
||||||
|
|
||||||
|
### 8.1 环境差异
|
||||||
|
|
||||||
|
| 项 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| TwinCAT 工程 | `TwinCAT_EL6695_Primary_800B`(1kHz 任务) |
|
||||||
|
| PDO 尺寸 | 800B(SM2/SM3 = 0x0320) |
|
||||||
|
| J1900 二进制 | `/root/gateway/el6695_rt_800b` |
|
||||||
|
| 校验脚本 | `scripts/tc-ads-verify-800b.ps1`、`tc-ads-mismatch-delta.ps1`、`tc-relink-800b.ps1`、`tc-deploy-retry-800b.ps1` |
|
||||||
|
|
||||||
|
### 8.2 配置确认(激活后 _Boot tszip)
|
||||||
|
|
||||||
|
```
|
||||||
|
SM2: 001c200364... (start=0x1C00 len=0x0320=800 ✓ 保留)
|
||||||
|
SM3: 008e200320... (start=0x8E00 len=0x0320=800 ✓ 保留)
|
||||||
|
PDO 条目: ARRAY [0..799] OF BYTE (GUID {7D10FDFE-13E3-A012-D822-BE30F7D36D23})
|
||||||
|
事件日志: 无 EL6695 错误
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 功能验证
|
||||||
|
|
||||||
|
| 项 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| J1900 映射 blob | 27 条目(26×240+160bit)被固件接受,SAFEOP/OP 通过 |
|
||||||
|
| 处理证明 | `proc = raw*7+3` 逐样本精确(如 107636 → 753455) |
|
||||||
|
| PLC 校验 | bSync=True,lag=4 |
|
||||||
|
| 图案覆盖 | aIn1 非零 775–776/800;[16..263]+[280..799] 逐字节 `(seq+i)%256` 抽查全对 |
|
||||||
|
|
||||||
|
### 8.4 10 分钟稳定性(600,000 周期)
|
||||||
|
|
||||||
|
| 指标 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| 周期完整 | 600000 周期 / 600.0s,零丢失 |
|
||||||
|
| 传输错误 | tx_err=0, rx_err=0 |
|
||||||
|
| J1900 图案失配 | mism=0 |
|
||||||
|
| PLC nMismatch | 295s 运行窗口零增长;90s 确认测试 delta=0 |
|
||||||
|
| stale | 2999 周期(0.5%) |
|
||||||
|
| 周期抖动 | 均值 4.89µs / p99 5.4µs(单次 499µs 离群) |
|
||||||
|
| exec | 均值 384µs / p99 400µs |
|
||||||
|
| FPRD / FPWR | 139µs / 244µs |
|
||||||
|
|
||||||
|
注:起止时刻的 nMismatch 增长均为**对端缺席期**的预期累积(ticker 未启动/已停止),运行窗口内零失配。
|
||||||
|
|
||||||
|
### 8.5 800B 特有故障记录
|
||||||
|
|
||||||
|
| # | 故障 | 根因 | 处置 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| F1 | relink 激活时 EL6695 SAFEOP 失败(AL 0x0003) | 激活与运行中的 ticker 会话并发冲突 | J1900 空闲后重新激活即恢复;流程约定:**先激活 TwinCAT,后启动 ticker,激活期间 J1900 必须空闲** |
|
||||||
|
| F2 | 数据通但每周期固定 16 字节失配 | 图案区 [16..799] 覆盖 J1900 统计字段 [264..279] | 图案区改为 [16..263]+[280..799](MAIN 与校验循环同步修改) |
|
||||||
|
|
||||||
|
### 8.6 回退
|
||||||
|
|
||||||
|
800B 工程为独立副本,不影响 8B/1kHz 主工程;切回:重新激活 `TwinCAT_EL6695_Primary`(`tc-deploy-retry.ps1`),J1900 使用 `el6695_rt_8b_xor`。
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# EL6695 8B 双向通讯 — 验证说明文档
|
# EL6695 8B 双向通讯 — 验证说明文档
|
||||||
|
|
||||||
> **版本**: 1.0
|
> **版本**: 1.1(新增 §7 800B 验证)
|
||||||
> **日期**: 2026-07-27
|
> **日期**: 2026-07-27
|
||||||
> **配套文档**: `8b-bridge-design.md`(设计)、`8b-test-report.md`(测试记录)
|
> **配套文档**: `8b-bridge-design.md`(设计)、`8b-test-report.md`(测试记录)
|
||||||
|
|
||||||
|
|
@ -151,3 +151,47 @@ aIn1^5A = 98-B8-D8-F8-18-38-58-78 ← 8/8 全等
|
||||||
1. 验证图案为周期性确定图案,未做随机负载压力测试;
|
1. 验证图案为周期性确定图案,未做随机负载压力测试;
|
||||||
2. `lag=4` 为当前拓扑/周期下的实测值,未验证不同任务周期下的行为;
|
2. `lag=4` 为当前拓扑/周期下的实测值,未验证不同任务周期下的行为;
|
||||||
3. 8B 配置不含诊断 PDO,"对端是否在线"需以数据流本身判断(失配即掉线)。
|
3. 8B 配置不含诊断 PDO,"对端是否在线"需以数据流本身判断(失配即掉线)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 800B 变体的验证方法
|
||||||
|
|
||||||
|
800B 工程采用**序列号 + 图案 + 反推校验**协议(类 1KB),与 8B 的 XOR 历史环不同,原因是 800B 要同时验证"大载荷每个字节"与"序号处理"两个维度。
|
||||||
|
|
||||||
|
### 7.1 协议与验证链
|
||||||
|
|
||||||
|
```
|
||||||
|
TwinCAT 发送: aOut1[0..7] = nCycles; aOut1[i] = (nCycles+i) mod 256
|
||||||
|
(i ∈ [16..263] ∪ [280..799], 避开 [264..279] 统计字段)
|
||||||
|
J1900 处理: tx[0..7] = rx_seq*7+3; tx[8..15] = 本地周期号;
|
||||||
|
tx[264..279] = 抖动/失配/原始 seq; 其余字节原样回显
|
||||||
|
TwinCAT 校验: nProcessedSeq = aIn1[0..7]
|
||||||
|
nReturnedSeq = (nProcessedSeq - 3) / 7 (MOD 7 = 0 才有效)
|
||||||
|
逐字节比对 aIn1[i] == (nReturnedSeq + i) mod 256
|
||||||
|
```
|
||||||
|
|
||||||
|
- **反推校验**:J1900 的 f(x)=x·7+3 可逆,PLC 从回传值反推原始发送周期,无需预设管线延迟(实测 lag=4);
|
||||||
|
- **J1900 侧自检**:ticker 对图案区 [16..255] 做 `(tc_seq+i) mod 256` 独立校验(`mism` 计数),与 PLC 侧校验互为对侧证据。
|
||||||
|
|
||||||
|
### 7.2 与 8B 方法的取舍
|
||||||
|
|
||||||
|
| 维度 | 8B(XOR 历史环) | 800B(seq+图案反推) |
|
||||||
|
|---|---|---|
|
||||||
|
| 每字节独立性 | 强(每字节独立 XOR) | 中(图案含周期序号,错位立现) |
|
||||||
|
| 延迟测量 | 直接(nMatchLag) | 直接(nCycles − nReturnedSeq) |
|
||||||
|
| 大载荷成本 | 历史环 16×8B 比对/周期 | 768 字节比对/周期 |
|
||||||
|
| 适合 | 小载荷通路证明 | 应用级带宽验证 |
|
||||||
|
|
||||||
|
### 7.3 ADS 快照判读(800B 特有)
|
||||||
|
|
||||||
|
外部 ADS 校验脚本用 `nReturnedSeq` 重建预期图案时,因 `nCycles/nReturnedSeq/aIn1` 分次读取,可能出现 `nReturnedSeq > nCycles`(显示 lag 为负)与全图案"失配"的假象——**以 PLC 内部 1kHz 实时计算的 `bSync` 为唯一权威判据**;ADS 全数组读取仅用于非零覆盖率统计(800B 时应 ≈770+/800)。
|
||||||
|
|
||||||
|
### 7.4 800B 稳定性判据
|
||||||
|
|
||||||
|
| 判据 | 通过条件 | 实测 |
|
||||||
|
|---|---|---|
|
||||||
|
| J1900 周期完整 | 周期数 = 时长 × 频率 | 600000 / 600.0s ✅ |
|
||||||
|
| J1900 `mism` | 0 | ✅ |
|
||||||
|
| PLC `nMismatch` | 运行窗口内零增长 | 295s 窗口 delta=0;90s 确认 delta=0 ✅ |
|
||||||
|
| 传输错误 | tx/rx = 0 | ✅ |
|
||||||
|
| 非零覆盖率 | ≈770/800 | 775–776 ✅ |
|
||||||
|
|
|
||||||
129
docs/d2000-dev-plan.md
Normal file
129
docs/d2000-dev-plan.md
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
# 飞腾 D2000 ↔ EL6695 ↔ TwinCAT 通讯链路 — 开发计划
|
||||||
|
|
||||||
|
> **版本**: 1.0
|
||||||
|
> **日期**: 2026-07-28
|
||||||
|
> **状态**: 待启动
|
||||||
|
> **前置成果**: J1900 链路全部方法论(8B/800B 设计、验证体系、脚本集、Rust 代码库)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 目标与拓扑
|
||||||
|
|
||||||
|
在现有 J1900 链路之外,新增一条**并列**通讯链路:飞腾 D2000(ARM64)经 EL6695#2 与 TwinCAT PC 实时数据交换,复用已验证的配置方法与验证体系。
|
||||||
|
|
||||||
|
```
|
||||||
|
TwinCAT PC (169.254.176.217)
|
||||||
|
│
|
||||||
|
├─ [现有链路] EK1100 → ... → EL6695#1 → J1900 (x86_64, Debian13+RT) ✅ 已验收
|
||||||
|
│
|
||||||
|
└─ [新增链路] E-bus 扩展或独立网口 → EL6695#2 → 飞腾 D2000 (aarch64) 🆕
|
||||||
|
▲
|
||||||
|
跳板机#2 网口 (SSH 管理通道)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 待确认事项(启动前)
|
||||||
|
|
||||||
|
| # | 问题 | 影响 |
|
||||||
|
|---|---|---|
|
||||||
|
| Q1 | EL6695#2 挂在哪里?(现有 E-bus 末端 EL9011 前扩展,还是 TwinCAT 第二网口独立段?) | TwinCAT 工程拓扑/网卡规划 |
|
||||||
|
| Q2 | D2000 的 OS 与内核(麒麟 V10? UOS? Ubuntu? 内核版本? 是否有 PREEMPT_RT?) | RT 性能预期、移植工作量 |
|
||||||
|
| Q3 | D2000 管理通道:跳板机#2 的地址、SSH 凭据、能否免密 | 自动化部署方式 |
|
||||||
|
| Q4 | D2000 接 EL6695 的网口(板载 GbE? 独立网卡? 驱动?) | ethercrab 裸 socket 可用性 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 阶段计划
|
||||||
|
|
||||||
|
### 阶段 1:环境与可达性(0.5 天)
|
||||||
|
|
||||||
|
| # | 任务 | 产出 |
|
||||||
|
|---|---|---|
|
||||||
|
| 1.1 | 经跳板机#2 SSH 到 D2000,采集:`uname -a`、`/etc/os-release`、`/proc/version`、CPU/核数、网口列表(`ip link`)、中断分布(`/proc/interrupts`) | 环境档案 |
|
||||||
|
| 1.2 | 确认 PREEMPT_RT(`uname -v` 含 rt?)、`chrt`/`mlockall` 可用性 | RT 基线判定 |
|
||||||
|
| 1.3 | 网络打通:D2000 → EL6695#2 网线;D2000 → 跳板机 管理网 | 拓扑照片/记录 |
|
||||||
|
| 1.4 | 空口探测:tcpdump/裸 socket 读 ethX,确认能收发二层帧 | 网卡可用性 |
|
||||||
|
|
||||||
|
### 阶段 2:Rust 交叉编译 aarch64(0.5 天)
|
||||||
|
|
||||||
|
| # | 任务 | 备注 |
|
||||||
|
|---|---|---|
|
||||||
|
| 2.1 | `rustup target add aarch64-unknown-linux-musl`;`cargo build --target aarch64-unknown-linux-musl --release` | ethercrab 为纯 Rust,理论上零改动;libc 裸 socket(AF_PACKET)在 aarch64 内核同样支持 |
|
||||||
|
| 2.2 | 链接器:沿用 `RUSTFLAGS="-C linker=rust-lld"`(musl 静态,免去 D2000 上 glibc 版本问题) | musl 静态二进制通吃 Kylin/UOS/Debian |
|
||||||
|
| 2.3 | 部署 scp → D2000,跑 `--mode probe`(只读扫描) | 首次上机验证 |
|
||||||
|
| 2.4 | 若 AF_PACKET 在目标内核异常,备选 `aarch64-unknown-linux-gnu` + 检查目标 glibc | 风险预案 |
|
||||||
|
|
||||||
|
### 阶段 3:EL6695 链路开通(0.5–1 天,方法全部现成)
|
||||||
|
|
||||||
|
| # | 任务 | 复用资产 |
|
||||||
|
|---|---|---|
|
||||||
|
| 3.1 | TwinCAT 侧:复制 8B 工程为 `TwinCAT_EL6695_Primary_D2000`(或在 EL6695#2 所在段新建拓扑),激活确认 SM2/SM3 | `tc-deploy-retry.ps1` 模式 |
|
||||||
|
| 3.2 | D2000 侧:`el6695_rt ethX --mode verify` CoE 审计 → ConfigOwner 到 OP | 与 J1900 完全相同的流程 |
|
||||||
|
| 3.3 | **严格遵守顺序:TwinCAT 激活时 D2000 侧空闲;先激活,后启 ticker**(800B 踩坑 F1) | 教训文档化 |
|
||||||
|
| 3.4 | ticker 8B XOR 模式联通,PLC bSync=True | `GVL_Sync` 验证体系 |
|
||||||
|
|
||||||
|
### 阶段 4:数据验证与实时性测量(0.5 天)
|
||||||
|
|
||||||
|
| # | 任务 | 判据 |
|
||||||
|
|---|---|---|
|
||||||
|
| 4.1 | 8B 逐字节验证(图案+XOR+历史环) | bSync=True,lag 稳定 |
|
||||||
|
| 4.2 | 10 分钟稳定性 | 60 万周期零错误,nMismatch 零增长 |
|
||||||
|
| 4.3 | D2000 实时性基线:周期抖动/exec/FPRD/FPWR 统计 | 与 J1900 对照(7µs 抖动基准) |
|
||||||
|
| 4.4 | 如需 800B:换 `el6695_rt_800b` 对应构建(同源码,常量切换) | 800B 工程现成 |
|
||||||
|
|
||||||
|
### 阶段 5:D2000 RT 调优(按需,0.5–1 天)
|
||||||
|
|
||||||
|
| # | 措施 | 场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| 5.1 | IRQ 亲和:管理网口 IRQ 钉到非 RT 核 | 必做 |
|
||||||
|
| 5.2 | 核隔离 `isolcpus` + ticker 绑核(程序已支持 `--core`) | 推荐 |
|
||||||
|
| 5.3 | 若无 RT 补丁且抖动不可接受:评估编译 aarch64 PREEMPT_RT 内核(源码 kernel.org + rt patch,交叉编译) | 兜底 |
|
||||||
|
| 5.4 | 抖动向因分析:hwlatdetect/cyclictest 对照 | 诊断工具 |
|
||||||
|
|
||||||
|
### 阶段 6:文档与归档(0.5 天)
|
||||||
|
|
||||||
|
- 新增 `docs/d2000-bringup.md`(环境档案、移植记录、与 J1900 的性能对照)
|
||||||
|
- 更新三份主文档的适用范围章节;git 提交
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 复用率评估
|
||||||
|
|
||||||
|
| 资产 | 复用程度 |
|
||||||
|
|---|---|
|
||||||
|
| el6695_rt 源码(main.rs/bridge.rs) | ~100%(唯一改动:默认网口名/可能的字节序检查——ARM 与 x86 同小端,无影响) |
|
||||||
|
| TwinCAT 工程与 PLC 验证程序 | 100%(复制副本改拓扑) |
|
||||||
|
| 部署/诊断/监视脚本(20+ 个) | ~90%(J1900 地址替换为 D2000 地址) |
|
||||||
|
| 配置方法论(SM/PDO/blob/链接/激活顺序) | 100% |
|
||||||
|
|
||||||
|
## 4. 风险登记
|
||||||
|
|
||||||
|
| # | 风险 | 概率 | 缓解 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| R1 | D2000 OS 无 PREEMPT_RT,抖动大(几十~几百 µs) | 高 | 阶段 5.3 自编译 RT 内核;或先测后判,1kHz 场景通常可接受 |
|
||||||
|
| R2 | 麒麟/UOS 老内核(4.19)与 ethercrab 兼容性 | 低 | musl 静态;AF_PACKET 自 2.2 内核即有 |
|
||||||
|
| R3 | D2000 网口驱动不支持混杂/裸包或性能差 | 低 | 阶段 1.4 提前探测;备 USB 网卡 |
|
||||||
|
| R4 | 跳板机#2 到 D2000 的 SSH 不稳定 | 中 | 串口兜底;长会话用 nohup+日志(既有模式) |
|
||||||
|
| R5 | 双链路并存时 TwinCAT 两张网卡/两段总线资源冲突 | 低 | Q1 拓扑确认后评审 |
|
||||||
|
|
||||||
|
## 5. 里程碑
|
||||||
|
|
||||||
|
| 里程碑 | 判据 | 状态 |
|
||||||
|
|---|---|---|
|
||||||
|
| M1 | D2000 SSH 通,环境档案完成(Q1–Q4 全部答案) | ⬜ 待硬件接入 |
|
||||||
|
| M2 | aarch64 二进制构建成功 | ✅ 2026-08-06(见下) |
|
||||||
|
| M3 | 链路 OP,8B bSync=True | ⬜ |
|
||||||
|
| M4 | 10 分钟零错误 + 抖动基线报告 | ⬜ |
|
||||||
|
| M5 | 文档归档 + git 提交 | ⬜ |
|
||||||
|
|
||||||
|
### M2 完成记录(2026-08-06)
|
||||||
|
|
||||||
|
- `rustup target add aarch64-unknown-linux-musl` 曾因清华镜像缺该日期组件 404 且 `RUSTUP_DIST_SERVER` 环境变量被缓存清单吞掉;**根因:`~/.rustup/toolchains/<tc>/lib/rustlib/multirust-channel-manifest.toml` 缓存清单内含 tuna 绝对 URL**,替换为 `https://static.rust-lang.org/dist/` 后安装成功。
|
||||||
|
- `cargo build --release --target aarch64-unknown-linux-musl` **零代码改动通过**(el6695-rt + xfc_probe),产物 ELF `e_machine=0xB7 (AArch64)`,musl 静态,约 1MB。
|
||||||
|
- 构建配置固化:`.cargo/config.toml` 已为两个 musl target 指定 `rust-lld`,不再需要 `RUSTFLAGS` 环境变量。
|
||||||
|
- 部署脚本:`scripts/d2000-deploy.ps1`(构建→架构校验→scp→chmod),JumpHost/TargetHost 待 D2000 接入后填实际值。
|
||||||
|
|
||||||
|
## 6. 修订历史
|
||||||
|
|
||||||
|
| 日期 | 内容 |
|
||||||
|
|---|---|
|
||||||
|
| 2026-07-28 | 初版计划 |
|
||||||
69
docs/jitter-analysis.md
Normal file
69
docs/jitter-analysis.md
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# J1900 实时性能偶发最大抖动分析
|
||||||
|
|
||||||
|
> **日期**: 2026-07-28
|
||||||
|
> **对象**: J1900 (Debian 13, Linux 6.6.135-rt74 PREEMPT_RT) 上 el6695_rt ticker 的周期抖动
|
||||||
|
> **数据来源**: 1kHz 8B 600s 测试、800B 10 分钟测试、4kHz 测试、800B 5 小时(1800 万周期)测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 现象
|
||||||
|
|
||||||
|
周期抖动(实际周期相对名义周期的偏差)的**均值极小(5–7µs)**,但偶发数百微秒的最大值:
|
||||||
|
|
||||||
|
| 测试 | 样本数 | jitter 均值 | jitter max |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1kHz 8B, 600s | 600,000 | 7.29µs | **10.0µs** |
|
||||||
|
| 800B, 10 分钟 | 600,000 | 4.89µs | **499.1µs** |
|
||||||
|
| 4kHz, 120s | 352,917 | 90µs(含滑相) | **1549µs** |
|
||||||
|
| **800B, 5 小时** | **18,000,000** | **7.39µs** | **41.8µs** |
|
||||||
|
|
||||||
|
关键观察:尖峰幅度**不是系统固有属性**——5 小时静默运行(1800 万样本)最大仅 41.8µs,而活动密集的 10 分钟测试出现 499µs。尖峰与并发系统活动强相关。
|
||||||
|
|
||||||
|
## 2. 诊断证据
|
||||||
|
|
||||||
|
### 2.1 系统环境(J1900 实测)
|
||||||
|
|
||||||
|
| 项 | 值 | 影响 |
|
||||||
|
|---|---|---|
|
||||||
|
| 根文件系统 | **NFS**:`192.168.68.1:/j1900-node2/rootfs`(TwinCAT PC 做 NFS 服务器) | 一切文件访问走网络 |
|
||||||
|
| /tmp | tmpfs(内存) | ticker 日志不落盘 ✓ |
|
||||||
|
| 设备中断 | eth0-rx 30 万次全在 CPU0;ahci/ttyS0 也在 CPU0/1 | ticker 核 CPU2 无设备 IRQ ✓ |
|
||||||
|
| 调度 | ticker 绑定核 2,SCHED_FIFO 80,mlockall,栈预缺页 | 基础保障已做 |
|
||||||
|
| dmesg | 无热事件、无异常;sshd 栈深度提示(良性) | 排除过热 |
|
||||||
|
|
||||||
|
### 2.2 相关性
|
||||||
|
|
||||||
|
尖峰最频繁的测试(10 分钟、4kHz)期间,Windows 侧正密集进行 TwinCAT 构建/激活与 SSH 会话;5 小时静默期几乎无尖峰。
|
||||||
|
|
||||||
|
## 3. 原因排序
|
||||||
|
|
||||||
|
| # | 原因 | 机理 | 符合度 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | **NFS 根文件系统停顿** | Windows NFS 服务器忙(VS 构建、大文件 I/O)→ J1900 侧触盘进程阻塞 → 网络 softirq、内存回收压力波及 RT 核 | ★★★★ 主因(静默期 max 仅 41.8µs) |
|
||||||
|
| 2 | **SMI 系统管理中断** | J1900(Bay Trail Atom)BIOS 的 USB legacy / 嵌入式控制器 / 热管理 SMI,100–500µs 不可屏蔽、OS 不可见 | ★★★ 次嫌疑(偶发形态吻合,需 hwlatdetect 证实) |
|
||||||
|
| 3 | **深 C 状态退出** | Bay Trail C6 退出延迟 ~100–200µs;周期睡眠偶入深态 | ★★(均值仅 5–7µs,多数时间为浅态) |
|
||||||
|
|
||||||
|
## 4. 验证方法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 区分硬件(SMI) vs 软件(NFS/OS)延迟 — 内核 6.6 自带 rtla:
|
||||||
|
rtla hwlatdetect --cpus 2 --duration 60
|
||||||
|
# 判定: hwlatdetect 报数百微秒 => SMI/硬件; 干净 => NFS/OS 软件因素
|
||||||
|
|
||||||
|
# 辅助: cyclictest 对照(无 EtherCAT 负载时测量裸调度抖动)
|
||||||
|
cyclictest -m -p 80 -i 1000 -l 60000 -a 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 缓解建议(按收益排序)
|
||||||
|
|
||||||
|
| # | 措施 | 预期效果 |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | gateway 目录与日志移出 NFS(tmpfs 或本地存储) | 消除主因 |
|
||||||
|
| 2 | BIOS 关闭 USB Legacy、不需要的 EC 功能 | 消 SMI 尖峰 |
|
||||||
|
| 3 | 内核启动参数 `isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3` | 完全隔离 RT 核 |
|
||||||
|
| 4 | `intel_idle.max_cstate=1` 限制最深 C 状态 | 消 C6 退出尖峰(功耗换延迟) |
|
||||||
|
| 5 | IRQ 亲和性复核(保持设备 IRQ 远离核 2/3) | 已满足,固化 |
|
||||||
|
|
||||||
|
## 6. 结论
|
||||||
|
|
||||||
|
J1900 基础实时性良好(均值 5–7µs,p99 < 10µs,1800 万周期零丢帧)。偶发数百微秒尖峰的主因是 **NFS 根文件系统在 Windows 服务器繁忙时的停顿**;SMI 为次要嫌疑,可用 `rtla hwlatdetect` 一测定论。对当前 EL6695 数据通道无实质影响(尖峰只占 ~0.0001%,且 1kHz 预算 1ms 内 exec 最大 400µs 仍有富余)。
|
||||||
287
docs/rt-link-verification-report.md
Normal file
287
docs/rt-link-verification-report.md
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
# TwinCAT ↔ J1900 实时数据通讯验证报告(EL6695 桥接)
|
||||||
|
|
||||||
|
> **版本**: 1.0
|
||||||
|
> **日期**: 2026-08-12
|
||||||
|
> **结论**: ✅ 全部通过(4kHz 3 小时 soak 零错误;8kHz RTT min 0.75ms / mean 1.04ms)
|
||||||
|
> **关联文档**: `8b-bridge-design.md`、`8b-verification-methodology.md`、`500us-latency-plan.md`、`jitter-analysis.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 摘要
|
||||||
|
|
||||||
|
本报告覆盖 TwinCAT PC 与 J1900 嵌入式平台之间经 EL6695 EtherCAT 桥接的实时数据通讯验证,
|
||||||
|
包括数据完整性、长期稳定性、周期扩展(1kHz→8kHz)、双侧 DC-Synchron、RTT 延迟优化与
|
||||||
|
断电恢复能力。
|
||||||
|
|
||||||
|
**核心结果**:
|
||||||
|
|
||||||
|
| 验证项 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| 数据完整性(800B 载荷,双向交叉校验) | 3 小时 4537 万周期零错误 |
|
||||||
|
| 长期稳定性(4kHz soak 11400s) | bSync=True ×190 采样点,nMismatch delta=0 |
|
||||||
|
| J1900 侧自校验 | 33.6M 帧 payload mismatch=0,tx/rx err=0 |
|
||||||
|
| RTT(8kHz + tx_rx RT 提权) | min 0.75ms / mean 1.04ms / max 1.38ms |
|
||||||
|
| 单向延迟(推导) | 典型 0.3–0.5ms,最坏 ~0.6ms |
|
||||||
|
| 死链路检测 | lag 指标 + bSync 双重敏感(实测 5 秒内可检出) |
|
||||||
|
| DC-Synchron(双侧) | 从站侧固件 sync_mode=2 接受,180s 同步计数器全零 |
|
||||||
|
| 断电恢复 | TwinCAT 自恢复 Run;J1900 重建 /tmp 后 <2 分钟恢复全链路 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 系统架构与测试环境
|
||||||
|
|
||||||
|
### 2.1 硬件拓扑
|
||||||
|
|
||||||
|
```
|
||||||
|
TwinCAT PC (Windows, TwinCAT 3.1.4026.24)
|
||||||
|
└─ EtherCAT 主站网卡 "以太网 2" (TwinCAT-Intel PCI, MAC 00-15-17-8A-B3-DA)
|
||||||
|
└─ EK1100 → EL2202 → EL1252 → EL2262 → EL2008 → EL6851-0010 → EL6022
|
||||||
|
→ EL6695 (桥接端子, 主站侧 X2)
|
||||||
|
└─ EL6695 从站侧 X1 ←直连→ J1900 eth1
|
||||||
|
J1900 (Linux 6.6.135-rt74 PREEMPT_RT, Celeron J1900 4 核)
|
||||||
|
```
|
||||||
|
|
||||||
|
| 项 | 值 |
|
||||||
|
|---|---|
|
||||||
|
| TwinCAT AmsNetId | `169.254.176.217.1.1` |
|
||||||
|
| J1900 管理 IP | `192.168.68.100`(DHCP,曾漂移至 .101,MAC `00:e2:69:34:71:2c`) |
|
||||||
|
| SSH 跳板 | `tonycao@192.168.58.8` |
|
||||||
|
| EL6695 从站侧站址 | `0x1000` |
|
||||||
|
| J1900 内核 | 6.6.135-rt74(PREEMPT_RT,io_uring 可用) |
|
||||||
|
|
||||||
|
### 2.2 软件与工程
|
||||||
|
|
||||||
|
| 项 | 位置 |
|
||||||
|
|---|---|
|
||||||
|
| TwinCAT 工程(800B 变体) | `C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary_800B\` |
|
||||||
|
| J1900 源码 | `C:\Users\tonycao\work\ethercat-linux`(Rust,ethercrab 0.7 本地 patch) |
|
||||||
|
| 构建目标 | `x86_64-unknown-linux-musl`(静态链接,零运行时依赖) |
|
||||||
|
| 关键 commits | `f0163d8`(800B) → `aabfe31`(DC-Synchron) → `cdb90c3`(800B 异步 FPWR) → `253f4b3`(J1900 DC) → `4083df0`(8kHz+RT 提权) |
|
||||||
|
| 配置备份 | tsproj `.bak-1k-dc` / `.bak-4k`;TcTTO `.bak-1k` / `.bak-4k` |
|
||||||
|
|
||||||
|
### 2.3 载荷布局(800B)
|
||||||
|
|
||||||
|
| 偏移 | 内容 | 方向 |
|
||||||
|
|---|---|---|
|
||||||
|
| `[0..7]` | 序列号:PLC 写 `tc_seq=nCycles`;J1900 回写 `f(tc_seq)=tc_seq*7+3` | 双向 |
|
||||||
|
| `[8..15]` | J1900 本地周期计数 | 上行 |
|
||||||
|
| `[16..799]` | 图案区:`(seq + i) mod 256`(J1900 原样回传) | 双向 |
|
||||||
|
| `[264..267]` | J1900 周期抖动 i32(统计区,不参与校验) | 上行 |
|
||||||
|
| `[268..271]` | J1900 payload mismatch 计数 | 上行 |
|
||||||
|
| `[272..279]` | 原始 tc_seq 保留(供 PLC 交叉验证) | 上行 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 验证方法学
|
||||||
|
|
||||||
|
### 3.1 数据完整性证明链
|
||||||
|
|
||||||
|
单向"收到数据"不能证明通讯正确。本方案用**双向交叉校验 + 数学变换**构成闭环证明:
|
||||||
|
|
||||||
|
1. **PLC 侧写入**:`aOut1[i] = (nCycles + i) mod 256`(图案区),`aOut1[0..7] = tc_seq`
|
||||||
|
2. **J1900 侧处理**(证明"读取+计算+写回"三个动作真实发生):
|
||||||
|
- 校验收到图案 `[16..256)` 与 `tc_seq` 一致(不一致计入 `payload_mismatch`)
|
||||||
|
- 对序列号做不可由硬件桥接伪造的数学变换:`f(x) = x*7 + 3`
|
||||||
|
- 回写 `aIn1[0..7] = f(tc_seq)`,图案区原样回传,`[272..279]` 保留原始 tc_seq
|
||||||
|
3. **PLC 侧验证**:
|
||||||
|
- 从 `aIn1[0..7]` 反解 `nReturnedSeq = (x-3)/7`(整除性本身是一道校验)
|
||||||
|
- `bSync := 全部 aIn1[i] == (nReturnedSeq + i) mod 256`(768 字节逐字节)
|
||||||
|
|
||||||
|
**设计要点**:
|
||||||
|
- `f(x)=7x+3` 保证回传数据不可能由桥接端子透传/环回伪造
|
||||||
|
- 用 `nReturnedSeq`(回传帧自带的序号)而非本地 `nCycles` 做图案比对,
|
||||||
|
使校验对传输滞后免疫——校验的是"回传帧内部自洽性"
|
||||||
|
- J1900 侧独立再做一遍正向校验,两侧互为佐证
|
||||||
|
|
||||||
|
### 3.2 新鲜度(liveness)证明
|
||||||
|
|
||||||
|
自洽性校验的盲区:回传通道冻结时数据仍自洽。对策:
|
||||||
|
|
||||||
|
- **`nMatchLag = nCycles - nReturnedSeq`**(PLC 内每周期原子计算):
|
||||||
|
回传帧的产生时刻距当前的周期数。通道健康时稳定在小范围;
|
||||||
|
通道死亡时以任务频率无界增长
|
||||||
|
- **实测死链路响应**:杀掉 J1900 ticker 后 5 秒内 lag 从 6 → 8.1 亿且以 4000/s 增长,
|
||||||
|
bSync 同步翻 False(EL6695 从站侧失联使输入映像清零)
|
||||||
|
- **反向验证**:重启 ticker 后 lag 立即回落至 4–6 周期
|
||||||
|
|
||||||
|
### 3.3 方法学盲区(如实声明)
|
||||||
|
|
||||||
|
| 盲区 | 说明 | 缓解 |
|
||||||
|
|---|---|---|
|
||||||
|
| 桥转发冻结而两侧 ESC 保持 OP | aIn1 冻结时自洽校验仍通过,任何黑盒测试无法检出 | 概率极低;需侵入式手段 |
|
||||||
|
| J1900 侧图案校验仅覆盖 `[16..256)`(240/800 字节) | PLC 侧覆盖 768 字节,互补 | 可扩大 J1900 校验范围 |
|
||||||
|
| J1900 处理帧率低于发送帧率(设计性跳帧) | echo 恒为最新帧(lag 指标证明),无陈旧累积 | 见 5.4 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 指标详解
|
||||||
|
|
||||||
|
### 4.1 PLC 侧(GVL_Sync,ADS 可读)
|
||||||
|
|
||||||
|
| 指标 | 类型 | 定义 | 意义与解读 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `nCycles` | ULINT | PLC 任务周期计数 | 任务存活与速率基准;速率 = Δ/Δt,偏离任务频率即异常 |
|
||||||
|
| `bSync` | BOOL | 当前周期 768 字节图案全部匹配 | **瞬时**正确性。单次 False 可能是瞬态;持续 False = 链路/配置故障 |
|
||||||
|
| `nMismatch` | DINT | 累计失配字节数(仅 diff>0 时累加) | **历史**错误总量。soak 中看 delta=0;注意 int32,错误率 3M/s 时约 11 分钟回绕 |
|
||||||
|
| `nReturnedSeq` | ULINT | 回传帧携带的原始序号(反解后) | 回传链路的"时间戳";与 nCycles 之差即 lag |
|
||||||
|
| `nMatchLag` | DINT | `nCycles - nReturnedSeq`,每周期原子更新 | **新鲜度核心指标**:回传 RTT 的周期数表示。健康值=小范围稳定;无界增长=回传通道死 |
|
||||||
|
|
||||||
|
**ADS 采样注意**:`nCycles` 与 `nReturnedSeq` 分两次读取会产生撕裂(高速任务下可读出负 lag 假象);
|
||||||
|
必须直接读原子计算的 `nMatchLag`。
|
||||||
|
|
||||||
|
### 4.2 J1900 侧(ticker S 行遥测)
|
||||||
|
|
||||||
|
| 字段 | 定义 | 异常含义 |
|
||||||
|
|---|---|---|
|
||||||
|
| `cyc` | J1900 处理循环计数 | 速率 = 实际处理能力上限的度量 |
|
||||||
|
| `raw` / `proc` | 收到的 tc_seq / 回写的 f(tc_seq) | 与 PLC 侧 nCycles 对账,验证双向一致 |
|
||||||
|
| `txe` / `rxe` | FPWR/FPRD 事务错误 | 持续增长 = 链路劣化或 ESC 掉态 |
|
||||||
|
| `late` | 异步 FPWR 响应未在下一周期收齐的次数 | 少量(<0.1%)正常;暴涨 = 总线拥塞或 RT 调度劣化 |
|
||||||
|
| `stale` | tc_seq 未变化(读到旧帧)占比 | spin 模式下少量正常(跳帧);100% = TwinCAT 侧停发 |
|
||||||
|
| `mono` | tc_seq 回退次数 | 非零 = 帧乱序/映像撕裂,严重异常 |
|
||||||
|
| `cerr` | J1900 内部计算自洽错误 | 必须恒为 0(软件自检) |
|
||||||
|
| `mism` | J1900 侧图案失配累计 | 必须冻结(delta=0) |
|
||||||
|
| `dcph` | EL6695 从站侧 DC 系统时间 mod 周期 | DC 时钟相位观测 |
|
||||||
|
| `dcdr` | DC 时钟相对 CLOCK_MONOTONIC 的累计漂移 | 无持续漂移补偿时有界 wander(实测 ±几十 ppm) |
|
||||||
|
|
||||||
|
### 4.3 时延类统计(OnlineStats,结束报告)
|
||||||
|
|
||||||
|
| 统计 | 定义 | 实测(3h soak, 4kHz spin) |
|
||||||
|
|---|---|---|
|
||||||
|
| `exec_time` | 单周期处理耗时(读→变换→写就绪) | p50=25µs, p99=44µs, max=300µs |
|
||||||
|
| `period_jitter` | spin 循环相邻 fresh 检测的间隔偏差 | p50=12µs, mean=88µs(受跳帧节奏支配,非调度抖动) |
|
||||||
|
| `fprd_rtt` / `fpwr_rtt` | 单次事务往返 | 800B FPRD ≈ 160µs(决定 spin 检测粒度) |
|
||||||
|
|
||||||
|
### 4.4 RTT 分解模型(8kHz,125µs/周期)
|
||||||
|
|
||||||
|
实测 RTT mean 1.04ms ≈ 8.3 周期,分解:
|
||||||
|
|
||||||
|
| 环节 | 典型耗时 | 性质 | 可优化性 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| TwinCAT 输出量化(任务边界→帧发出) | ~62µs(0–125) | 整周期 | 仅能提频 |
|
||||||
|
| 正向:线缆+EL6695 桥固件转发 | ~150µs | 连续 | 桥固件决定,不可控 |
|
||||||
|
| J1900 spin 检测(FPRD 轮询粒度 160µs) | ~125µs(0–250) | 量化 | 受 800B 事务往返物理下限约束 |
|
||||||
|
| J1900 处理 + 异步 FPWR 上线 | ~45µs | 连续 | 已优化至极限(24–25µs exec) |
|
||||||
|
| 回程:线缆+桥转发 | ~150µs | 连续 | 同上 |
|
||||||
|
| TwinCAT 输入量化(帧到达→任务读取) | ~62µs(0–125) | 整周期 | 仅能提频 |
|
||||||
|
| 整周期簿记(lag 按整周期计) | ~125–250µs | 量化 | — |
|
||||||
|
|
||||||
|
**单向延迟推导**(TwinCAT→J1900,去掉回程三项):典型 **0.3–0.5ms**,最坏 ~0.6ms。
|
||||||
|
适用设定值级控制、配方下发、同步基准传递;紧伺服闭环受限于完整 RTT(~1ms,闭环带宽 ~100Hz 量级)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 测试结果
|
||||||
|
|
||||||
|
### 5.1 配置演进总表
|
||||||
|
|
||||||
|
| 阶段 | TwinCAT 任务 | J1900 模式 | 结果 | commit |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 8B 原理验证 | 1kHz | ticker | 通过(XOR 逐字节证明) | `309aaba` |
|
||||||
|
| 800B + SM 精确匹配 | 1kHz | ticker | 5h 零错误(18M 周期) | `f0163d8` |
|
||||||
|
| TwinCAT 侧 DC-Synchron | 1kHz | ticker | 激活干净,90k 周期零错误 | `aabfe31` |
|
||||||
|
| spin-lock + 异步 FPWR | 4kHz | spin | J1900 2040/s,数据零错误 | `cdb90c3` |
|
||||||
|
| J1900 侧 DC-Synchron | 4kHz | spin+dc | 180s 同步计数器全零 | `253f4b3` |
|
||||||
|
| **3 小时 soak** | **4kHz** | **spin+dc** | **全绿(见 5.2)** | — |
|
||||||
|
| RTT 优化 | 8kHz | spin+dc+RT 提权 | RTT min 0.75ms / mean 1.04ms | `4083df0` |
|
||||||
|
|
||||||
|
### 5.2 3 小时 soak(4kHz + spin + DC,2026-08-11 22:12 → 01:21)
|
||||||
|
|
||||||
|
**PLC 侧**(ADS 每 60s 采样 ×190):
|
||||||
|
|
||||||
|
| 指标 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| bSync | True ×190/190 |
|
||||||
|
| nMismatch delta | 0 ×190/190 |
|
||||||
|
| ADS 错误 | 0 |
|
||||||
|
| 任务速率 | 4000.9/s 恒定 |
|
||||||
|
| 总周期 | 45,370,909 |
|
||||||
|
|
||||||
|
**J1900 侧**(`--duration 11400` 整):
|
||||||
|
|
||||||
|
| 指标 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| 处理周期 | 33,711,687(~2957/s,74% 帧率,设计性跳帧) |
|
||||||
|
| payload 校验 | checked=33,598,819,mismatch=0 |
|
||||||
|
| tx_err / rx_err / comp_err | 0 / 0 / 0 |
|
||||||
|
| stale | 0.3%(跳帧读旧,正常) |
|
||||||
|
| late | 15,741(0.05%) |
|
||||||
|
| exec_time | p50=25µs,p99=44µs |
|
||||||
|
| DC 漂移 dcdr | 3.2h 累计 ~-9.8ms(≈-0.85ppm,有界 wander) |
|
||||||
|
|
||||||
|
### 5.3 DC-Synchron 验证细节
|
||||||
|
|
||||||
|
| 发现 | 证据 |
|
||||||
|
|---|---|
|
||||||
|
| ethercrab init 已分发从站侧 DC 系统时间 | 0x0920 offset 非零、0x0930 漂移补偿激活、0x0910 以真实速率推进 |
|
||||||
|
| EL6695 从站侧 ESC SYNC 寄存器(0x0980+)ECAT 侧不可读 | FPRD WKC=0;写入 WKC=1(固件经 PDI 自管) |
|
||||||
|
| 正确启用路径是 CoE | `0x1C32/0x1C33:01 sync_mode=2` 写入被固件接受且回读保持 |
|
||||||
|
| DC 模式下运行无副作用 | 180s:sm_event_missed=0,cycle_exceeded=0,sync_error=0(SM2+SM3) |
|
||||||
|
|
||||||
|
**未证实项**:SYNC0 脉冲全程实际激活状态(收尾计数器复读失败);
|
||||||
|
dcph 全范围游走表明 J1900 处理时序锁的是帧到达(spin)而非 SYNC0 网格。
|
||||||
|
表述应为"DC 配置下数据通路无异常",而非"DC 对性能有贡献"。
|
||||||
|
|
||||||
|
### 5.4 RTT 优化实验矩阵
|
||||||
|
|
||||||
|
| 配置 | RTT min | RTT mean | RTT max | J1900 处理率 | 结论 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 4kHz AF_PACKET | 1.0ms | 1.45ms | 1.75ms | 2957/s (74%) | 基线 |
|
||||||
|
| 8kHz AF_PACKET | 0.75ms | 1.09ms | 1.5ms | ~4000/s (50%) | 两端量化减半生效 |
|
||||||
|
| 8kHz io_uring 后端 | 1.13ms | 1.42ms | 1.75ms | 2043/s | ❌ 阻塞式设计更差,弃用 |
|
||||||
|
| **8kHz + tx_rx RT 提权** | **0.75ms** | **1.04ms** | **1.38ms** | ~3100–4000/s | ✅ 当前最优 |
|
||||||
|
|
||||||
|
**结论**:p50=1.0ms、min=0.75ms,"1ms 内"在最优/中位情形达成;mean 超出 4%。
|
||||||
|
剩余耗时为结构性(桥固件转发、FPRD 检测粒度、两端量化),
|
||||||
|
16kHz 提频预期收益仅 ~60µs 且引入 TcTTO 整数 µs 取整、PLC exec 预算、EL6695 能力三重风险,不建议。
|
||||||
|
|
||||||
|
### 5.5 鲁棒性测试
|
||||||
|
|
||||||
|
| 场景 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| ticker 误退(默认 60s 时长) | PLC 侧 bSync→False、nMismatch 增长——故障可观测;`--duration 0` 为正确用法 |
|
||||||
|
| 死链路注入(杀 ticker) | lag 5s 内 6→8.1 亿,bSync=False;重启后 4–6 周期恢复 |
|
||||||
|
| 双实例抢网卡(误操作注入) | 错误计数(txe/rxe)立即暴露,err_streak 超限自动退出 |
|
||||||
|
| 整机断电重启 | TwinCAT 随 boot project 自恢复 Run+PLC Run(4kHz 配置保持);J1900 /tmp 清空需重建(脚本化 <2 分钟);DHCP 地址漂移 .101→.100 需 ARP 扫描定位 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 已知限制与残余风险
|
||||||
|
|
||||||
|
1. **J1900 处理帧率 < 发送帧率**(8kHz 时 ~50%):受 800B FPRD 事务往返 ~160µs 物理约束。
|
||||||
|
echo 恒为最新帧(lag 指标证明),数据正确性不受影响,但每帧独立处理的场景不适用
|
||||||
|
2. **无持续 DC 漂移补偿**:ethercrab 仅静态补偿,dcdr 以 ±几十 ppm wander。
|
||||||
|
事件驱动(spin)交换不受影响;若需 J1900 硬锁 DC 网格需加周期性 FRMW 补偿
|
||||||
|
3. **TwinCAT 试用许可 7 天过期**:长期运行需周期性重新激活(过期症状:851 端口缺失、卡 Config 态)
|
||||||
|
4. **J1900 DHCP 地址漂移**:建议静态化或按 MAC 扫描定位(已备脚本化流程)
|
||||||
|
5. **方法学盲区**:桥转发冻结而 ESC 保持 OP 的失效模式黑盒不可检出(见 3.3)
|
||||||
|
|
||||||
|
## 7. 结论与建议
|
||||||
|
|
||||||
|
**结论**:TwinCAT 4kHz(250µs)/8kHz(125µs) + EL6695 桥 + J1900 spin-lock + 双侧 DC-Synchron
|
||||||
|
配置下,800B 双向实时通讯经 3 小时 4500 万周期验证零数据错误;
|
||||||
|
RTT 达 min 0.75ms / p50 1.0ms / mean 1.04ms,单向延迟典型 0.3–0.5ms。
|
||||||
|
数据完整性、新鲜度、故障可观测性、断电恢复均经实证。
|
||||||
|
|
||||||
|
**建议**:
|
||||||
|
1. 生产化时将图案区替换为真实过程数据(帧结构/序号校验/延迟特性已验证可直接复用)
|
||||||
|
2. soak 采样脚本已内置 lag 监控(`tc-ads-soak.ps1`),后续长测自动覆盖新鲜度
|
||||||
|
3. 若需 mean RTT 严格 <1ms,评估 LRW 单帧交换(需 FMMU 配置,改动中等)前先量化收益
|
||||||
|
4. J1900 管理口建议配置静态 IP,消除 DHCP 漂移运维成本
|
||||||
|
|
||||||
|
## 8. 附录:复现命令
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# J1900 侧(经跳板)
|
||||||
|
ssh -J tonycao@192.168.58.8 root@192.168.68.100
|
||||||
|
/tmp/el6695_rt_dc eth1 --duration 0 --cycle-us 125 --spin --dc # 8kHz 常驻
|
||||||
|
/tmp/kill_el6695.sh # 停止全部实例
|
||||||
|
|
||||||
|
# TwinCAT 侧(非提权可读)
|
||||||
|
scripts\tc-ads-state.ps1 # 运行状态
|
||||||
|
scripts\tc-ads-quick.ps1 # 速率 + bSync
|
||||||
|
scripts\tc-ads-lag.ps1 # nMatchLag 新鲜度采样
|
||||||
|
scripts\tc-ads-mismatch-delta.ps1 # nMismatch 冻结检查
|
||||||
|
scripts\tc-ads-soak.ps1 -Minutes N -LogFile X # 长测采样(WMI 方式后台启动)
|
||||||
|
scripts\tc-deploy-retry-800b.ps1 # 提权部署(构建+激活+重启)
|
||||||
|
```
|
||||||
301
monitor_5h.csv
Normal file
301
monitor_5h.csv
Normal file
|
|
@ -0,0 +1,301 @@
|
||||||
|
timestamp,nCycles,bSync,nReturnedSeq,nMismatch,nProcMismatch
|
||||||
|
2026-07-28 02:13:04,1825622,False,0,648246528,844072
|
||||||
|
2026-07-28 02:14:04,1885662,False,0,694359552,904116
|
||||||
|
2026-07-28 02:15:04,1945680,False,0,740451072,964130
|
||||||
|
2026-07-28 02:16:04,2005694,False,0,786541824,1024144
|
||||||
|
2026-07-28 02:17:04,2065708,True,2065707,789764352,1028339
|
||||||
|
2026-07-28 02:18:04,2125730,True,2125727,789764352,1028339
|
||||||
|
2026-07-28 02:19:04,2185753,True,2185751,789764352,1028339
|
||||||
|
2026-07-28 02:20:04,2245766,True,2245766,789764352,1028339
|
||||||
|
2026-07-28 02:21:04,2305794,True,2305794,789764352,1028339
|
||||||
|
2026-07-28 02:22:04,2365820,True,2365819,789764352,1028339
|
||||||
|
2026-07-28 02:23:04,2425842,True,2425840,789764352,1028339
|
||||||
|
2026-07-28 02:24:04,2485861,True,2485861,789764352,1028339
|
||||||
|
2026-07-28 02:25:04,2545882,True,2545880,789764352,1028339
|
||||||
|
2026-07-28 02:26:04,2605900,True,2605898,789764352,1028339
|
||||||
|
2026-07-28 02:27:04,2665917,True,2665914,789764352,1028339
|
||||||
|
2026-07-28 02:28:04,2725936,True,2725934,789764352,1028339
|
||||||
|
2026-07-28 02:29:04,2785961,True,2785960,789764352,1028339
|
||||||
|
2026-07-28 02:30:04,2845983,True,2845981,789764352,1028339
|
||||||
|
2026-07-28 02:31:04,2906006,True,2906006,789764352,1028339
|
||||||
|
2026-07-28 02:32:05,2966030,True,2966028,789764352,1028339
|
||||||
|
2026-07-28 02:33:05,3026050,True,3026048,789764352,1028339
|
||||||
|
2026-07-28 02:34:05,3086068,True,3086066,789764352,1028339
|
||||||
|
2026-07-28 02:35:05,3146087,True,3146087,789764352,1028339
|
||||||
|
2026-07-28 02:36:05,3206113,True,3206111,789764352,1028339
|
||||||
|
2026-07-28 02:37:05,3266139,True,3266139,789764352,1028339
|
||||||
|
2026-07-28 02:38:05,3326161,True,3326159,789764352,1028339
|
||||||
|
2026-07-28 02:39:05,3386183,True,3386181,789764352,1028339
|
||||||
|
2026-07-28 02:40:05,3446206,True,3446206,789764352,1028339
|
||||||
|
2026-07-28 02:41:05,3506232,True,3506232,789764352,1028339
|
||||||
|
2026-07-28 02:42:05,3566251,True,3566249,789764352,1028339
|
||||||
|
2026-07-28 02:43:05,3626266,True,3626264,789764352,1028339
|
||||||
|
2026-07-28 02:44:05,3686283,True,3686281,789764352,1028339
|
||||||
|
2026-07-28 02:45:05,3746311,True,3746310,789764352,1028339
|
||||||
|
2026-07-28 02:46:05,3806331,True,3806329,789764352,1028339
|
||||||
|
2026-07-28 02:47:05,3866346,True,3866344,789764352,1028339
|
||||||
|
2026-07-28 02:48:05,3926364,True,3926364,789764352,1028339
|
||||||
|
2026-07-28 02:49:05,3986384,True,3986382,789764352,1028339
|
||||||
|
2026-07-28 02:50:05,4046400,True,4046400,789764352,1028339
|
||||||
|
2026-07-28 02:51:05,4106421,True,4106419,789764352,1028339
|
||||||
|
2026-07-28 02:52:05,4166439,True,4166439,789764352,1028339
|
||||||
|
2026-07-28 02:53:05,4226466,True,4226466,789764352,1028339
|
||||||
|
2026-07-28 02:54:05,4286494,True,4286493,789764352,1028339
|
||||||
|
2026-07-28 02:55:05,4346518,True,4346518,789764352,1028339
|
||||||
|
2026-07-28 02:56:05,4406539,True,4406537,789764352,1028339
|
||||||
|
2026-07-28 02:57:05,4466565,True,4466565,789764352,1028339
|
||||||
|
2026-07-28 02:58:05,4526588,True,4526586,789764352,1028339
|
||||||
|
2026-07-28 02:59:05,4586605,True,4586603,789764352,1028339
|
||||||
|
2026-07-28 03:00:05,4646620,True,4646618,789764352,1028339
|
||||||
|
2026-07-28 03:01:05,4706637,True,4706636,789764352,1028339
|
||||||
|
2026-07-28 03:02:05,4766661,True,4766661,789764352,1028339
|
||||||
|
2026-07-28 03:03:05,4826684,True,4826681,789764352,1028339
|
||||||
|
2026-07-28 03:04:05,4886701,True,4886699,789764352,1028339
|
||||||
|
2026-07-28 03:05:05,4946724,True,4946724,789764352,1028339
|
||||||
|
2026-07-28 03:06:05,5006746,True,5006744,789764352,1028339
|
||||||
|
2026-07-28 03:07:05,5066761,True,5066759,789764352,1028339
|
||||||
|
2026-07-28 03:08:05,5126775,True,5126773,789764352,1028339
|
||||||
|
2026-07-28 03:09:05,5186795,True,5186795,789764352,1028339
|
||||||
|
2026-07-28 03:10:05,5246822,True,5246822,789764352,1028339
|
||||||
|
2026-07-28 03:11:05,5306841,True,5306839,789764352,1028339
|
||||||
|
2026-07-28 03:12:05,5366855,True,5366852,789764352,1028339
|
||||||
|
2026-07-28 03:13:05,5426873,True,5426870,789764352,1028339
|
||||||
|
2026-07-28 03:14:05,5486893,True,5486891,789764352,1028339
|
||||||
|
2026-07-28 03:15:05,5546919,True,5546919,789764352,1028339
|
||||||
|
2026-07-28 03:16:05,5606940,True,5606938,789764352,1028339
|
||||||
|
2026-07-28 03:17:05,5666962,True,5666962,789764352,1028339
|
||||||
|
2026-07-28 03:18:05,5727000,True,5727000,789764352,1028339
|
||||||
|
2026-07-28 03:19:05,5787020,True,5787018,789764352,1028339
|
||||||
|
2026-07-28 03:20:06,5847036,True,5847034,789764352,1028339
|
||||||
|
2026-07-28 03:21:06,5907050,True,5907047,789764352,1028339
|
||||||
|
2026-07-28 03:22:06,5967067,True,5967067,789764352,1028339
|
||||||
|
2026-07-28 03:23:06,6027086,True,6027084,789764352,1028339
|
||||||
|
2026-07-28 03:24:06,6087101,True,6087099,789764352,1028339
|
||||||
|
2026-07-28 03:25:06,6147119,True,6147117,789764352,1028339
|
||||||
|
2026-07-28 03:26:06,6207138,True,6207136,789764352,1028339
|
||||||
|
2026-07-28 03:27:06,6267161,True,6267161,789764352,1028339
|
||||||
|
2026-07-28 03:28:06,6327180,True,6327178,789764352,1028339
|
||||||
|
2026-07-28 03:29:06,6387197,True,6387195,789764352,1028339
|
||||||
|
2026-07-28 03:30:06,6447216,True,6447215,789764352,1028339
|
||||||
|
2026-07-28 03:31:06,6507241,True,6507241,789764352,1028339
|
||||||
|
2026-07-28 03:32:06,6567266,True,6567266,789764352,1028339
|
||||||
|
2026-07-28 03:33:06,6627289,True,6627287,789764352,1028339
|
||||||
|
2026-07-28 03:34:06,6687315,True,6687315,789764352,1028339
|
||||||
|
2026-07-28 03:35:06,6747338,True,6747336,789764352,1028339
|
||||||
|
2026-07-28 03:36:06,6807356,True,6807354,789764352,1028339
|
||||||
|
2026-07-28 03:37:06,6867377,True,6867375,789764352,1028339
|
||||||
|
2026-07-28 03:38:06,6927395,True,6927393,789764352,1028339
|
||||||
|
2026-07-28 03:39:06,6987416,True,6987416,789764352,1028339
|
||||||
|
2026-07-28 03:40:06,7047439,True,7047436,789764352,1028339
|
||||||
|
2026-07-28 03:41:06,7107458,True,7107456,789764352,1028339
|
||||||
|
2026-07-28 03:42:06,7167475,True,7167473,789764352,1028339
|
||||||
|
2026-07-28 03:43:06,7227497,True,7227497,789764352,1028339
|
||||||
|
2026-07-28 03:44:06,7287521,True,7287519,789764352,1028339
|
||||||
|
2026-07-28 03:45:06,7347541,True,7347539,789764352,1028339
|
||||||
|
2026-07-28 03:46:06,7407560,True,7407558,789764352,1028339
|
||||||
|
2026-07-28 03:47:06,7467578,True,7467577,789764352,1028339
|
||||||
|
2026-07-28 03:48:06,7527598,True,7527596,789764352,1028339
|
||||||
|
2026-07-28 03:49:06,7587620,True,7587619,789764352,1028339
|
||||||
|
2026-07-28 03:50:06,7647641,True,7647639,789764352,1028339
|
||||||
|
2026-07-28 03:51:06,7707657,True,7707655,789764352,1028339
|
||||||
|
2026-07-28 03:52:06,7767680,True,7767678,789764352,1028339
|
||||||
|
2026-07-28 03:53:06,7827700,True,7827700,789764352,1028339
|
||||||
|
2026-07-28 03:54:06,7887729,True,7887729,789764352,1028339
|
||||||
|
2026-07-28 03:55:06,7947752,True,7947750,789764352,1028339
|
||||||
|
2026-07-28 03:56:06,8007768,True,8007766,789764352,1028339
|
||||||
|
2026-07-28 03:57:06,8067790,True,8067788,789764352,1028339
|
||||||
|
2026-07-28 03:58:06,8127817,True,8127816,789764352,1028339
|
||||||
|
2026-07-28 03:59:06,8187839,True,8187838,789764352,1028339
|
||||||
|
2026-07-28 04:00:06,8247866,True,8247866,789764352,1028339
|
||||||
|
2026-07-28 04:01:06,8307889,True,8307887,789764352,1028339
|
||||||
|
2026-07-28 04:02:06,8367908,True,8367906,789764352,1028339
|
||||||
|
2026-07-28 04:03:06,8427927,True,8427927,789764352,1028339
|
||||||
|
2026-07-28 04:04:06,8487947,True,8487945,789764352,1028339
|
||||||
|
2026-07-28 04:05:06,8547969,True,8547969,789764352,1028339
|
||||||
|
2026-07-28 04:06:06,8607993,True,8607992,789764352,1028339
|
||||||
|
2026-07-28 04:07:06,8668018,True,8668017,789764352,1028339
|
||||||
|
2026-07-28 04:08:06,8728040,True,8728040,789764352,1028339
|
||||||
|
2026-07-28 04:09:07,8788059,True,8788057,789764352,1028339
|
||||||
|
2026-07-28 04:10:07,8848075,True,8848073,789764352,1028339
|
||||||
|
2026-07-28 04:11:07,8908092,True,8908090,789764352,1028339
|
||||||
|
2026-07-28 04:12:07,8968110,True,8968108,789764352,1028339
|
||||||
|
2026-07-28 04:13:07,9028132,True,9028132,789764352,1028339
|
||||||
|
2026-07-28 04:14:07,9088155,True,9088153,789764352,1028339
|
||||||
|
2026-07-28 04:15:07,9148175,True,9148174,789764352,1028339
|
||||||
|
2026-07-28 04:16:07,9208198,True,9208195,789764352,1028339
|
||||||
|
2026-07-28 04:17:07,9268217,True,9268215,789764352,1028339
|
||||||
|
2026-07-28 04:18:07,9328235,True,9328233,789764352,1028339
|
||||||
|
2026-07-28 04:19:07,9388256,True,9388256,789764352,1028339
|
||||||
|
2026-07-28 04:20:07,9448276,True,9448274,789764352,1028339
|
||||||
|
2026-07-28 04:21:07,9508295,True,9508293,789764352,1028339
|
||||||
|
2026-07-28 04:22:07,9568317,True,9568316,789764352,1028339
|
||||||
|
2026-07-28 04:23:07,9628343,True,9628343,789764352,1028339
|
||||||
|
2026-07-28 04:24:07,9688364,True,9688362,789764352,1028339
|
||||||
|
2026-07-28 04:25:07,9748382,True,9748379,789764352,1028339
|
||||||
|
2026-07-28 04:26:07,9808397,True,9808395,789764352,1028339
|
||||||
|
2026-07-28 04:27:07,9868412,True,9868410,789764352,1028339
|
||||||
|
2026-07-28 04:28:07,9928428,True,9928426,789764352,1028339
|
||||||
|
2026-07-28 04:29:07,9988442,True,9988440,789764352,1028339
|
||||||
|
2026-07-28 04:30:07,10048458,True,10048456,789764352,1028339
|
||||||
|
2026-07-28 04:31:07,10108475,True,10108473,789764352,1028339
|
||||||
|
2026-07-28 04:32:07,10168490,True,10168488,789764352,1028339
|
||||||
|
2026-07-28 04:33:07,10228506,True,10228504,789764352,1028339
|
||||||
|
2026-07-28 04:34:07,10288522,True,10288520,789764352,1028339
|
||||||
|
2026-07-28 04:35:07,10348545,True,10348545,789764352,1028339
|
||||||
|
2026-07-28 04:36:07,10408568,True,10408566,789764352,1028339
|
||||||
|
2026-07-28 04:37:07,10468588,True,10468586,789764352,1028339
|
||||||
|
2026-07-28 04:38:07,10528606,True,10528604,789764352,1028339
|
||||||
|
2026-07-28 04:39:07,10588623,True,10588621,789764352,1028339
|
||||||
|
2026-07-28 04:40:07,10648642,True,10648641,789764352,1028339
|
||||||
|
2026-07-28 04:41:07,10708667,True,10708667,789764352,1028339
|
||||||
|
2026-07-28 04:42:07,10768686,True,10768684,789764352,1028339
|
||||||
|
2026-07-28 04:43:07,10828711,True,10828710,789764352,1028339
|
||||||
|
2026-07-28 04:44:07,10888733,True,10888730,789764352,1028339
|
||||||
|
2026-07-28 04:45:07,10948755,True,10948754,789764352,1028339
|
||||||
|
2026-07-28 04:46:07,11008776,True,11008774,789764352,1028339
|
||||||
|
2026-07-28 04:47:07,11068798,True,11068798,789764352,1028339
|
||||||
|
2026-07-28 04:48:07,11128823,True,11128821,789764352,1028339
|
||||||
|
2026-07-28 04:49:07,11188847,True,11188847,789764352,1028339
|
||||||
|
2026-07-28 04:50:07,11248870,True,11248868,789764352,1028339
|
||||||
|
2026-07-28 04:51:07,11308890,True,11308888,789764352,1028339
|
||||||
|
2026-07-28 04:52:07,11368912,True,11368910,789764352,1028339
|
||||||
|
2026-07-28 04:53:07,11428935,True,11428934,789764352,1028339
|
||||||
|
2026-07-28 04:54:07,11488961,True,11488959,789764352,1028339
|
||||||
|
2026-07-28 04:55:07,11548978,True,11548976,789764352,1028339
|
||||||
|
2026-07-28 04:56:07,11608993,True,11608991,789764352,1028339
|
||||||
|
2026-07-28 04:57:07,11669013,True,11669011,789764352,1028339
|
||||||
|
2026-07-28 04:58:07,11729031,True,11729030,789764352,1028339
|
||||||
|
2026-07-28 04:59:07,11789060,True,11789060,789764352,1028339
|
||||||
|
2026-07-28 05:00:08,11849083,True,11849081,789764352,1028339
|
||||||
|
2026-07-28 05:01:08,11909100,True,11909098,789764352,1028339
|
||||||
|
2026-07-28 05:02:08,11969117,True,11969114,789764352,1028339
|
||||||
|
2026-07-28 05:03:08,12029135,True,12029133,789764352,1028339
|
||||||
|
2026-07-28 05:04:08,12089154,True,12089152,789764352,1028339
|
||||||
|
2026-07-28 05:05:08,12149172,True,12149170,789764352,1028339
|
||||||
|
2026-07-28 05:06:08,12209191,True,12209189,789764352,1028339
|
||||||
|
2026-07-28 05:07:08,12269215,True,12269215,789764352,1028339
|
||||||
|
2026-07-28 05:08:08,12329245,True,12329245,789764352,1028339
|
||||||
|
2026-07-28 05:09:08,12389273,True,12389273,789764352,1028339
|
||||||
|
2026-07-28 05:10:08,12449292,True,12449290,789764352,1028339
|
||||||
|
2026-07-28 05:11:08,12509311,True,12509309,789764352,1028339
|
||||||
|
2026-07-28 05:12:08,12569337,True,12569337,789764352,1028339
|
||||||
|
2026-07-28 05:13:08,12629364,True,12629364,789764352,1028339
|
||||||
|
2026-07-28 05:14:08,12689386,True,12689384,789764352,1028339
|
||||||
|
2026-07-28 05:15:08,12749405,True,12749403,789764352,1028339
|
||||||
|
2026-07-28 05:16:08,12809427,True,12809427,789764352,1028339
|
||||||
|
2026-07-28 05:17:08,12869455,True,12869455,789764352,1028339
|
||||||
|
2026-07-28 05:18:08,12929478,True,12929476,789764352,1028339
|
||||||
|
2026-07-28 05:19:08,12989502,True,12989502,789764352,1028339
|
||||||
|
2026-07-28 05:20:08,13049528,True,13049526,789764352,1028339
|
||||||
|
2026-07-28 05:21:08,13109545,True,13109542,789764352,1028339
|
||||||
|
2026-07-28 05:22:08,13169562,True,13169560,789764352,1028339
|
||||||
|
2026-07-28 05:23:08,13229578,True,13229576,789764352,1028339
|
||||||
|
2026-07-28 05:24:08,13289595,True,13289593,789764352,1028339
|
||||||
|
2026-07-28 05:25:08,13349611,True,13349609,789764352,1028339
|
||||||
|
2026-07-28 05:26:08,13409629,True,13409629,789764352,1028339
|
||||||
|
2026-07-28 05:27:08,13469651,True,13469649,789764352,1028339
|
||||||
|
2026-07-28 05:28:08,13529673,True,13529673,789764352,1028339
|
||||||
|
2026-07-28 05:29:08,13589702,True,13589701,789764352,1028339
|
||||||
|
2026-07-28 05:30:08,13649725,True,13649723,789764352,1028339
|
||||||
|
2026-07-28 05:31:08,13709741,True,13709740,789764352,1028339
|
||||||
|
2026-07-28 05:32:08,13769765,True,13769763,789764352,1028339
|
||||||
|
2026-07-28 05:33:08,13829783,True,13829781,789764352,1028339
|
||||||
|
2026-07-28 05:34:08,13889802,True,13889800,789764352,1028339
|
||||||
|
2026-07-28 05:35:08,13949825,True,13949825,789764352,1028339
|
||||||
|
2026-07-28 05:36:08,14009845,True,14009843,789764352,1028339
|
||||||
|
2026-07-28 05:37:08,14069859,True,14069857,789764352,1028339
|
||||||
|
2026-07-28 05:38:08,14129878,True,14129876,789764352,1028339
|
||||||
|
2026-07-28 05:39:08,14189894,True,14189891,789764352,1028339
|
||||||
|
2026-07-28 05:40:08,14249915,True,14249913,789764352,1028339
|
||||||
|
2026-07-28 05:41:08,14309933,True,14309931,789764352,1028339
|
||||||
|
2026-07-28 05:42:08,14369958,True,14369958,789764352,1028339
|
||||||
|
2026-07-28 05:43:08,14429982,True,14429982,789764352,1028339
|
||||||
|
2026-07-28 05:44:08,14490000,True,14489998,789764352,1028339
|
||||||
|
2026-07-28 05:45:08,14550017,True,14550015,789764352,1028339
|
||||||
|
2026-07-28 05:46:08,14610035,True,14610033,789764352,1028339
|
||||||
|
2026-07-28 05:47:08,14670056,True,14670056,789764352,1028339
|
||||||
|
2026-07-28 05:48:09,14730086,True,14730085,789764352,1028339
|
||||||
|
2026-07-28 05:49:09,14790110,True,14790109,789764352,1028339
|
||||||
|
2026-07-28 05:50:09,14850133,True,14850133,789764352,1028339
|
||||||
|
2026-07-28 05:51:09,14910155,True,14910153,789764352,1028339
|
||||||
|
2026-07-28 05:52:09,14970174,True,14970172,789764352,1028339
|
||||||
|
2026-07-28 05:53:09,15030193,True,15030191,789764352,1028339
|
||||||
|
2026-07-28 05:54:09,15090216,True,15090216,789764352,1028339
|
||||||
|
2026-07-28 05:55:09,15150245,True,15150245,789764352,1028339
|
||||||
|
2026-07-28 05:56:09,15210267,True,15210265,789764352,1028339
|
||||||
|
2026-07-28 05:57:09,15270285,True,15270282,789764352,1028339
|
||||||
|
2026-07-28 05:58:09,15330308,True,15330308,789764352,1028339
|
||||||
|
2026-07-28 05:59:09,15390329,True,15390327,789764352,1028339
|
||||||
|
2026-07-28 06:00:09,15450348,True,15450348,789764352,1028339
|
||||||
|
2026-07-28 06:01:09,15510376,True,15510376,789764352,1028339
|
||||||
|
2026-07-28 06:02:09,15570397,True,15570395,789764352,1028339
|
||||||
|
2026-07-28 06:03:09,15630422,True,15630421,789764352,1028339
|
||||||
|
2026-07-28 06:04:09,15690443,True,15690441,789764352,1028339
|
||||||
|
2026-07-28 06:05:09,15750461,True,15750459,789764352,1028339
|
||||||
|
2026-07-28 06:06:09,15810489,True,15810488,789764352,1028339
|
||||||
|
2026-07-28 06:07:09,15870512,True,15870510,789764352,1028339
|
||||||
|
2026-07-28 06:08:09,15930533,True,15930533,789764352,1028339
|
||||||
|
2026-07-28 06:09:09,15990556,True,15990554,789764352,1028339
|
||||||
|
2026-07-28 06:10:09,16050574,True,16050572,789764352,1028339
|
||||||
|
2026-07-28 06:11:09,16110587,True,16110585,789764352,1028339
|
||||||
|
2026-07-28 06:12:09,16170604,True,16170604,789764352,1028339
|
||||||
|
2026-07-28 06:13:09,16230627,True,16230627,789764352,1028339
|
||||||
|
2026-07-28 06:14:09,16290650,True,16290648,789764352,1028339
|
||||||
|
2026-07-28 06:15:09,16350673,True,16350672,789764352,1028339
|
||||||
|
2026-07-28 06:16:09,16410698,True,16410696,789764352,1028339
|
||||||
|
2026-07-28 06:17:09,16470717,True,16470715,789764352,1028339
|
||||||
|
2026-07-28 06:18:09,16530735,True,16530733,789764352,1028339
|
||||||
|
2026-07-28 06:19:09,16590757,True,16590757,789764352,1028339
|
||||||
|
2026-07-28 06:20:09,16650780,True,16650778,789764352,1028339
|
||||||
|
2026-07-28 06:21:09,16710798,True,16710798,789764352,1028339
|
||||||
|
2026-07-28 06:22:09,16770825,True,16770824,789764352,1028339
|
||||||
|
2026-07-28 06:23:09,16830844,True,16830842,789764352,1028339
|
||||||
|
2026-07-28 06:24:09,16890861,True,16890859,789764352,1028339
|
||||||
|
2026-07-28 06:25:09,16950878,True,16950875,789764352,1028339
|
||||||
|
2026-07-28 06:26:09,17010896,True,17010894,789764352,1028339
|
||||||
|
2026-07-28 06:27:09,17070912,True,17070910,789764352,1028339
|
||||||
|
2026-07-28 06:28:09,17130929,True,17130927,789764352,1028339
|
||||||
|
2026-07-28 06:29:09,17190947,True,17190945,789764352,1028339
|
||||||
|
2026-07-28 06:30:09,17250966,True,17250964,789764352,1028339
|
||||||
|
2026-07-28 06:31:09,17310985,True,17310983,789764352,1028339
|
||||||
|
2026-07-28 06:32:09,17371003,True,17371001,789764352,1028339
|
||||||
|
2026-07-28 06:33:09,17431023,True,17431021,789764352,1028339
|
||||||
|
2026-07-28 06:34:09,17491047,True,17491046,789764352,1028339
|
||||||
|
2026-07-28 06:35:09,17551074,True,17551074,789764352,1028339
|
||||||
|
2026-07-28 06:36:10,17611098,True,17611096,789764352,1028339
|
||||||
|
2026-07-28 06:37:10,17671122,True,17671122,789764352,1028339
|
||||||
|
2026-07-28 06:38:10,17731145,True,17731143,789764352,1028339
|
||||||
|
2026-07-28 06:39:10,17791173,True,17791172,789764352,1028339
|
||||||
|
2026-07-28 06:40:10,17851193,True,17851192,789764352,1028339
|
||||||
|
2026-07-28 06:41:10,17911221,True,17911221,789764352,1028339
|
||||||
|
2026-07-28 06:42:10,17971243,True,17971241,789764352,1028339
|
||||||
|
2026-07-28 06:43:10,18031261,True,18031258,789764352,1028339
|
||||||
|
2026-07-28 06:44:10,18091285,True,18091285,789764352,1028339
|
||||||
|
2026-07-28 06:45:10,18151306,True,18151304,789764352,1028339
|
||||||
|
2026-07-28 06:46:10,18211322,True,18211320,789764352,1028339
|
||||||
|
2026-07-28 06:47:10,18271342,True,18271340,789764352,1028339
|
||||||
|
2026-07-28 06:48:10,18331360,True,18331358,789764352,1028339
|
||||||
|
2026-07-28 06:49:10,18391383,True,18391383,789764352,1028339
|
||||||
|
2026-07-28 06:50:10,18451405,True,18451403,789764352,1028339
|
||||||
|
2026-07-28 06:51:10,18511423,True,18511421,789764352,1028339
|
||||||
|
2026-07-28 06:52:10,18571443,True,18571442,789764352,1028339
|
||||||
|
2026-07-28 06:53:10,18631472,True,18631472,789764352,1028339
|
||||||
|
2026-07-28 06:54:10,18691494,True,18691492,789764352,1028339
|
||||||
|
2026-07-28 06:55:10,18751520,True,18751520,789764352,1028339
|
||||||
|
2026-07-28 06:56:10,18811545,True,18811545,789764352,1028339
|
||||||
|
2026-07-28 06:57:10,18871568,True,18871566,789764352,1028339
|
||||||
|
2026-07-28 06:58:10,18931586,True,18931584,789764352,1028339
|
||||||
|
2026-07-28 06:59:10,18991609,True,18991608,789764352,1028339
|
||||||
|
2026-07-28 07:00:10,19051633,True,19051632,789764352,1028339
|
||||||
|
2026-07-28 07:01:10,19111652,True,19111649,789764352,1028339
|
||||||
|
2026-07-28 07:02:10,19171669,True,19171667,789764352,1028339
|
||||||
|
2026-07-28 07:03:10,19231688,True,19231686,789764352,1028339
|
||||||
|
2026-07-28 07:04:10,19291705,True,19291703,789764352,1028339
|
||||||
|
2026-07-28 07:05:10,19351730,True,19351730,789764352,1028339
|
||||||
|
2026-07-28 07:06:10,19411758,True,19411758,789764352,1028339
|
||||||
|
2026-07-28 07:07:10,19471781,True,19471779,789764352,1028339
|
||||||
|
2026-07-28 07:08:10,19531799,True,19531797,789764352,1028339
|
||||||
|
2026-07-28 07:09:10,19591815,True,19591815,789764352,1028339
|
||||||
|
2026-07-28 07:10:10,19651838,True,19651837,789764352,1028339
|
||||||
|
2026-07-28 07:11:10,19711862,True,19711862,789764352,1028339
|
||||||
|
2026-07-28 07:12:10,19771883,True,19771881,789764352,1028339
|
||||||
|
29
scripts/d2000-deploy.ps1
Normal file
29
scripts/d2000-deploy.ps1
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#Requires -Version 5.1
|
||||||
|
# Build + deploy el6695-rt for Phytium D2000 (aarch64-unknown-linux-musl).
|
||||||
|
# Usage: .\d2000-deploy.ps1 [-JumpHost user@host] [-TargetHost user@ip] [-RemoteDir /root/gateway]
|
||||||
|
param(
|
||||||
|
[string]$JumpHost = 'tonycao@192.168.58.8', # TODO: replace with jump host #2 for D2000
|
||||||
|
[string]$TargetHost = 'root@192.168.68.100', # TODO: replace with D2000 address
|
||||||
|
[string]$RemoteName = 'el6695_rt_d2000',
|
||||||
|
[string]$RemoteDir = '/root/gateway',
|
||||||
|
[string]$Variant = '800b' # payload variant marker for filename only
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$repo = 'C:\Users\tonycao\work\ethercat-linux'
|
||||||
|
$bin = Join-Path $repo 'target\aarch64-unknown-linux-musl\release\el6695-rt'
|
||||||
|
|
||||||
|
Write-Host '=== D2000 (aarch64) build ==='
|
||||||
|
cargo build --release --target aarch64-unknown-linux-musl --manifest-path (Join-Path $repo 'Cargo.toml')
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw 'build failed' }
|
||||||
|
|
||||||
|
# verify ELF arch = AArch64 (0xB7)
|
||||||
|
$d = [System.IO.File]::ReadAllBytes($bin)
|
||||||
|
if ($d[18] -ne 0xB7 -or $d[19] -ne 0x00) { throw 'not an AArch64 binary!' }
|
||||||
|
Write-Host ('aarch64 ELF OK, {0} bytes' -f $d.Length)
|
||||||
|
|
||||||
|
Write-Host '=== deploy ==='
|
||||||
|
$remote = "$RemoteDir/$RemoteName"
|
||||||
|
scp -o ConnectTimeout=10 -o BatchMode=yes -J $JumpHost $bin "${TargetHost}:$remote"
|
||||||
|
ssh -o BatchMode=yes -J $JumpHost $TargetHost "chmod +x $remote; $remote --help 2>&1 | head -3 || true"
|
||||||
|
Write-Host "deployed to ${TargetHost}:$remote"
|
||||||
23
scripts/tc-ads-lag.ps1
Normal file
23
scripts/tc-ads-lag.ps1
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
param([int]$Samples = 20, [int]$IntervalMs = 500)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$adsDll = 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll'
|
||||||
|
Add-Type -Path $adsDll
|
||||||
|
$c = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c.Connect('169.254.176.217.1.1', 851)
|
||||||
|
function ReadSym($name, $type) {
|
||||||
|
$h = $c.CreateVariableHandle($name)
|
||||||
|
$v = $c.ReadAny($h, $type)
|
||||||
|
$c.DeleteVariableHandle($h)
|
||||||
|
return $v
|
||||||
|
}
|
||||||
|
$lags = New-Object System.Collections.Generic.List[int]
|
||||||
|
for ($k = 0; $k -lt $Samples; $k++) {
|
||||||
|
$lag = ReadSym 'GVL_Sync.nMatchLag' ([int])
|
||||||
|
$bs = ReadSym 'GVL_Sync.bSync' ([bool])
|
||||||
|
$lags.Add($lag)
|
||||||
|
Write-Host ("{0} lag={1,4} cyc bSync={2}" -f (Get-Date -Format 'HH:mm:ss'), $lag, $bs)
|
||||||
|
if ($k -lt $Samples - 1) { Start-Sleep -Milliseconds $IntervalMs }
|
||||||
|
}
|
||||||
|
$s = $lags | Sort-Object
|
||||||
|
Write-Host ("min={0} p50={1} max={2} mean={3:F2}" -f $s[0], $s[[int]($Samples/2)], $s[$Samples-1], (($lags | Measure-Object -Average).Average))
|
||||||
|
$c.Dispose()
|
||||||
15
scripts/tc-ads-mismatch-delta.ps1
Normal file
15
scripts/tc-ads-mismatch-delta.ps1
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
Add-Type -Path 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll'
|
||||||
|
$c = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c.Connect('169.254.176.217.1.1', 851)
|
||||||
|
$h = $c.CreateVariableHandle('GVL_Sync.nMismatch')
|
||||||
|
$h2 = $c.CreateVariableHandle('GVL_Sync.bSync')
|
||||||
|
$m1 = $c.ReadAny($h, [int]); $b1 = $c.ReadAny($h2, [bool])
|
||||||
|
Write-Host ("t+15s : bSync=$b1 nMismatch=$m1")
|
||||||
|
Start-Sleep 30
|
||||||
|
$m2 = $c.ReadAny($h, [int]); $b2 = $c.ReadAny($h2, [bool])
|
||||||
|
Write-Host ("t+45s : bSync=$b2 nMismatch=$m2 delta=$($m2-$m1)")
|
||||||
|
Start-Sleep 30
|
||||||
|
$m3 = $c.ReadAny($h, [int]); $b3 = $c.ReadAny($h2, [bool])
|
||||||
|
Write-Host ("t+75s : bSync=$b3 nMismatch=$m3 delta=$($m3-$m2)")
|
||||||
|
$c.Dispose()
|
||||||
13
scripts/tc-ads-quick.ps1
Normal file
13
scripts/tc-ads-quick.ps1
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
Add-Type -Path 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll'
|
||||||
|
$c = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c.Connect('169.254.176.217.1.1', 851)
|
||||||
|
$h = $c.CreateVariableHandle('GVL_Sync.nCycles')
|
||||||
|
$n1 = $c.ReadAny($h, [uint64])
|
||||||
|
Start-Sleep -Milliseconds 5000
|
||||||
|
$n2 = $c.ReadAny($h, [uint64])
|
||||||
|
$c.DeleteVariableHandle($h)
|
||||||
|
Write-Host ("nCycles rate = {0} Hz" -f [math]::Round(($n2 - $n1) / 5.0, 1))
|
||||||
|
$bs = $c.ReadAny($c.CreateVariableHandle('GVL_Sync.bSync'), [bool])
|
||||||
|
Write-Host "bSync = $bs"
|
||||||
|
$c.Dispose()
|
||||||
15
scripts/tc-ads-setrun.ps1
Normal file
15
scripts/tc-ads-setrun.ps1
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
Add-Type -Path 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll'
|
||||||
|
$c = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c.Connect('169.254.176.217.1.1', 10000)
|
||||||
|
Write-Host "before: $($c.ReadState().AdsState)"
|
||||||
|
try {
|
||||||
|
$c.WriteControl((New-Object TwinCAT.Ads.StateInfo([TwinCAT.Ads.AdsState]::Run, 0)))
|
||||||
|
Write-Host "WriteControl Run sent"
|
||||||
|
} catch { Write-Host "err: $_" }
|
||||||
|
$c.Dispose()
|
||||||
|
Start-Sleep 10
|
||||||
|
$c2 = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c2.Connect('169.254.176.217.1.1', 10000)
|
||||||
|
Write-Host "after: $($c2.ReadState().AdsState)"
|
||||||
|
$c2.Dispose()
|
||||||
32
scripts/tc-ads-soak.ps1
Normal file
32
scripts/tc-ads-soak.ps1
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
param([int]$Minutes = 190, [string]$LogFile = "$env:TEMP\soak.log")
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$adsDll = 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll'
|
||||||
|
Add-Type -Path $adsDll
|
||||||
|
$c = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c.Connect('169.254.176.217.1.1', 851)
|
||||||
|
function ReadSym($name, $type) {
|
||||||
|
$h = $c.CreateVariableHandle($name)
|
||||||
|
$v = $c.ReadAny($h, $type)
|
||||||
|
$c.DeleteVariableHandle($h)
|
||||||
|
return $v
|
||||||
|
}
|
||||||
|
$prevMism = $null
|
||||||
|
$prevCyc = $null
|
||||||
|
for ($k = 0; $k -lt $Minutes; $k++) {
|
||||||
|
try {
|
||||||
|
$nc = ReadSym 'GVL_Sync.nCycles' ([uint64])
|
||||||
|
$bs = ReadSym 'GVL_Sync.bSync' ([bool])
|
||||||
|
$nm = ReadSym 'GVL_Sync.nMismatch' ([int])
|
||||||
|
$lag = ReadSym 'GVL_Sync.nMatchLag' ([int])
|
||||||
|
$dmism = if ($null -ne $prevMism) { $nm - $prevMism } else { 0 }
|
||||||
|
$rate = if ($null -ne $prevCyc) { ([int64]$nc - $prevCyc) / 60.0 } else { 0 }
|
||||||
|
$prevMism = $nm
|
||||||
|
$prevCyc = [int64]$nc
|
||||||
|
$line = "{0} k={1,3} nCycles={2,-9} rate={3,7:F1}/s bSync={4} lag={5,4} nMismatch={6} dMism={7}" -f (Get-Date -Format 'HH:mm:ss'), $k, $nc, $rate, $bs, $lag, $nm, $dmism
|
||||||
|
} catch {
|
||||||
|
$line = "{0} k={1,3} ADS ERROR: {2}" -f (Get-Date -Format 'HH:mm:ss'), $k, $_.Exception.Message
|
||||||
|
}
|
||||||
|
Add-Content -Path $LogFile -Value $line
|
||||||
|
Start-Sleep -Seconds 60
|
||||||
|
}
|
||||||
|
$c.Dispose()
|
||||||
13
scripts/tc-ads-state.ps1
Normal file
13
scripts/tc-ads-state.ps1
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
Add-Type -Path 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll'
|
||||||
|
foreach ($port in @(10000, 851)) {
|
||||||
|
try {
|
||||||
|
$c = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c.Connect('169.254.176.217.1.1', $port)
|
||||||
|
$st = $c.ReadState()
|
||||||
|
Write-Host "port $port : AdsState=$($st.AdsState) DeviceState=$($st.DeviceState)"
|
||||||
|
$c.Dispose()
|
||||||
|
} catch {
|
||||||
|
Write-Host "port $port : ERR $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
48
scripts/tc-ads-verify-800b.ps1
Normal file
48
scripts/tc-ads-verify-800b.ps1
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
#Requires -Version 5.1
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
$adsDll = 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll'
|
||||||
|
Add-Type -Path $adsDll -ErrorAction Stop
|
||||||
|
$c = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c.Connect('169.254.176.217.1.1', 851)
|
||||||
|
function ReadSym($name, $type) {
|
||||||
|
$h = $c.CreateVariableHandle($name)
|
||||||
|
$v = $c.ReadAny($h, $type)
|
||||||
|
$c.DeleteVariableHandle($h)
|
||||||
|
return $v
|
||||||
|
}
|
||||||
|
function ReadBytes($name, $len) {
|
||||||
|
$h = $c.CreateVariableHandle($name)
|
||||||
|
$arr = $c.ReadAny($h, [byte[]], @($len))
|
||||||
|
$c.DeleteVariableHandle($h)
|
||||||
|
return $arr
|
||||||
|
}
|
||||||
|
$nc = ReadSym 'GVL_Sync.nCycles' ([uint64])
|
||||||
|
$bs = ReadSym 'GVL_Sync.bSync' ([bool])
|
||||||
|
$nrs = ReadSym 'GVL_Sync.nReturnedSeq' ([uint64])
|
||||||
|
$nm = ReadSym 'GVL_Sync.nMismatch' ([int])
|
||||||
|
$in = ReadBytes 'GVL_Sync.aIn1' 800
|
||||||
|
$out = ReadBytes 'GVL_Sync.aOut1' 800
|
||||||
|
|
||||||
|
$nzIn = ($in | Where-Object { $_ -ne 0 }).Count
|
||||||
|
$nzOut = ($out | Where-Object { $_ -ne 0 }).Count
|
||||||
|
Write-Host ("nCycles={0} bSync={1} nReturnedSeq={2} lag={3} nMismatch={4}" -f $nc, $bs, $nrs, ($nc - $nrs), $nm)
|
||||||
|
Write-Host ("aIn1 nonzero = {0}/800" -f $nzIn)
|
||||||
|
Write-Host ("aOut1 nonzero = {0}/800" -f $nzOut)
|
||||||
|
|
||||||
|
# per-byte pattern check against nReturnedSeq: expected[i] = (nReturnedSeq + i) mod 256, pattern region
|
||||||
|
$bad = 0
|
||||||
|
$badIdx = @()
|
||||||
|
for ($i = 16; $i -lt 800; $i++) {
|
||||||
|
if ($i -ge 264 -and $i -le 279) { continue }
|
||||||
|
$exp = [byte](($nrs + $i) % 256)
|
||||||
|
if ($in[$i] -ne $exp) { $bad++; if ($badIdx.Count -lt 8) { $badIdx += $i } }
|
||||||
|
}
|
||||||
|
Write-Host ("pattern[16..799 excl 264..279] mismatches = {0} {1}" -f $bad, $(if ($badIdx.Count) { 'at ' + ($badIdx -join ',') } else { '' }))
|
||||||
|
|
||||||
|
# show window samples at several offsets
|
||||||
|
foreach ($off in @(0, 16, 128, 256, 288, 512, 768)) {
|
||||||
|
$seg = [BitConverter]::ToString($in[$off..($off + 7)])
|
||||||
|
Write-Host ("aIn1[{0,3}..{1,3}] = {2}" -f $off, ($off + 7), $seg)
|
||||||
|
}
|
||||||
|
$c.Dispose()
|
||||||
24
scripts/tc-ads-watch-10min.ps1
Normal file
24
scripts/tc-ads-watch-10min.ps1
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$adsDll = 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll'
|
||||||
|
Add-Type -Path $adsDll
|
||||||
|
$c = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c.Connect('169.254.176.217.1.1', 851)
|
||||||
|
function ReadSym($name, $type) {
|
||||||
|
$h = $c.CreateVariableHandle($name)
|
||||||
|
$v = $c.ReadAny($h, $type)
|
||||||
|
$c.DeleteVariableHandle($h)
|
||||||
|
return $v
|
||||||
|
}
|
||||||
|
$prev = $null
|
||||||
|
for ($k = 1; $k -le 10; $k++) {
|
||||||
|
$nc = ReadSym 'GVL_Sync.nCycles' ([uint64])
|
||||||
|
$bs = ReadSym 'GVL_Sync.bSync' ([bool])
|
||||||
|
$nrs = ReadSym 'GVL_Sync.nReturnedSeq' ([uint64])
|
||||||
|
$nm = ReadSym 'GVL_Sync.nMismatch' ([int])
|
||||||
|
$lag = [int64]$nc - [int64]$nrs
|
||||||
|
$delta = if ($null -ne $prev) { $nm - $prev } else { 0 }
|
||||||
|
$prev = $nm
|
||||||
|
Write-Host ("{0,2} nCycles={1,-8} lag={2,4} bSync={3} nMismatch={4} delta={5}" -f $k, $nc, $lag, $bs, $nm, $delta)
|
||||||
|
if ($k -lt 10) { Start-Sleep -Seconds 60 }
|
||||||
|
}
|
||||||
|
$c.Dispose()
|
||||||
61
scripts/tc-deploy-retry-4khz.ps1
Normal file
61
scripts/tc-deploy-retry-4khz.ps1
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
#Requires -Version 5.1
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
|
||||||
|
function Invoke-Com {
|
||||||
|
param([scriptblock]$Fn, [int]$Retries = 30, [int]$DelayMs = 2000)
|
||||||
|
for ($i = 0; $i -lt $Retries; $i++) {
|
||||||
|
try { return & $Fn } catch {
|
||||||
|
$hr = $_.Exception.HResult
|
||||||
|
if ($hr -eq -2147418111) { Start-Sleep -Milliseconds $DelayMs; continue }
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw "COM call rejected $Retries times (RPC_E_CALL_REJECTED)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "=== Deploy 8B with RPC retry ==="
|
||||||
|
$dte = Invoke-Com { New-Object -ComObject TcXaeShell.DTE.17.0 }
|
||||||
|
Invoke-Com { $dte.MainWindow.Visible = $true } | Out-Null
|
||||||
|
try { Invoke-Com { $dte.SuppressUI = $false } | Out-Null } catch {}
|
||||||
|
Invoke-Com { $dte.Solution.Open('C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary_4kHz\TwinCATProject1.sln') } | Out-Null
|
||||||
|
Write-Host "solution opened"
|
||||||
|
|
||||||
|
$sm = $null
|
||||||
|
for ($i = 0; $i -lt 40; $i++) {
|
||||||
|
try { $sm = Invoke-Com { $dte.Solution.Projects.Item(1).Object } -Retries 3 -DelayMs 3000; if ($sm) { break } } catch { Write-Host " waiting sysmgr..." }
|
||||||
|
}
|
||||||
|
if (-not $sm) { Write-Host "FATAL: no sysmgr"; exit 1 }
|
||||||
|
Write-Host "sysmgr OK"
|
||||||
|
Invoke-Com { $sm.SetTargetNetId('169.254.176.217.1.1') } | Out-Null
|
||||||
|
|
||||||
|
Write-Host "building..."
|
||||||
|
try { Invoke-Com { $dte.ExecuteCommand('Build.BuildSolution') } -Retries 10 | Out-Null } catch { Write-Host "build cmd: $_" }
|
||||||
|
# wait for build done (BuildState 3 = done)
|
||||||
|
$done = $false
|
||||||
|
for ($i = 0; $i -lt 90; $i++) {
|
||||||
|
try {
|
||||||
|
$bs = Invoke-Com { $dte.Solution.SolutionBuild.BuildState } -Retries 5 -DelayMs 3000
|
||||||
|
$bi = Invoke-Com { $dte.Solution.SolutionBuild.LastBuildInfo } -Retries 5 -DelayMs 1000
|
||||||
|
if ($bs -eq 3) { Write-Host "build done (LastBuildInfo=$bi) after $($i*2)s"; $done = $true; break }
|
||||||
|
} catch { Write-Host " build poll err: $_" }
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
}
|
||||||
|
if (-not $done) { Write-Host "WARN: build state never reached done" }
|
||||||
|
|
||||||
|
# dump build errors if any
|
||||||
|
try {
|
||||||
|
$ew = $dte.ToolWindows.ErrorList
|
||||||
|
Write-Host "error list check skipped"
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
Write-Host "activating..."
|
||||||
|
Invoke-Com { $sm.ActivateConfiguration() } -Retries 30 -DelayMs 3000 | Out-Null
|
||||||
|
Write-Host "activated"
|
||||||
|
Start-Sleep 10
|
||||||
|
Write-Host "restarting runtime..."
|
||||||
|
Invoke-Com { $sm.StartRestartTwinCAT() } -Retries 30 -DelayMs 3000 | Out-Null
|
||||||
|
Write-Host "restart issued"
|
||||||
|
Start-Sleep 12
|
||||||
|
try { Write-Host "IsTwinCATStarted=$($sm.IsTwinCATStarted)" } catch {}
|
||||||
|
Write-Host "=== done ==="
|
||||||
61
scripts/tc-deploy-retry-800b.ps1
Normal file
61
scripts/tc-deploy-retry-800b.ps1
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
#Requires -Version 5.1
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
|
||||||
|
function Invoke-Com {
|
||||||
|
param([scriptblock]$Fn, [int]$Retries = 30, [int]$DelayMs = 2000)
|
||||||
|
for ($i = 0; $i -lt $Retries; $i++) {
|
||||||
|
try { return & $Fn } catch {
|
||||||
|
$hr = $_.Exception.HResult
|
||||||
|
if ($hr -eq -2147418111) { Start-Sleep -Milliseconds $DelayMs; continue }
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw "COM call rejected $Retries times (RPC_E_CALL_REJECTED)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "=== Deploy 8B with RPC retry ==="
|
||||||
|
$dte = Invoke-Com { New-Object -ComObject TcXaeShell.DTE.17.0 }
|
||||||
|
Invoke-Com { $dte.MainWindow.Visible = $true } | Out-Null
|
||||||
|
try { Invoke-Com { $dte.SuppressUI = $false } | Out-Null } catch {}
|
||||||
|
Invoke-Com { $dte.Solution.Open('C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary_800B\TwinCATProject1.sln') } | Out-Null
|
||||||
|
Write-Host "solution opened"
|
||||||
|
|
||||||
|
$sm = $null
|
||||||
|
for ($i = 0; $i -lt 40; $i++) {
|
||||||
|
try { $sm = Invoke-Com { $dte.Solution.Projects.Item(1).Object } -Retries 3 -DelayMs 3000; if ($sm) { break } } catch { Write-Host " waiting sysmgr..." }
|
||||||
|
}
|
||||||
|
if (-not $sm) { Write-Host "FATAL: no sysmgr"; exit 1 }
|
||||||
|
Write-Host "sysmgr OK"
|
||||||
|
Invoke-Com { $sm.SetTargetNetId('169.254.176.217.1.1') } | Out-Null
|
||||||
|
|
||||||
|
Write-Host "building..."
|
||||||
|
try { Invoke-Com { $dte.ExecuteCommand('Build.BuildSolution') } -Retries 10 | Out-Null } catch { Write-Host "build cmd: $_" }
|
||||||
|
# wait for build done (BuildState 3 = done)
|
||||||
|
$done = $false
|
||||||
|
for ($i = 0; $i -lt 90; $i++) {
|
||||||
|
try {
|
||||||
|
$bs = Invoke-Com { $dte.Solution.SolutionBuild.BuildState } -Retries 5 -DelayMs 3000
|
||||||
|
$bi = Invoke-Com { $dte.Solution.SolutionBuild.LastBuildInfo } -Retries 5 -DelayMs 1000
|
||||||
|
if ($bs -eq 3) { Write-Host "build done (LastBuildInfo=$bi) after $($i*2)s"; $done = $true; break }
|
||||||
|
} catch { Write-Host " build poll err: $_" }
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
}
|
||||||
|
if (-not $done) { Write-Host "WARN: build state never reached done" }
|
||||||
|
|
||||||
|
# dump build errors if any
|
||||||
|
try {
|
||||||
|
$ew = $dte.ToolWindows.ErrorList
|
||||||
|
Write-Host "error list check skipped"
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
Write-Host "activating..."
|
||||||
|
Invoke-Com { $sm.ActivateConfiguration() } -Retries 30 -DelayMs 3000 | Out-Null
|
||||||
|
Write-Host "activated"
|
||||||
|
Start-Sleep 10
|
||||||
|
Write-Host "restarting runtime..."
|
||||||
|
Invoke-Com { $sm.StartRestartTwinCAT() } -Retries 30 -DelayMs 3000 | Out-Null
|
||||||
|
Write-Host "restart issued"
|
||||||
|
Start-Sleep 12
|
||||||
|
try { Write-Host "IsTwinCATStarted=$($sm.IsTwinCATStarted)" } catch {}
|
||||||
|
Write-Host "=== done ==="
|
||||||
30
scripts/tc-monitor-5h.ps1
Normal file
30
scripts/tc-monitor-5h.ps1
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
# 5-hour stability monitor: logs PLC sync metrics to CSV every 60s.
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
Add-Type -Path 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll'
|
||||||
|
$logFile = 'C:\Users\tonycao\work\ethercat-linux\monitor_5h.csv'
|
||||||
|
'timestamp,nCycles,bSync,nReturnedSeq,nMismatch,nProcMismatch' | Out-File $logFile -Encoding utf8
|
||||||
|
$start = Get-Date
|
||||||
|
for ($i = 0; $i -lt 300; $i++) {
|
||||||
|
try {
|
||||||
|
$c = New-Object TwinCAT.Ads.TcAdsClient
|
||||||
|
$c.Connect('169.254.176.217.1.1', 851)
|
||||||
|
$h1 = $c.CreateVariableHandle('GVL_Sync.nCycles')
|
||||||
|
$h2 = $c.CreateVariableHandle('GVL_Sync.bSync')
|
||||||
|
$h3 = $c.CreateVariableHandle('GVL_Sync.nReturnedSeq')
|
||||||
|
$h4 = $c.CreateVariableHandle('GVL_Sync.nMismatch')
|
||||||
|
$h5 = $c.CreateVariableHandle('GVL_Sync.nProcMismatch')
|
||||||
|
$nc = $c.ReadAny($h1, [uint64])
|
||||||
|
$bs = $c.ReadAny($h2, [bool])
|
||||||
|
$nrs = $c.ReadAny($h3, [uint64])
|
||||||
|
$nm = $c.ReadAny($h4, [int])
|
||||||
|
$npm = $c.ReadAny($h5, [uint32])
|
||||||
|
$c.Dispose()
|
||||||
|
$ts = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
|
||||||
|
"$ts,$nc,$bs,$nrs,$nm,$npm" | Out-File $logFile -Append -Encoding utf8
|
||||||
|
} catch {
|
||||||
|
$ts = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
|
||||||
|
"$ts,ERROR,,,," | Out-File $logFile -Append -Encoding utf8
|
||||||
|
}
|
||||||
|
Start-Sleep -Seconds 60
|
||||||
|
}
|
||||||
18
scripts/tc-open-project-800b.ps1
Normal file
18
scripts/tc-open-project-800b.ps1
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
# Open project in visible XAE and leave it running for interactive inspection.
|
||||||
|
$dte = New-Object -ComObject TcXaeShell.DTE.17.0
|
||||||
|
$dte.MainWindow.Visible = $true
|
||||||
|
try { $dte.SuppressUI = $false } catch {}
|
||||||
|
$dte.Solution.Open('C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary_800B\TwinCATProject1.sln')
|
||||||
|
Start-Sleep 25
|
||||||
|
$sm = $null
|
||||||
|
for ($i = 0; $i -lt 20; $i++) {
|
||||||
|
try { $sm = $dte.Solution.Projects.Item(1).Object; if ($sm) { break } } catch {}
|
||||||
|
Start-Sleep 3
|
||||||
|
}
|
||||||
|
if (-not $sm) { Write-Host 'no sysmgr'; exit 1 }
|
||||||
|
try { $sm.SetTargetNetId('169.254.176.217.1.1'); Write-Host 'target set' } catch { Write-Host "target: $_" }
|
||||||
|
Start-Sleep 3
|
||||||
|
try { Write-Host "IsTwinCATStarted=$($sm.IsTwinCATStarted)" } catch {}
|
||||||
|
Write-Host 'XAE left open. Inspect: TIID -> 设备 1 (EtherCAT) -> 模块 3 (EK1100) -> 模块 10 (EL6695) -> IO Inputs / IO Outputs -> Online tab'
|
||||||
62
scripts/tc-relink-800b.ps1
Normal file
62
scripts/tc-relink-800b.ps1
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
$dte = New-Object -ComObject TcXaeShell.DTE.17.0
|
||||||
|
$dte.MainWindow.Visible = $true
|
||||||
|
try { $dte.SuppressUI = $false } catch {}
|
||||||
|
$dte.Solution.Open("C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary_800B\TwinCATProject1.sln")
|
||||||
|
Start-Sleep 20
|
||||||
|
$sm = $null
|
||||||
|
for ($i = 0; $i -lt 20; $i++) {
|
||||||
|
try { $sm = $dte.Solution.Projects.Item(1).Object; if ($sm) { break } } catch {}
|
||||||
|
Start-Sleep 3
|
||||||
|
}
|
||||||
|
if (-not $sm) { Write-Host "no sysmgr"; exit 1 }
|
||||||
|
try { $sm.SetTargetNetId("169.254.176.217.1.1") } catch {}
|
||||||
|
Start-Sleep 2
|
||||||
|
|
||||||
|
$box = "TIID^设备 1 (EtherCAT)^模块 3 (EK1100)^模块 10 (EL6695)"
|
||||||
|
$cands = @(
|
||||||
|
"$box^IO Outputs^IO Outputs",
|
||||||
|
"$box^IO Outputs^IO Outputs[0]",
|
||||||
|
"$box^IO Inputs^IO Inputs",
|
||||||
|
"$box^IO Inputs^IO Inputs[0]"
|
||||||
|
)
|
||||||
|
foreach ($c in $cands) {
|
||||||
|
$it = $null
|
||||||
|
try { $it = $sm.TryLookupTreeItem($c) } catch { Write-Host "err $c : $_" }
|
||||||
|
if ($it) {
|
||||||
|
$sz = ""
|
||||||
|
try { $sz = $it.BitSize } catch {}
|
||||||
|
Write-Host "OK: $c name=$($it.Name) type=$($it.ItemType) bitsize=$sz"
|
||||||
|
} else {
|
||||||
|
Write-Host "MISS: $c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$varAin = "TIPC^PlcSync^PlcSync Instance^PlcTask Inputs^GVL_Sync.aIn1"
|
||||||
|
$varAout = "TIPC^PlcSync^PlcSync Instance^PlcTask Outputs^GVL_Sync.aOut1"
|
||||||
|
foreach ($v in @($varAin, $varAout)) {
|
||||||
|
$it = $null
|
||||||
|
try { $it = $sm.TryLookupTreeItem($v) } catch {}
|
||||||
|
if ($it) { Write-Host "PLC OK: $v" } else { Write-Host "PLC MISS: $v" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "--- unlink existing ---"
|
||||||
|
try { $sm.UnlinkVariables($varAin, "$box^IO Inputs^IO Inputs") ; Write-Host "unlinked aIn1" } catch { Write-Host "unlink aIn1: $_" }
|
||||||
|
try { $sm.UnlinkVariables($varAout, "$box^IO Outputs^IO Outputs"); Write-Host "unlinked aOut1" } catch { Write-Host "unlink aOut1: $_" }
|
||||||
|
|
||||||
|
Write-Host "--- link whole arrays ---"
|
||||||
|
try { $sm.LinkVariables($varAin, "$box^IO Inputs^IO Inputs"); Write-Host "linked aIn1" } catch { Write-Host "link aIn1: $_" }
|
||||||
|
try { $sm.LinkVariables($varAout, "$box^IO Outputs^IO Outputs"); Write-Host "linked aOut1" } catch { Write-Host "link aOut1: $_" }
|
||||||
|
|
||||||
|
$xml = $sm.ProduceMappingInfo()
|
||||||
|
[System.IO.File]::WriteAllText("C:\Users\tonycao\work\ethercat-linux\tmp_mapping2.xml", $xml, [System.Text.Encoding]::UTF8)
|
||||||
|
Write-Host "mapping saved"
|
||||||
|
|
||||||
|
Write-Host "--- save + activate + restart ---"
|
||||||
|
try { $dte.Solution.SaveAll(); Write-Host "saved" } catch { Write-Host "save: $_" }
|
||||||
|
try { $sm.ActivateConfiguration(); Write-Host "activated" } catch { Write-Host "activate: $_" }
|
||||||
|
Start-Sleep 10
|
||||||
|
try { $sm.StartRestartTwinCAT(); Write-Host "restart issued" } catch { Write-Host "restart: $_" }
|
||||||
|
Start-Sleep 10
|
||||||
|
Write-Host "done"
|
||||||
|
|
@ -38,18 +38,35 @@ pub fn twin_layout_blob(obj: u16) -> Vec<u8> {
|
||||||
/// Size-parameterised TwinCAT line-format blob.
|
/// Size-parameterised TwinCAT line-format blob.
|
||||||
///
|
///
|
||||||
/// For payloads of 1024 bytes this is byte-identical to `twin_layout_blob`.
|
/// For payloads of 1024 bytes this is byte-identical to `twin_layout_blob`.
|
||||||
/// For small payloads (<= 30 bytes, fitting one 240-bit entry) a single
|
/// For payloads up to 240 bits a single entry references the real object at
|
||||||
/// entry references the real object at the exact bit length.
|
/// the exact bit length. Larger payloads use the TwinCAT line format:
|
||||||
|
/// 1 real entry @240bit + continuation entries @240bit + 1 continuation
|
||||||
|
/// entry at the remainder bit length.
|
||||||
pub fn twin_layout_blob_sized(obj: u16, payload_bytes: usize) -> Vec<u8> {
|
pub fn twin_layout_blob_sized(obj: u16, payload_bytes: usize) -> Vec<u8> {
|
||||||
if payload_bytes == 1024 {
|
if payload_bytes == 1024 {
|
||||||
return twin_layout_blob(obj);
|
return twin_layout_blob(obj);
|
||||||
}
|
}
|
||||||
let bits = (payload_bytes * 8).min(240) as u8;
|
let total_bits = payload_bytes * 8;
|
||||||
let idx_lo = (obj & 0xFF) as u8;
|
let idx_lo = (obj & 0xFF) as u8;
|
||||||
let idx_hi = (obj >> 8) as u8;
|
let idx_hi = (obj >> 8) as u8;
|
||||||
let mut blob = Vec::with_capacity(6);
|
if total_bits <= 240 {
|
||||||
blob.extend_from_slice(&1u16.to_le_bytes());
|
let mut blob = Vec::with_capacity(6);
|
||||||
blob.extend_from_slice(&[bits, 1, idx_lo, idx_hi]);
|
blob.extend_from_slice(&1u16.to_le_bytes());
|
||||||
|
blob.extend_from_slice(&[total_bits as u8, 1, idx_lo, idx_hi]);
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
let full = total_bits / 240;
|
||||||
|
let rem = (total_bits % 240) as u8;
|
||||||
|
let n = if rem > 0 { full + 1 } else { full };
|
||||||
|
let mut blob = Vec::with_capacity(2 + n * 4);
|
||||||
|
blob.extend_from_slice(&(n as u16).to_le_bytes());
|
||||||
|
blob.extend_from_slice(&[240, 1, idx_lo, idx_hi]);
|
||||||
|
for _ in 1..full {
|
||||||
|
blob.extend_from_slice(&[240, 0, 0, 0]);
|
||||||
|
}
|
||||||
|
if rem > 0 {
|
||||||
|
blob.extend_from_slice(&[rem, 0, 0, 0]);
|
||||||
|
}
|
||||||
blob
|
blob
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
292
src/main.rs
292
src/main.rs
|
|
@ -19,7 +19,7 @@ use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use el6695_rt::{bridge, regs, rt, stats};
|
use el6695_rt::{bridge, regs, rt, stats};
|
||||||
use ethercrab::{
|
use ethercrab::{
|
||||||
std::{ethercat_now, tx_rx_task},
|
std::{ethercat_now, tx_rx_task, tx_rx_task_io_uring},
|
||||||
MainDevice, MainDeviceConfig, PduStorage, Timeouts,
|
MainDevice, MainDeviceConfig, PduStorage, Timeouts,
|
||||||
};
|
};
|
||||||
use stats::OnlineStats;
|
use stats::OnlineStats;
|
||||||
|
|
@ -31,15 +31,15 @@ const PDI_LEN: usize = 3072;
|
||||||
|
|
||||||
// Secondary side SM layout (ESI-defined process data SMs)
|
// Secondary side SM layout (ESI-defined process data SMs)
|
||||||
const SM_TX_ADDR: u16 = 0x1C00; // SM2: outputs (J1900 writes data to TwinCAT)
|
const SM_TX_ADDR: u16 = 0x1C00; // SM2: outputs (J1900 writes data to TwinCAT)
|
||||||
const SM_TX_LEN: usize = 8;
|
const SM_TX_LEN: usize = 800;
|
||||||
const SM_RX_ADDR: u16 = 0x8E00; // SM3: inputs (J1900 reads data from TwinCAT)
|
const SM_RX_ADDR: u16 = 0x8E00; // SM3: inputs (J1900 reads data from TwinCAT)
|
||||||
// Full PDO: 2B diagnostic word + 8B payload (single-entry twin_layout blob
|
// Full PDO: 2B diagnostic word + 8B payload (single-entry twin_layout blob
|
||||||
// expands 0x6000:01 to 8 bytes).
|
// expands 0x6000:01 to 8 bytes).
|
||||||
const SM_RX_LEN: usize = 8; // SM3 buffer (no diag word, pure 8B payload)
|
const SM_RX_LEN: usize = 800; // SM3 buffer (no diag word, pure 8B payload)
|
||||||
const SM_RX_PLD_LEN: usize = 8; // actual payload after stripping diagnostic word
|
const SM_RX_PLD_LEN: usize = 800; // actual payload after stripping diagnostic word
|
||||||
const RX_PREFIX: usize = 0; // no diag word prefix (0x1C13=[0x1A08] only)
|
const RX_PREFIX: usize = 0; // no diag word prefix (0x1C13=[0x1A08] only)
|
||||||
|
|
||||||
const PAYLOAD_LEN: usize = 8;
|
const PAYLOAD_LEN: usize = 800;
|
||||||
|
|
||||||
/// Abort after this many consecutive cycles with a TX or RX error.
|
/// Abort after this many consecutive cycles with a TX or RX error.
|
||||||
const MAX_ERR_STREAK: u64 = 1000;
|
const MAX_ERR_STREAK: u64 = 1000;
|
||||||
|
|
@ -98,6 +98,11 @@ struct Config {
|
||||||
/// payload changes (new TwinCAT frame), then process immediately.
|
/// payload changes (new TwinCAT frame), then process immediately.
|
||||||
/// Phase-locks J1900 processing to the primary master's frame arrival.
|
/// Phase-locks J1900 processing to the primary master's frame arrival.
|
||||||
spin: bool,
|
spin: bool,
|
||||||
|
/// Enable DC SYNC0 on the EL6695 secondary (cycle = --cycle-us) and print
|
||||||
|
/// per-second DC phase/drift telemetry in the report line.
|
||||||
|
dc: bool,
|
||||||
|
/// Use the io_uring-based blocking TX/RX loop instead of AF_PACKET poll.
|
||||||
|
iouring: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_args() -> Result<Config, String> {
|
fn parse_args() -> Result<Config, String> {
|
||||||
|
|
@ -116,6 +121,8 @@ fn parse_args() -> Result<Config, String> {
|
||||||
el2262: false,
|
el2262: false,
|
||||||
el1252_every: 1,
|
el1252_every: 1,
|
||||||
spin: false,
|
spin: false,
|
||||||
|
dc: false,
|
||||||
|
iouring: false,
|
||||||
};
|
};
|
||||||
let mut args = std::env::args().skip(1).peekable();
|
let mut args = std::env::args().skip(1).peekable();
|
||||||
if let Some(a) = args.peek() {
|
if let Some(a) = args.peek() {
|
||||||
|
|
@ -146,6 +153,8 @@ fn parse_args() -> Result<Config, String> {
|
||||||
"--prio" => cfg.prio = val("--prio")?.parse().map_err(|_| "bad --prio")?,
|
"--prio" => cfg.prio = val("--prio")?.parse().map_err(|_| "bad --prio")?,
|
||||||
"--quiet" => cfg.quiet = true,
|
"--quiet" => cfg.quiet = true,
|
||||||
"--spin" => cfg.spin = true,
|
"--spin" => cfg.spin = true,
|
||||||
|
"--dc" => cfg.dc = true,
|
||||||
|
"--iouring" => cfg.iouring = true,
|
||||||
"--el2202" => cfg.el2202 = true,
|
"--el2202" => cfg.el2202 = true,
|
||||||
"--el2202-dual" => {
|
"--el2202-dual" => {
|
||||||
cfg.el2202 = true;
|
cfg.el2202 = true;
|
||||||
|
|
@ -231,10 +240,21 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
);
|
);
|
||||||
|
|
||||||
let iface = cfg.iface.clone();
|
let iface = cfg.iface.clone();
|
||||||
|
let use_iouring = cfg.iouring;
|
||||||
let _txrx = std::thread::spawn(move || {
|
let _txrx = std::thread::spawn(move || {
|
||||||
let task = tx_rx_task(&iface, tx, rx).expect("create tx_rx task");
|
if let Err(e) = rt::promote_current_thread(1, 79, false) {
|
||||||
if let Err(e) = futures_lite::future::block_on(task) {
|
eprintln!("tx_rx RT promotion failed (continuing): {}", e);
|
||||||
eprintln!("tx_rx task ended with error: {:?}", e);
|
}
|
||||||
|
if use_iouring {
|
||||||
|
match tx_rx_task_io_uring(&iface, tx, rx) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => eprintln!("io_uring tx_rx task ended with error: {:?}", e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let task = tx_rx_task(&iface, tx, rx).expect("create tx_rx task");
|
||||||
|
if let Err(e) = futures_lite::future::block_on(task) {
|
||||||
|
eprintln!("tx_rx task ended with error: {:?}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -525,6 +545,148 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
println!(" >>> OP achieved! <<<");
|
println!(" >>> OP achieved! <<<");
|
||||||
|
|
||||||
|
// ---- Optional DC SYNC0 on the EL6695 secondary ----
|
||||||
|
// ethercrab init already distributed the system time (offset/propagation
|
||||||
|
// delay/static drift). Here we only start the SYNC0 unit on --cycle-us
|
||||||
|
// grid and verify the clock actually advances.
|
||||||
|
let mut dc_ref: Option<(u64, u64)> = None; // (dc_time, mono_time) at setup
|
||||||
|
if cfg.dc {
|
||||||
|
let t1 = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||||
|
.receive::<u64>(&maindevice)
|
||||||
|
.await
|
||||||
|
.unwrap_or(0);
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
let t2 = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||||
|
.receive::<u64>(&maindevice)
|
||||||
|
.await
|
||||||
|
.unwrap_or(0);
|
||||||
|
let adv = t2.wrapping_sub(t1);
|
||||||
|
println!("DC system time: {} -> {} (adv {} ns in 100ms)", t1, t2, adv);
|
||||||
|
if adv < 50_000_000 || adv > 500_000_000 {
|
||||||
|
println!("DC: clock not advancing sanely; SYNC0 setup skipped");
|
||||||
|
} else {
|
||||||
|
// Diagnostics: which DC register groups actually exist?
|
||||||
|
for (reg, name) in [
|
||||||
|
(0x0900u16, "rx_time_port0(u32)"),
|
||||||
|
(0x0920u16, "sys_time_offset(u64)"),
|
||||||
|
(0x0928u16, "prop_delay(u32)"),
|
||||||
|
(0x0930u16, "drift... (u16)"),
|
||||||
|
(0x0980u16, "sync_activation(u8)"),
|
||||||
|
(0x0981u16, "sync_active_byte(u8)"),
|
||||||
|
(0x09A0u16, "sync0_cycle(u32)"),
|
||||||
|
(0x09AEu16, "sync0_status(u8)"),
|
||||||
|
] {
|
||||||
|
match ethercrab::Command::fprd(station, reg)
|
||||||
|
.receive_slice(&maindevice, 8)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(pdu) => {
|
||||||
|
let raw: &[u8] = &pdu;
|
||||||
|
println!(" DC probe 0x{:04X} {}: {:02X?}", reg, name, &raw[..8.min(raw.len())]);
|
||||||
|
}
|
||||||
|
Err(e) => println!(" DC probe 0x{:04X} {}: ERR {:?}", reg, name, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CoE view: 0x1C32 (SM2 outputs) / 0x1C33 (SM3 inputs) sync params
|
||||||
|
for idx in [0x1C32u16, 0x1C33] {
|
||||||
|
let n = sd.sdo_read::<u8>(idx, 0).await.unwrap_or(0xFF);
|
||||||
|
println!(" CoE 0x{:04X}: {} subentries", idx, n);
|
||||||
|
for (sub, name) in [(0x01u8, "sync_mode"), (0x04, "modes_supported"), (0x0B, "sm_event_missed"), (0x0C, "cycle_exceeded")] {
|
||||||
|
match sd.sdo_read::<u16>(idx, sub).await {
|
||||||
|
Ok(x) => println!(" CoE 0x{:04X}:{:02X} {} = 0x{:04X}", idx, sub, name, x),
|
||||||
|
Err(e) => println!(" CoE 0x{:04X}:{:02X} {} ERR {:?}", idx, sub, name, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match sd.sdo_read::<u32>(idx, 0x02).await {
|
||||||
|
Ok(x) => println!(" CoE 0x{:04X}:02 cycle_time_ns = {}", idx, x),
|
||||||
|
Err(e) => println!(" CoE 0x{:04X}:02 cycle_time_ns ERR {:?}", idx, e),
|
||||||
|
}
|
||||||
|
match sd.sdo_read::<u8>(idx, 0x20).await {
|
||||||
|
Ok(x) => println!(" CoE 0x{:04X}:20 sync_error = {}", idx, x),
|
||||||
|
Err(e) => println!(" CoE 0x{:04X}:20 sync_error ERR {:?}", idx, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let start = t2.wrapping_add(100_000_000);
|
||||||
|
let w1 = ethercrab::Command::fpwr(station, regs::DC_SYNC_START_TIME)
|
||||||
|
.send(&maindevice, start)
|
||||||
|
.await;
|
||||||
|
println!(" DC write start_time(0x0990): {:?}", w1.as_ref().map(|_| "OK").map_err(|e| format!("{:?}", e)));
|
||||||
|
let w2 = ethercrab::Command::fpwr(station, regs::DC_SYNC0_CYCLE)
|
||||||
|
.send(&maindevice, cycle_ns as u32)
|
||||||
|
.await;
|
||||||
|
println!(" DC write sync0_cycle(0x09A0): {:?}", w2.as_ref().map(|_| "OK").map_err(|e| format!("{:?}", e)));
|
||||||
|
let w3 = regs::rmw_u8(
|
||||||
|
&maindevice,
|
||||||
|
station,
|
||||||
|
regs::DC_SYNC_ACTIVATION,
|
||||||
|
regs::DC_SYNC0_ACTIVATE,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
println!(" DC rmw activation(0x0980): {:?}", w3.as_ref().map(|v| format!("0x{:02X}", v)).map_err(|e| format!("{:?}", e)));
|
||||||
|
// rmw needs a read (fails here); try a blind full-byte write instead
|
||||||
|
let w4 = ethercrab::Command::fpwr(station, regs::DC_SYNC_ACTIVATION)
|
||||||
|
.send(&maindevice, regs::DC_SYNC0_ACTIVATE)
|
||||||
|
.await;
|
||||||
|
println!(" DC blind write activation(0x0980)=0x01: {:?}", w4.as_ref().map(|_| "OK").map_err(|e| format!("{:?}", e)));
|
||||||
|
std::thread::sleep(Duration::from_millis(150));
|
||||||
|
// Try selecting DC-Synchron SYNC0 via firmware object (mode 2).
|
||||||
|
// May be rejected in OP; worth observing the abort code either way.
|
||||||
|
let mut coe_dc_ok = true;
|
||||||
|
for idx in [0x1C32u16, 0x1C33] {
|
||||||
|
match sd.sdo_write::<u16>(idx, 0x01, 2).await {
|
||||||
|
Ok(_) => println!(" CoE write 0x{:04X}:01 sync_mode=2 OK", idx),
|
||||||
|
Err(e) => {
|
||||||
|
coe_dc_ok = false;
|
||||||
|
println!(" CoE write 0x{:04X}:01 sync_mode=2 ERR {:?}", idx, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match sd.sdo_read::<u16>(idx, 0x01).await {
|
||||||
|
Ok(x) => println!(" CoE-after 0x{:04X}:01 sync_mode = {}", idx, x),
|
||||||
|
Err(e) => {
|
||||||
|
coe_dc_ok = false;
|
||||||
|
println!(" CoE-after 0x{:04X}:01 sync_mode ERR {:?}", idx, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if coe_dc_ok {
|
||||||
|
let t_dc = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||||
|
.receive::<u64>(&maindevice)
|
||||||
|
.await
|
||||||
|
.unwrap_or(0);
|
||||||
|
dc_ref = Some((t_dc, rt::now_ns()));
|
||||||
|
println!("DC-Synchron: firmware sync_mode=2 accepted, DC telemetry on");
|
||||||
|
}
|
||||||
|
let act = ethercrab::Command::fprd(station, regs::DC_SYNC_ACTIVATION)
|
||||||
|
.receive::<u8>(&maindevice)
|
||||||
|
.await;
|
||||||
|
let cyc0 = ethercrab::Command::fprd(station, regs::DC_SYNC0_CYCLE)
|
||||||
|
.receive::<u32>(&maindevice)
|
||||||
|
.await;
|
||||||
|
let t3 = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||||
|
.receive::<u64>(&maindevice)
|
||||||
|
.await
|
||||||
|
.unwrap_or(0);
|
||||||
|
let (al, alc) = bridge::drive_state(&maindevice, station, 0x0008).await;
|
||||||
|
println!(" AL after DC writes: {}", bridge::verdict(al, alc));
|
||||||
|
println!(
|
||||||
|
"DC SYNC0: act={:?} cycle={:?} start=+100ms (t3={} start_delta={}ns)",
|
||||||
|
act.as_ref().map(|v| format!("0x{:02X}", v)).map_err(|e| format!("{:?}", e)),
|
||||||
|
cyc0.as_ref().map_err(|e| format!("{:?}", e)),
|
||||||
|
t3,
|
||||||
|
t3.wrapping_sub(start) as i64
|
||||||
|
);
|
||||||
|
if matches!(act, Ok(a) if a & regs::DC_SYNC0_ACTIVATE != 0)
|
||||||
|
&& matches!(cyc0, Ok(c) if c == cycle_ns as u32)
|
||||||
|
{
|
||||||
|
dc_ref = Some((t3, rt::now_ns()));
|
||||||
|
println!("DC SYNC0: active on EL6695 secondary");
|
||||||
|
} else {
|
||||||
|
println!("DC SYNC0: readback mismatch; running without DC telemetry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Optional EL2202 oscilloscope waveform (toggle one DO per cycle) ----
|
// ---- Optional EL2202 oscilloscope waveform (toggle one DO per cycle) ----
|
||||||
const EL2202_OUT_ADDR: u16 = 0x0F00;
|
const EL2202_OUT_ADDR: u16 = 0x0F00;
|
||||||
let mut el2202: Option<(u16, u16)> = None;
|
let mut el2202: Option<(u16, u16)> = None;
|
||||||
|
|
@ -755,6 +917,17 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut prev_rx8 = [0u8; 8]; // spin-lock: previous payload (change detect)
|
let mut prev_rx8 = [0u8; 8]; // spin-lock: previous payload (change detect)
|
||||||
let mut last_fresh: Option<Instant> = None; // spin-lock: last fresh-data instant
|
let mut last_fresh: Option<Instant> = None; // spin-lock: last fresh-data instant
|
||||||
|
|
||||||
|
// Async pipelined FPWR: two alternating leaked tx slots so the in-flight
|
||||||
|
// write future can hold a 'static buffer while the response is reaped
|
||||||
|
// at the start of the next cycle.
|
||||||
|
let tx_slot_a = Box::leak(Box::new([0u8; PAYLOAD_LEN]));
|
||||||
|
let tx_slot_b = Box::leak(Box::new([0u8; PAYLOAD_LEN]));
|
||||||
|
let mut tx_slot_toggle = false;
|
||||||
|
let mut pending_tx: Option<
|
||||||
|
std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), ethercrab::error::Error>> + '_>>,
|
||||||
|
> = None;
|
||||||
|
let mut tx_late: u64 = 0;
|
||||||
|
|
||||||
let mut aborted: Option<String> = None;
|
let mut aborted: Option<String> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -823,6 +996,21 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
cyc += 1;
|
cyc += 1;
|
||||||
|
|
||||||
|
// Reap async FPWR kicked last cycle: the spin-detect above gave the
|
||||||
|
// response ~one full cycle to arrive, so this should never block.
|
||||||
|
if let Some(mut f) = pending_tx.take() {
|
||||||
|
match futures_lite::future::poll_once(f.as_mut()).await {
|
||||||
|
Some(Ok(_)) => {}
|
||||||
|
Some(Err(_)) => {
|
||||||
|
tx_err += 1;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
tx_late += 1;
|
||||||
|
let _ = f.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !spin_mode {
|
if !spin_mode {
|
||||||
// 1. Read SM3 (TwinCAT data via bridge)
|
// 1. Read SM3 (TwinCAT data via bridge)
|
||||||
let t_rx0 = Instant::now();
|
let t_rx0 = Instant::now();
|
||||||
|
|
@ -913,14 +1101,47 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tx_buf[264..268].copy_from_slice(&(jitter_ns as i32).to_le_bytes());
|
tx_buf[264..268].copy_from_slice(&(jitter_ns as i32).to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
let t_tx0 = Instant::now();
|
if spin_mode {
|
||||||
let tx_ok = ethercrab::Command::fpwr(station, SM_TX_ADDR)
|
// Async pipelined FPWR: kick the frame onto the wire now
|
||||||
.send(&maindevice, &tx_buf[..PAYLOAD_LEN])
|
// (poll_once drives it to sendable), reap response next
|
||||||
.await
|
// cycle. Frees ~240us of blocking write time per cycle.
|
||||||
.is_ok();
|
let p = if tx_slot_toggle {
|
||||||
tx_rtt_st.push(t_tx0.elapsed().as_nanos() as i64);
|
tx_slot_b.as_mut_ptr()
|
||||||
if !tx_ok {
|
} else {
|
||||||
tx_err += 1;
|
tx_slot_a.as_mut_ptr()
|
||||||
|
};
|
||||||
|
tx_slot_toggle = !tx_slot_toggle;
|
||||||
|
let slot: &'static mut [u8] =
|
||||||
|
unsafe { std::slice::from_raw_parts_mut(p, PAYLOAD_LEN) };
|
||||||
|
slot.copy_from_slice(&tx_buf[..PAYLOAD_LEN]);
|
||||||
|
let mut fut = Box::pin(
|
||||||
|
ethercrab::Command::fpwr(station, SM_TX_ADDR).send(&maindevice, &slot[..]),
|
||||||
|
);
|
||||||
|
// Kick: drive the write future for up to ~20us so the frame
|
||||||
|
// is marked sendable and the TX thread puts it on the wire
|
||||||
|
// NOW. poll_once alone does not reach sendable state.
|
||||||
|
let t_kick = Instant::now();
|
||||||
|
let _ = futures_lite::future::or(
|
||||||
|
async { fut.as_mut().await.is_ok() },
|
||||||
|
async {
|
||||||
|
while t_kick.elapsed().as_micros() < 20 {
|
||||||
|
futures_lite::future::yield_now().await;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
pending_tx = Some(fut);
|
||||||
|
} else {
|
||||||
|
let t_tx0 = Instant::now();
|
||||||
|
let tx_ok = ethercrab::Command::fpwr(station, SM_TX_ADDR)
|
||||||
|
.send(&maindevice, &tx_buf[..PAYLOAD_LEN])
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
tx_rtt_st.push(t_tx0.elapsed().as_nanos() as i64);
|
||||||
|
if !tx_ok {
|
||||||
|
tx_err += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1050,20 +1271,37 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
let proc_val = tc_seq.wrapping_mul(7).wrapping_add(3);
|
let proc_val = tc_seq.wrapping_mul(7).wrapping_add(3);
|
||||||
let stale_pct = if cyc > 0 { (rx_stale * 100 / cyc) } else { 0 };
|
let stale_pct = if cyc > 0 { (rx_stale * 100 / cyc) } else { 0 };
|
||||||
|
let mut dc_info = String::new();
|
||||||
|
if let Some((dc0, mono0)) = dc_ref {
|
||||||
|
if let Ok(dc_now) = ethercrab::Command::fprd(station, regs::DC_SYSTEM_TIME)
|
||||||
|
.receive::<u64>(&maindevice)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let mono_now = rt::now_ns();
|
||||||
|
let drift = (dc_now.wrapping_sub(dc0)) as i64 - (mono_now - mono0) as i64;
|
||||||
|
dc_info = format!(
|
||||||
|
" dcph={} dcdr={}ns",
|
||||||
|
dc_now % cycle_ns,
|
||||||
|
drift
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
println!(
|
println!(
|
||||||
"S tms={} cyc={} raw={} proc={} txe={} rxe={} stale={}% mono={} cerr={} mism={} e0={} e1={}",
|
"S tms={} cyc={} raw={} proc={} txe={} rxe={} late={} stale={}% mono={} cerr={} mism={} e0={} e1={}{}",
|
||||||
start.elapsed().as_millis(),
|
start.elapsed().as_millis(),
|
||||||
cyc,
|
cyc,
|
||||||
tc_seq,
|
tc_seq,
|
||||||
proc_val,
|
proc_val,
|
||||||
tx_err,
|
tx_err,
|
||||||
rx_err,
|
rx_err,
|
||||||
|
tx_late,
|
||||||
stale_pct,
|
stale_pct,
|
||||||
rx_nonmono,
|
rx_nonmono,
|
||||||
comp_err,
|
comp_err,
|
||||||
payload_mismatch,
|
payload_mismatch,
|
||||||
pos_edges,
|
pos_edges,
|
||||||
pos1_edges,
|
pos1_edges,
|
||||||
|
dc_info,
|
||||||
);
|
);
|
||||||
last_report = Instant::now();
|
last_report = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
@ -1073,6 +1311,26 @@ async fn run(cfg: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reap any in-flight async FPWR before reporting
|
||||||
|
if let Some(f) = pending_tx.take() {
|
||||||
|
let _ = f.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- DC post-run: re-read firmware sync counters accumulated during the run ----
|
||||||
|
if cfg.dc {
|
||||||
|
println!("\n=== DC post-run sync counters (0x1C32 SM2 / 0x1C33 SM3) ===");
|
||||||
|
for idx in [0x1C32u16, 0x1C33] {
|
||||||
|
let mode = sd.sdo_read::<u16>(idx, 0x01).await.unwrap_or(0xFFFF);
|
||||||
|
let missed = sd.sdo_read::<u16>(idx, 0x0B).await.unwrap_or(0xFFFF);
|
||||||
|
let exceeded = sd.sdo_read::<u16>(idx, 0x0C).await.unwrap_or(0xFFFF);
|
||||||
|
let serr = sd.sdo_read::<u8>(idx, 0x20).await.unwrap_or(0xFF);
|
||||||
|
println!(
|
||||||
|
" 0x{:04X}: sync_mode={} sm_event_missed={} cycle_exceeded={} sync_error={}",
|
||||||
|
idx, mode, missed, exceeded, serr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Final report ----
|
// ---- Final report ----
|
||||||
println!("\n=== timing stats ===");
|
println!("\n=== timing stats ===");
|
||||||
jitter_st.report(1000, "us");
|
jitter_st.report(1000, "us");
|
||||||
|
|
|
||||||
18
uia_dump.py
Normal file
18
uia_dump.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import sys, time
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||||
|
from pywinauto import Application
|
||||||
|
|
||||||
|
app = Application(backend='uia').connect(process=11748, timeout=10)
|
||||||
|
wins = app.windows()
|
||||||
|
print('windows:', len(wins))
|
||||||
|
for w in wins:
|
||||||
|
try:
|
||||||
|
t = w.window_text()
|
||||||
|
if not t:
|
||||||
|
continue
|
||||||
|
print('---', t)
|
||||||
|
tabs = [x.window_text() for x in w.descendants(control_type='TabItem')]
|
||||||
|
if tabs:
|
||||||
|
print(' tabs:', tabs)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
15
uia_find_dc.py
Normal file
15
uia_find_dc.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import sys, time
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||||
|
from pywinauto import Application
|
||||||
|
|
||||||
|
app = Application(backend='uia').connect(process=int(sys.argv[1]), timeout=10)
|
||||||
|
win = app.top_window()
|
||||||
|
# look for DC-related text and combo boxes anywhere
|
||||||
|
texts = win.descendants(control_type='Text')
|
||||||
|
interesting = [t.window_text() for t in texts if t.window_text() and any(k in t.window_text() for k in ['FreeRun', 'DC', 'Synchron', 'EtherCAT', 'Sync'])]
|
||||||
|
print('interesting texts:', interesting[:40])
|
||||||
|
combos = win.descendants(control_type='ComboBox')
|
||||||
|
print('combos:', [c.window_text() for c in combos])
|
||||||
|
listitems = win.descendants(control_type='ListItem')
|
||||||
|
li = [l.window_text() for l in listitems if l.window_text()]
|
||||||
|
print('listitems:', li[:30])
|
||||||
44
uia_open_el6695.py
Normal file
44
uia_open_el6695.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import sys, time
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||||
|
from pywinauto import Application
|
||||||
|
from pywinauto.mouse import click, double_click
|
||||||
|
|
||||||
|
app = Application(backend='uia').connect(process=int(sys.argv[1]), timeout=10)
|
||||||
|
win = app.top_window()
|
||||||
|
tree = win.descendants(control_type='Tree')[0]
|
||||||
|
|
||||||
|
def expand_children(item):
|
||||||
|
try:
|
||||||
|
item.expand(); time.sleep(0.4)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return list(item.children())
|
||||||
|
|
||||||
|
nodes = []
|
||||||
|
for root in tree.children():
|
||||||
|
nodes.extend(expand_children(root))
|
||||||
|
for name in ['TwinCATProject1', 'I/O', '设备', '设备 1 (EtherCAT)', '模块 3 (EK1100)']:
|
||||||
|
nxt = None
|
||||||
|
for n in nodes:
|
||||||
|
try:
|
||||||
|
if n.window_text() == name:
|
||||||
|
nxt = n
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if nxt is None:
|
||||||
|
print('missing:', name); sys.exit(1)
|
||||||
|
nodes = expand_children(nxt)
|
||||||
|
target = None
|
||||||
|
for n in nodes:
|
||||||
|
if n.window_text() == '模块 10 (EL6695)':
|
||||||
|
target = n
|
||||||
|
break
|
||||||
|
if not target:
|
||||||
|
print('EL6695 not found'); sys.exit(1)
|
||||||
|
r = target.rectangle()
|
||||||
|
double_click(coords=(int((r.left + r.right) / 2), int((r.top + r.bottom) / 2)))
|
||||||
|
time.sleep(4)
|
||||||
|
win.set_focus()
|
||||||
|
tabs = [t.window_text() for t in win.descendants(control_type='TabItem')]
|
||||||
|
print('tabs:', tabs)
|
||||||
Loading…
Reference in New Issue
Block a user