feat(link): 外壳启动强制 open+motor/start,不必等电机页切 EtherCAT
DeviceLink Owner 在控制台加载后自动 bootstrapEcatBus:status→open→ motor/start,沿用 hub 网卡与 period/spin 持久化。成功后不重复 start; 掉线 10s 退避重开。有线 ECAT 与 WiFi 不冲突。
This commit is contained in:
@@ -48,6 +48,8 @@ can_monitor、motor、ecat_load、LAN9252 改为从 Hub 读端点/订在线布
|
||||
3. **端点配置只在 ConnectionHub**:IP(`dcu_esp_ip`)、网卡(`dcu_ecat_adapter`)读写集中。
|
||||
4. **命令仍页内发**,连接灯只认 DeviceLink 布尔(文案保留调用方语义)。
|
||||
5. BLE 为一等通道但单独适配:只广播状态,GATT 细节留在 `ble/*`。
|
||||
6. **外壳启动强制开总线**:Owner 首轮即 `status→open→motor/start`;成功后不再重复
|
||||
`motor/start`,掉线后 10s 退避重试。有线 EtherCAT 与 WiFi 不争网卡。
|
||||
|
||||
### ConnectionHub 客户端 API(`deviceLink`)
|
||||
|
||||
@@ -66,6 +68,7 @@ Owner 消息:`hello` / `snapshot` / `set-ip` / `ble-state`。
|
||||
| 位置 | 行为 |
|
||||
|------|------|
|
||||
| `tabs.html` | Owner |
|
||||
| Owner `bootstrapEcatBus` | 外壳启动即 open+motor/start,不必等电机页切 EtherCAT |
|
||||
| `connection.js` | 快照新鲜时覆盖 connected 布尔 |
|
||||
| `app.js` | IP/baseURL 走 `deviceLink`(保留 `#espIp` 输入回退) |
|
||||
| `can/dbc.js` + `can.js` | setDeviceIp 广播;顶栏灯订阅 hub |
|
||||
|
||||
@@ -23,6 +23,9 @@ var CH_NAME = "dcu-device-link";
|
||||
var ESP32_FAIL_ONLINE = 3; /* 连续失败 ≥3 判离线 */
|
||||
var ESP32_POLL_MS = 1000;
|
||||
var ECAT_POLL_MS = 2000;
|
||||
var ECAT_BUS_RETRY_MS = 10000; /* 开总线失败后的重试间隔 */
|
||||
var LS_ECAT_PERIOD = "dcu_ecat_period_ms";
|
||||
var LS_ECAT_SPIN = "dcu_ecat_spin";
|
||||
var LS_ECAT_ADAPTER = "dcu_ecat_adapter";
|
||||
var DEFAULT_ECAT_ADAPTER = "I210";
|
||||
|
||||
@@ -104,6 +107,11 @@ function createOwner() {
|
||||
var esp32Timer = null;
|
||||
var ecatTimer = null;
|
||||
var stopped = false;
|
||||
/* 外壳启动即强制拉起总线(open + motor/start),不必等电机页切 EtherCAT。
|
||||
* 成功后不再重复 motor/start;掉线后按 ECAT_BUS_RETRY_MS 退避重试。 */
|
||||
var ecatBusReady = false;
|
||||
var ecatBusBusy = false;
|
||||
var ecatBusNextTryAt = 0;
|
||||
|
||||
function publish() {
|
||||
snap.ts = nowMs();
|
||||
@@ -155,6 +163,50 @@ function createOwner() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开总线:status →(未开则)open 网卡 → motor/start 进 OP + 起 PDO 循环。
|
||||
* 与 motor/transport-ecat ensureOpen/ensureStarted 同源,由外壳 Owner 在
|
||||
* 控制台启动时强制执行,用户不必先去电机页把链路切到 EtherCAT。
|
||||
*/
|
||||
function bootstrapEcatBus() {
|
||||
if (stopped || ecatBusBusy) return;
|
||||
var now = nowMs();
|
||||
if (ecatBusReady) return;
|
||||
if (now < ecatBusNextTryAt) return;
|
||||
ecatBusBusy = true;
|
||||
ecatBusNextTryAt = now + ECAT_BUS_RETRY_MS;
|
||||
|
||||
var adapter = readAdapter();
|
||||
var body = {};
|
||||
try {
|
||||
var v = parseFloat(localStorage.getItem(LS_ECAT_PERIOD));
|
||||
if (v > 0) body.period_ms = v;
|
||||
var sp = localStorage.getItem(LS_ECAT_SPIN);
|
||||
if (sp === "0") body.spin = false;
|
||||
else if (sp === "1") body.spin = true;
|
||||
} catch (_) {}
|
||||
|
||||
ecatPost("status", {}, 6000).then(function (st) {
|
||||
var open = !!(st && st.ok);
|
||||
return open ? Promise.resolve(st)
|
||||
: ecatPost("open", { substr: adapter }, 20000);
|
||||
}).then(function (r) {
|
||||
if (r && r.ok === false) {
|
||||
throw new Error((r.log && r.log.join(" / ")) || "open 失败");
|
||||
}
|
||||
return ecatPost("motor/start", body, 30000);
|
||||
}).then(function (r) {
|
||||
if (r && r.ok === false) {
|
||||
throw new Error((r.log && r.log.join(" / ")) || "motor/start 失败");
|
||||
}
|
||||
ecatBusReady = true;
|
||||
}).catch(function () {
|
||||
/* 网线没插/从站未上电:退避后由 pollEcat 再触发 */
|
||||
}).then(function () {
|
||||
ecatBusBusy = false;
|
||||
});
|
||||
}
|
||||
|
||||
function pollEcat() {
|
||||
if (stopped) return;
|
||||
var stP = ecatPost("status", {}, 4000).catch(function () { return null; });
|
||||
@@ -164,7 +216,7 @@ function createOwner() {
|
||||
var st = arr[0], ld = arr[1];
|
||||
var e = snap.links.ecat;
|
||||
e.online = !!(st && st.ok && st.slave);
|
||||
e.busOpen = e.online;
|
||||
e.busOpen = !!(st && st.ok);
|
||||
/* native status 不直接给 op/loop;用 load valid + slave state 粗判 */
|
||||
e.op = !!(st && st.slave && st.slave.state != null && (st.slave.state & 0x0F) === 8);
|
||||
e.loadValid = !!(ld && ld.ok && ld.valid === true
|
||||
@@ -172,7 +224,10 @@ function createOwner() {
|
||||
e.loop = e.loadValid || e.op;
|
||||
e.loadPerm = (ld && Number.isFinite(ld.load_perm)) ? ld.load_perm : 0;
|
||||
e.ageMs = (ld && Number.isFinite(ld.age_ms)) ? ld.age_ms : -1;
|
||||
if (e.busOpen && e.loop) ecatBusReady = true;
|
||||
else if (ecatBusReady && !e.busOpen) ecatBusReady = false; /* 总线掉了 → 允许重开 */
|
||||
publish();
|
||||
bootstrapEcatBus();
|
||||
}).catch(function () {
|
||||
/* 字段访问/异常不得让 ECAT 轮询永久停摆 */
|
||||
}).then(function () {
|
||||
@@ -208,6 +263,7 @@ function createOwner() {
|
||||
if (ch) ch.addEventListener("message", onMsg);
|
||||
pollEsp32();
|
||||
pollEcat();
|
||||
bootstrapEcatBus();
|
||||
publish();
|
||||
|
||||
return {
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ function setTitle(node, title) {
|
||||
}
|
||||
|
||||
var TIP_BASE = 'DCU_APP CPU 负载率:中断+非延迟处理(任务派发/OTA中继)占墙钟比,不含常开轮询与空转;EtherCAT 输入 PDO 段5,1s 窗口';
|
||||
var TIP_OFF = TIP_BASE + ';当前不可用:需要 ECAT 总线已打开且过程数据循环在跑'
|
||||
+ '(如打开电机控制页选 EtherCAT 传输),且 DCU_APP 固件 >= 1.5.61。';
|
||||
var TIP_OFF = TIP_BASE + ';当前不可用:控制台外壳启动时会自动 open+motor/start;'
|
||||
+ '若仍为 --,检查网线/从站供电,或手动打开电机控制页选 EtherCAT。';
|
||||
|
||||
/** @description 把 /api/app/load 的 JSON 渲染到 #mGd32Load。返回值是否有效(驱动退避)。 */
|
||||
function render(d) {
|
||||
|
||||
@@ -181,6 +181,6 @@
|
||||
<script src="js/common/app-lease.js"></script>
|
||||
<!-- DeviceLink Owner:全链路健康轮询只在本外壳跑一次,各 iframe 订阅 -->
|
||||
<script>window.__dcuDeviceLinkOwner = true;</script>
|
||||
<script type="module" src="js/common/device-link.js?v=20260916a"></script>
|
||||
<script type="module" src="js/common/device-link.js?v=20260916c"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user