infinicode 2.8.107 → 2.8.113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/kernel/federation/dashboard-html.d.ts +1 -1
- package/dist/kernel/federation/dashboard-html.js +410 -157
- package/dist/kernel/mcp/mcp-server.js +61 -0
- package/dist/robopark/python-env.d.ts +1 -0
- package/dist/robopark/python-env.js +3 -0
- package/dist/robopark/robot-runtime.d.ts +1 -0
- package/dist/robopark/robot-runtime.js +168 -8
- package/dist/robopark/setup.d.ts +1 -0
- package/dist/robopark/setup.js +2 -0
- package/dist/robopark-cli.js +4 -0
- package/docs/ROBOPARK_ROBOVOICE_SIMPLIFICATION_PLAN.html +139 -0
- package/package.json +5 -2
- package/packages/robopark/pi-client/motor_bridge.py +10 -7
- package/packages/robopark/scheduler/main.py +144 -19
- package/packages/robopark/scheduler/robot_supervisor.py +46 -15
- package/packages/robopark/vision/motor_server.py +44 -18
- package/scripts/integrate-robovoice-control-center.mjs +423 -0
|
@@ -230,14 +230,42 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
230
230
|
.offline{background:var(--bad-bg);color:var(--bad);text-align:center;font-family:var(--mono);font-size:0.72rem;padding:0.4rem;display:none;
|
|
231
231
|
border-bottom:1px solid var(--glass-border);}
|
|
232
232
|
|
|
233
|
-
/*
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.
|
|
238
|
-
|
|
239
|
-
.
|
|
240
|
-
.
|
|
233
|
+
/* global gradient-glass navigation */
|
|
234
|
+
:root{--global-nav-width:264px}
|
|
235
|
+
body{padding-left:var(--global-nav-width);transition:padding-left .24s cubic-bezier(.2,.8,.2,1)}
|
|
236
|
+
body.global-nav-collapsed{--global-nav-width:78px}
|
|
237
|
+
.global-sidebar{position:fixed;inset:0 auto 0 0;z-index:48;width:var(--global-nav-width);height:100vh;max-height:100vh;display:flex;flex-direction:column;align-self:auto;overflow:hidden;
|
|
238
|
+
background:radial-gradient(240px 260px at 22% 0,rgba(255,211,112,.18),transparent 72%),radial-gradient(260px 340px at 100% 88%,rgba(126,79,8,.2),transparent 70%),linear-gradient(165deg,rgba(24,20,13,.93),rgba(7,7,6,.96));
|
|
239
|
+
-webkit-backdrop-filter:blur(28px) saturate(1.35);backdrop-filter:blur(28px) saturate(1.35);border-right:1px solid rgba(255,222,143,.18);box-shadow:22px 0 70px rgba(0,0,0,.38),inset -1px 0 rgba(255,240,200,.035);transition:width .24s cubic-bezier(.2,.8,.2,1),transform .24s cubic-bezier(.2,.8,.2,1)}
|
|
240
|
+
.global-sidebar:before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.22;background-image:linear-gradient(rgba(255,224,151,.04) 1px,transparent 1px),linear-gradient(90deg,rgba(255,224,151,.03) 1px,transparent 1px);background-size:28px 28px;mask-image:linear-gradient(to bottom,black,transparent 88%)}
|
|
241
|
+
.global-side-head{position:relative;display:flex;align-items:center;gap:.72rem;min-height:76px;padding:.78rem;border-bottom:1px solid var(--glass-border)}
|
|
242
|
+
.global-side-mark{display:grid;place-items:center;flex:0 0 46px;height:46px;border-radius:15px;font-family:var(--display);font-size:1rem;font-weight:800;color:#171006;background:linear-gradient(145deg,#fff0b5,var(--gold-bright) 48%,#a66f15);box-shadow:0 10px 28px rgba(224,168,55,.25),inset 0 1px rgba(255,255,255,.7)}
|
|
243
|
+
.global-side-brand{min-width:0;overflow:hidden;white-space:nowrap}.global-side-brand b{display:block;font-family:var(--display);font-size:.92rem;color:var(--gold-pale)}.global-side-brand span{display:block;margin-top:.1rem;font-family:var(--mono);font-size:.52rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
|
|
244
|
+
.global-nav-collapse{position:absolute;right:-1px;bottom:-13px;display:grid;place-items:center;width:28px;height:28px;border-radius:9px 0 0 9px;border:1px solid var(--border-strong);border-right:0;background:#171209;color:var(--gold-bright);font-family:var(--mono);font-size:.58rem;cursor:pointer;z-index:2}
|
|
245
|
+
.global-nav{position:relative;flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:1rem .65rem 6.2rem;scrollbar-width:thin;scrollbar-color:rgba(232,190,92,.3) transparent}
|
|
246
|
+
.global-nav-group{margin:.2rem .45rem .48rem;font-family:var(--mono);font-size:.49rem;letter-spacing:.14em;text-transform:uppercase;color:#7f735e;white-space:nowrap;overflow:hidden}.global-nav-group:not(:first-child){margin-top:1.1rem}
|
|
247
|
+
.global-nav-item,.global-nav-sub{position:relative;width:100%;display:flex;align-items:center;gap:.7rem;min-height:46px;margin:.16rem 0;padding:.48rem .58rem;border:1px solid transparent;border-radius:13px;background:transparent;color:var(--muted);cursor:pointer;text-align:left;transition:background .15s,border-color .15s,color .15s,transform .15s;overflow:hidden}
|
|
248
|
+
.global-nav-sub{min-height:39px;padding:.38rem .58rem .38rem 1rem}
|
|
249
|
+
.global-nav-item:hover,.global-nav-sub:hover{color:var(--ink);background:rgba(255,236,186,.055);border-color:rgba(255,224,151,.09);transform:translateX(2px)}
|
|
250
|
+
.global-nav-item.active,.global-nav-sub.active{color:var(--gold-pale);border-color:rgba(246,215,123,.28);background:linear-gradient(100deg,rgba(232,190,92,.17),rgba(232,190,92,.035));box-shadow:inset 3px 0 0 var(--gold-bright),0 8px 28px rgba(0,0,0,.16)}
|
|
251
|
+
.global-nav-item.active:after,.global-nav-sub.active:after{content:"";position:absolute;right:10px;width:5px;height:5px;border-radius:50%;background:var(--gold-bright);box-shadow:0 0 10px var(--gold-bright)}
|
|
252
|
+
.global-sidebar:not([data-active-tab="robopark"]) .global-nav-sub.active{color:var(--muted);border-color:transparent;background:transparent;box-shadow:none}.global-sidebar:not([data-active-tab="robopark"]) .global-nav-sub.active:after{display:none}
|
|
253
|
+
.global-nav-icon{display:grid;place-items:center;flex:0 0 34px;height:34px;border-radius:10px;border:1px solid rgba(255,224,151,.12);background:linear-gradient(145deg,rgba(255,239,195,.09),rgba(0,0,0,.12));font-family:var(--mono);font-size:.56rem;font-weight:800;letter-spacing:.03em;color:var(--gold-bright);box-shadow:inset 0 1px rgba(255,255,255,.04)}
|
|
254
|
+
.global-nav-sub .global-nav-icon{flex-basis:28px;height:28px;border-radius:8px;font-size:.49rem}.global-nav-copy{min-width:0;display:block;white-space:nowrap}.global-nav-copy b{display:block;font-size:.69rem;font-weight:700;color:inherit}.global-nav-copy small{display:block;margin-top:.05rem;font-size:.52rem;color:#8f846f}
|
|
255
|
+
.global-side-foot{position:absolute;left:.65rem;right:.65rem;bottom:.7rem;display:flex;align-items:center;gap:.55rem;min-height:48px;padding:.58rem;border:1px solid rgba(255,224,151,.1);border-radius:13px;background:rgba(3,3,2,.48);box-shadow:inset 0 1px rgba(255,255,255,.025)}
|
|
256
|
+
.global-side-foot i{flex:0 0 8px;height:8px;border-radius:50%;background:var(--ok);box-shadow:0 0 12px var(--ok)}.global-side-foot span{white-space:nowrap;overflow:hidden;font-family:var(--mono);font-size:.54rem;color:var(--muted)}
|
|
257
|
+
body.global-nav-collapsed .global-side-brand,body.global-nav-collapsed .global-nav-group,body.global-nav-collapsed .global-nav-copy,body.global-nav-collapsed .global-side-foot span{opacity:0;width:0;pointer-events:none}
|
|
258
|
+
body.global-nav-collapsed .global-side-head{padding-left:1rem}body.global-nav-collapsed .global-nav{padding-left:.65rem;padding-right:.65rem}body.global-nav-collapsed .global-nav-item,body.global-nav-collapsed .global-nav-sub{justify-content:center;padding:.42rem}body.global-nav-collapsed .global-nav-item.active:after,body.global-nav-collapsed .global-nav-sub.active:after{right:5px}body.global-nav-collapsed .global-side-foot{justify-content:center}
|
|
259
|
+
.global-nav-mobile{display:none;width:38px;height:38px;border-radius:11px;border:1px solid var(--glass-border);background:var(--panel-2);color:var(--gold-bright);font-family:var(--mono);font-size:.72rem;cursor:pointer}.global-nav-backdrop{display:none;position:fixed;inset:0;z-index:47;background:rgba(0,0,0,.62);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}
|
|
260
|
+
footer.cmd{left:var(--global-nav-width);transition:left .24s cubic-bezier(.2,.8,.2,1)}
|
|
261
|
+
#out{max-width:min(560px,calc(100vw - var(--global-nav-width) - 2rem))}
|
|
262
|
+
/* The sidebar owns Control Center navigation; legacy buttons remain for routing and accessibility tests. */
|
|
263
|
+
.rp-subtabs{display:none!important}
|
|
264
|
+
.pane{display:none;} .pane.active{display:block;animation:globalPaneIn .22s ease-out}
|
|
265
|
+
@keyframes globalPaneIn{from{opacity:.35;transform:translateY(4px)}to{opacity:1;transform:none}}
|
|
266
|
+
@media(max-width:760px){
|
|
267
|
+
:root,body.global-nav-collapsed{--global-nav-width:0px}body{padding-left:0}.global-sidebar{width:min(300px,86vw);transform:translateX(-105%);box-shadow:30px 0 90px rgba(0,0,0,.62)}body.global-nav-open .global-sidebar{transform:translateX(0)}body.global-nav-open .global-nav-backdrop{display:block}.global-nav-collapse{display:none}.global-nav-mobile{display:grid;place-items:center}body.global-nav-collapsed .global-side-brand,body.global-nav-collapsed .global-nav-group,body.global-nav-collapsed .global-nav-copy,body.global-nav-collapsed .global-side-foot span{opacity:1;width:auto;pointer-events:auto}body.global-nav-collapsed .global-nav-item,body.global-nav-collapsed .global-nav-sub{justify-content:flex-start;padding:.48rem .58rem}body.global-nav-collapsed .global-nav-sub{padding-left:1rem}footer.cmd{left:0}#out{max-width:90vw}
|
|
268
|
+
}
|
|
241
269
|
.rp-head{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:0.6rem;padding:1rem 1.1rem 0.2rem;}
|
|
242
270
|
.rp-totals{display:flex;gap:0.5rem;flex-wrap:wrap;}
|
|
243
271
|
.rp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:0.75rem;padding:0.8rem 1.1rem;}
|
|
@@ -706,12 +734,18 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
706
734
|
|
|
707
735
|
/* command sheet */
|
|
708
736
|
.rp-command-shell{display:grid;gap:1rem}.rp-command-env{display:grid;grid-template-columns:repeat(3,minmax(170px,1fr));gap:.65rem;padding:.85rem;border:1px solid var(--glass-border);border-radius:16px;background:var(--panel-2)}
|
|
737
|
+
.rp-minute{display:grid;gap:1rem;padding:1rem;border:1px solid rgba(232,190,92,.34);border-radius:20px;background:radial-gradient(circle at 90% 0,rgba(232,190,92,.13),transparent 38%),linear-gradient(145deg,rgba(29,24,15,.88),rgba(7,7,6,.94));box-shadow:0 22px 65px rgba(0,0,0,.34),inset 0 1px 0 rgba(255,239,190,.11)}
|
|
738
|
+
.rp-minute-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.rp-minute-head h4{font-family:var(--display);font-size:1.15rem;color:var(--gold-pale);margin:.15rem 0}.rp-minute-head p{font-size:.68rem;color:var(--muted);line-height:1.55;max-width:680px}.rp-minute-badge{font-family:var(--mono);font-size:.58rem;color:#0a0905;background:linear-gradient(135deg,var(--gold-bright),#fff0ad);border-radius:100px;padding:.35rem .65rem;white-space:nowrap;font-weight:800}
|
|
739
|
+
.rp-minute-fields{display:grid;grid-template-columns:repeat(4,minmax(150px,1fr));gap:.65rem}.rp-minute-field{position:relative;padding:.72rem;border:1px solid var(--glass-border);border-radius:14px;background:rgba(4,4,3,.42)}.rp-minute-field .step{display:block;font-family:var(--mono);font-size:.53rem;color:var(--gold-bright);text-transform:uppercase;letter-spacing:.08em;margin-bottom:.35rem}.rp-minute-field label{font-size:.64rem;color:var(--text);display:block;margin-bottom:.35rem}.rp-minute-field small{display:block;color:var(--muted);font-size:.54rem;line-height:1.4;margin-top:.35rem}
|
|
740
|
+
.rp-minute-command{display:grid;grid-template-columns:1fr auto;gap:.7rem;align-items:stretch}.rp-minute-command .rp-command-code{min-height:112px}.rp-minute-copy{min-width:180px;font-size:.76rem!important;font-weight:800}.rp-minute-status{font-family:var(--mono);font-size:.61rem;color:var(--muted)}.rp-minute-status.ok{color:var(--ok)}.rp-minute-status.bad{color:var(--bad)}
|
|
741
|
+
.rp-minute-run{display:grid;grid-template-columns:repeat(3,1fr);gap:.55rem}.rp-minute-run div{display:flex;gap:.5rem;align-items:flex-start;padding:.62rem;border-radius:12px;background:rgba(255,255,255,.025);font-size:.61rem;color:var(--muted);line-height:1.45}.rp-minute-run b{display:grid;place-items:center;flex:0 0 22px;height:22px;border-radius:50%;background:rgba(232,190,92,.15);color:var(--gold-bright);font-family:var(--mono)}
|
|
742
|
+
.rp-command-advanced{border:1px solid var(--glass-border);border-radius:16px;background:rgba(7,7,6,.46);overflow:hidden}.rp-command-advanced>summary{cursor:pointer;padding:.85rem 1rem;color:var(--gold-pale);font-family:var(--mono);font-size:.69rem;list-style:none}.rp-command-advanced>summary:after{content:' +';float:right;color:var(--gold-bright)}.rp-command-advanced[open]>summary:after{content:' -'}.rp-command-advanced-body{display:grid;gap:.85rem;padding:0 1rem 1rem}
|
|
709
743
|
.rp-command-toolbar{display:flex;gap:.55rem;align-items:center;flex-wrap:wrap}.rp-command-toolbar input{flex:1;min-width:220px}.rp-command-toolbar select{width:auto;min-width:170px}
|
|
710
744
|
.rp-command-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(330px,1fr));gap:.75rem}.rp-command-card{display:flex;flex-direction:column;gap:.55rem;min-width:0;padding:.85rem;border:1px solid var(--glass-border);border-radius:16px;background:linear-gradient(145deg,rgba(31,26,18,.66),rgba(9,8,6,.76));box-shadow:inset 0 1px 0 var(--glass-hi),0 12px 32px rgba(0,0,0,.22)}
|
|
711
745
|
.rp-command-head{display:flex;align-items:flex-start;justify-content:space-between;gap:.6rem}.rp-command-head b{font-family:var(--mono);font-size:.74rem;color:var(--gold-pale)}.rp-command-tag{font-family:var(--mono);font-size:.52rem;text-transform:uppercase;letter-spacing:.06em;color:var(--gold-bright);border:1px solid var(--border-strong);border-radius:100px;padding:.15rem .42rem;white-space:nowrap}.rp-command-card p{font-size:.64rem;line-height:1.5;color:var(--muted);min-height:2.9em}
|
|
712
746
|
.rp-command-code{position:relative;min-height:86px;padding:.68rem .72rem;border:1px solid rgba(255,224,151,.1);border-radius:11px;background:#050504;overflow:auto}.rp-command-code code{display:block;font-family:var(--mono);font-size:.59rem;line-height:1.55;color:#f2dfab;white-space:pre-wrap;overflow-wrap:anywhere}.rp-command-actions{display:flex;gap:.4rem;align-items:center;margin-top:auto}.rp-command-actions .rp-btn:first-child{flex:1}.rp-command-empty{grid-column:1/-1;padding:2rem;text-align:center;color:var(--muted);border:1px dashed var(--glass-border);border-radius:16px}
|
|
713
747
|
.rp-command-custom{display:grid;grid-template-columns:1fr 1fr;gap:.65rem;padding:.9rem;border:1px solid var(--glass-border);border-radius:16px;background:var(--panel-2)}.rp-command-custom h4,.rp-command-custom .wide{grid-column:1/-1}.rp-command-custom h4{font-family:var(--mono);font-size:.72rem;color:var(--gold-bright)}.rp-command-custom textarea{min-height:92px;resize:vertical}.rp-command-note{font-size:.61rem;color:var(--muted)}
|
|
714
|
-
@media(max-width:
|
|
748
|
+
@media(max-width:900px){.rp-minute-fields{grid-template-columns:1fr 1fr}.rp-minute-run{grid-template-columns:1fr}.rp-command-env{grid-template-columns:1fr 1fr}.rp-command-grid{grid-template-columns:1fr}}@media(max-width:540px){.rp-minute-head{display:grid}.rp-minute-fields,.rp-command-env,.rp-command-custom{grid-template-columns:1fr}.rp-minute-command{grid-template-columns:1fr}.rp-minute-copy{min-height:52px}.rp-command-custom h4,.rp-command-custom .wide{grid-column:auto}}
|
|
715
749
|
|
|
716
750
|
.rp-fleet-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:0.7rem;padding:0;}
|
|
717
751
|
.rp-fleet-card{position:relative;}
|
|
@@ -732,9 +766,42 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
732
766
|
.rp-input-grow{flex:1;min-width:140px;}
|
|
733
767
|
</style>
|
|
734
768
|
</head>
|
|
735
|
-
<body>
|
|
769
|
+
<body>
|
|
770
|
+
<aside class="global-sidebar" id="global-sidebar" aria-label="Global navigation">
|
|
771
|
+
<div class="global-side-head">
|
|
772
|
+
<div class="global-side-mark">RP</div>
|
|
773
|
+
<div class="global-side-brand"><b>RoboPark</b><span>Control center</span></div>
|
|
774
|
+
<button class="global-nav-collapse" id="global-nav-toggle" type="button" aria-label="Collapse navigation" aria-expanded="true"><<</button>
|
|
775
|
+
</div>
|
|
776
|
+
<nav class="global-nav">
|
|
777
|
+
<div class="global-nav-group">Fleet operations</div>
|
|
778
|
+
<button class="tab global-nav-item active" data-tab="fleet" id="tabbtn-fleet" type="button" title="Fleet intelligence">
|
|
779
|
+
<span class="global-nav-icon">FL</span><span class="global-nav-copy"><b>Fleet</b><small>Readiness and workload</small></span>
|
|
780
|
+
</button>
|
|
781
|
+
<button class="tab global-nav-item" data-tab="park" id="tabbtn-park" type="button" title="Park map">
|
|
782
|
+
<span class="global-nav-icon">PK</span><span class="global-nav-copy"><b>Park</b><small>Live map and camera ops</small></span>
|
|
783
|
+
</button>
|
|
784
|
+
<div class="global-nav-group">Control center</div>
|
|
785
|
+
<button class="tab global-nav-item" data-tab="robopark" id="tabbtn-robopark" type="button" title="Sessions and telemetry">
|
|
786
|
+
<span class="global-nav-icon">CC</span><span class="global-nav-copy"><b>Control center</b><small>Production oversight</small></span>
|
|
787
|
+
</button>
|
|
788
|
+
<button class="global-nav-sub" data-global-sub="overview" type="button" title="Overview"><span class="global-nav-icon">OV</span><span class="global-nav-copy"><b>Overview</b><small>Pipeline health</small></span></button>
|
|
789
|
+
<button class="global-nav-sub" data-global-sub="incidents" type="button" title="Incidents"><span class="global-nav-icon">IN</span><span class="global-nav-copy"><b>Incidents</b><small>Failures and blockers</small></span></button>
|
|
790
|
+
<button class="global-nav-sub" data-global-sub="live" type="button" title="Live sessions"><span class="global-nav-icon">LS</span><span class="global-nav-copy"><b>Live sessions</b><small>Active conversations</small></span></button>
|
|
791
|
+
<button class="global-nav-sub" data-global-sub="voice" type="button" title="Voice calls"><span class="global-nav-icon">VC</span><span class="global-nav-copy"><b>Voice calls</b><small>Operator voice studio</small></span></button>
|
|
792
|
+
<button class="global-nav-sub" data-global-sub="history" type="button" title="History"><span class="global-nav-icon">HI</span><span class="global-nav-copy"><b>History</b><small>Sessions and transcripts</small></span></button>
|
|
793
|
+
<button class="global-nav-sub" data-global-sub="audit" type="button" title="Audit log"><span class="global-nav-icon">AU</span><span class="global-nav-copy"><b>Audit log</b><small>Operator activity</small></span></button>
|
|
794
|
+
<button class="global-nav-sub" data-global-sub="fleet" type="button" title="Fleet analytics"><span class="global-nav-icon">FA</span><span class="global-nav-copy"><b>Fleet analytics</b><small>Robot telemetry</small></span></button>
|
|
795
|
+
<button class="global-nav-sub" data-global-sub="config" type="button" title="Characters and voice"><span class="global-nav-icon">CH</span><span class="global-nav-copy"><b>Characters</b><small>Voice stacks and prompts</small></span></button>
|
|
796
|
+
<button class="global-nav-sub" data-global-sub="commands" type="button" title="Command sheet"><span class="global-nav-icon">CM</span><span class="global-nav-copy"><b>Command sheet</b><small>Setup and recovery</small></span></button>
|
|
797
|
+
<button class="global-nav-sub" data-global-sub="setup" type="button" title="Setup"><span class="global-nav-icon">ST</span><span class="global-nav-copy"><b>Setup</b><small>Robots and servers</small></span></button>
|
|
798
|
+
</nav>
|
|
799
|
+
<div class="global-side-foot"><i></i><span>Live fleet navigation</span></div>
|
|
800
|
+
</aside>
|
|
801
|
+
<div class="global-nav-backdrop" id="global-nav-backdrop"></div>
|
|
736
802
|
<div class="offline" id="offline">connection lost — retrying</div>
|
|
737
|
-
<header class="bar">
|
|
803
|
+
<header class="bar">
|
|
804
|
+
<button class="global-nav-mobile" id="global-nav-mobile" type="button" aria-label="Open navigation" aria-expanded="false">NAV</button>
|
|
738
805
|
<div class="brand">
|
|
739
806
|
<span class="livedot" id="livedot"></span>
|
|
740
807
|
<div>
|
|
@@ -747,12 +814,6 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
747
814
|
<span class="rp-operator-badge set" id="rp-operator-badge" role="button" tabindex="0">set operator</span>
|
|
748
815
|
</header>
|
|
749
816
|
|
|
750
|
-
<div class="tabs">
|
|
751
|
-
<button class="tab active" data-tab="fleet" id="tabbtn-fleet">Fleet</button>
|
|
752
|
-
<button class="tab" data-tab="park" id="tabbtn-park">Park</button>
|
|
753
|
-
<button class="tab" data-tab="robopark" id="tabbtn-robopark">Sessions & Telemetry</button>
|
|
754
|
-
</div>
|
|
755
|
-
|
|
756
817
|
<div class="pane active" id="pane-fleet">
|
|
757
818
|
<div class="wrap">
|
|
758
819
|
<section>
|
|
@@ -825,6 +886,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
825
886
|
<div class="section"><h3>Live insights</h3><div class="pk-camera-insight" id="pk-camera-ops-insights"></div></div>
|
|
826
887
|
<div class="section"><h3>Conversation pipeline</h3><div class="pk-camera-pipeline" id="pk-camera-ops-pipeline"></div></div>
|
|
827
888
|
<div class="section"><h3>Hardware and media</h3><div class="pk-camera-insight" id="pk-camera-ops-hardware"></div></div>
|
|
889
|
+
<div class="section"><h3>Motor operations</h3><div class="rp-field"><label>Saved sequence</label><select id="pk-camera-ops-motor-sequence"></select></div><div class="pk-ctl"><button class="rp-btn" id="pk-camera-ops-motor-discover">Discover relays</button><button class="rp-btn primary" id="pk-camera-ops-motor-run">Run sequence</button><button class="rp-btn danger" id="pk-camera-ops-motor-stop">STOP ALL</button><button class="rp-btn danger" id="pk-camera-ops-motor-test">Test registered GPIO</button></div><div class="pk-motor-status" id="pk-camera-ops-motor-status">Loading robot motor profile...</div></div>
|
|
828
890
|
<div class="section"><h3>Operator note</h3><div class="pk-note">Video is relayed directly from the robot's RoboVision MJPEG service through the authenticated mesh. Voice remains on LiveKit, avoiding camera ownership conflicts.</div></div>
|
|
829
891
|
</aside>
|
|
830
892
|
</div>
|
|
@@ -1171,31 +1233,49 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1171
1233
|
<div class="rp-sub" id="rp-sub-commands">
|
|
1172
1234
|
<div class="rp-command-shell">
|
|
1173
1235
|
<div class="rp-config-hero">
|
|
1174
|
-
<div><h3>RoboPark
|
|
1175
|
-
<div class="rp-config-hero-actions"><button class="rp-btn" id="rp-command-reset">Reset
|
|
1236
|
+
<div><h3>RoboPark Easy Setup</h3><p>Set up or repair a complete robot in about one minute. Choose three things, copy one command, and paste it on the robot.</p></div>
|
|
1237
|
+
<div class="rp-config-hero-actions"><button class="rp-btn" id="rp-command-reset">Reset setup</button></div>
|
|
1176
1238
|
</div>
|
|
1177
|
-
<div class="rp-
|
|
1178
|
-
<div class="rp-
|
|
1179
|
-
<div class="rp-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1239
|
+
<div class="rp-minute">
|
|
1240
|
+
<div class="rp-minute-head"><div><div class="eyebrow">Guided robot setup</div><h4>One command. Complete production runtime.</h4><p>The command installs RoboPark, removes the old supervised process safely, and starts camera, microphone, speaker, motion detection, motors, RoboVoice, and remote management.</p></div><span class="rp-minute-badge">ABOUT 1 MINUTE</span></div>
|
|
1241
|
+
<div class="rp-minute-fields">
|
|
1242
|
+
<div class="rp-minute-field"><span class="step">Step 1</span><label>Robot name</label><input id="rp-command-robot" value="bmw" spellcheck="false" placeholder="bmw"><small>Use the permanent physical robot name.</small></div>
|
|
1243
|
+
<div class="rp-minute-field"><span class="step">Step 2</span><label>Character</label><select id="rp-command-character"><option value="vixen">VIXEN</option></select><small>Only real character presets appear here.</small></div>
|
|
1244
|
+
<div class="rp-minute-field"><span class="step">Step 3</span><label>Connection</label><select id="rp-command-network"><option value="lan">Same LAN as livekit-1</option><option value="tailscale">Tailscale remote</option></select><small>Choose LAN when the robot is onsite.</small></div>
|
|
1245
|
+
<div class="rp-minute-field"><span class="step">Optional</span><label>Site</label><input id="rp-command-site" value="Tel Aviv"><small>Shown in fleet and session history.</small></div>
|
|
1246
|
+
</div>
|
|
1247
|
+
<div class="rp-minute-command"><pre class="rp-command-code"><code id="rp-minute-command"></code></pre><button class="rp-btn primary rp-minute-copy" id="rp-minute-copy">Copy setup command</button></div>
|
|
1248
|
+
<div class="rp-minute-status" id="rp-minute-status">Checking setup...</div>
|
|
1249
|
+
<div class="rp-minute-run"><div><b>1</b><span>Open Terminal on the Raspberry Pi.</span></div><div><b>2</b><span>Paste the copied command and press Enter.</span></div><div><b>3</b><span>Wait for <strong>enabled and started systemd unit</strong>. The robot then appears in Fleet.</span></div></div>
|
|
1184
1250
|
</div>
|
|
1185
|
-
<
|
|
1186
|
-
<
|
|
1187
|
-
<
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1251
|
+
<details class="rp-command-advanced">
|
|
1252
|
+
<summary>Advanced tools, addresses, diagnostics, and recovery</summary>
|
|
1253
|
+
<div class="rp-command-advanced-body">
|
|
1254
|
+
<div class="rp-command-env">
|
|
1255
|
+
<div class="rp-field"><label>livekit-1 LAN IP</label><input id="rp-command-lan" value="192.168.0.9" spellcheck="false"></div>
|
|
1256
|
+
<div class="rp-field"><label>livekit-1 Tailscale IP / DNS</label><input id="rp-command-tail" value="100.84.147.24" spellcheck="false"></div>
|
|
1257
|
+
<div class="rp-field"><label>Canonical device ID</label><input id="rp-command-device" placeholder="optional: dev_0422e545" spellcheck="false"></div>
|
|
1258
|
+
<div class="rp-field"><label>Enrollment token</label><input id="rp-command-enrollment" placeholder="optional" spellcheck="false"></div>
|
|
1259
|
+
<div class="rp-field"><label>Control token</label><input id="rp-command-token" type="password" spellcheck="false"></div>
|
|
1260
|
+
<div class="rp-field"><label>RoboVoice folder on livekit-1</label><input id="rp-command-robovoice" value="C:\ROBOVOICE-main" spellcheck="false"></div>
|
|
1261
|
+
<div class="rp-field"><label>Release</label><input id="rp-command-release" value="2.8.113" spellcheck="false"></div>
|
|
1262
|
+
</div>
|
|
1263
|
+
<div class="rp-command-toolbar">
|
|
1264
|
+
<input type="search" id="rp-command-search" placeholder="Search logs, camera, Docker, motors, npm...">
|
|
1265
|
+
<select id="rp-command-category"><option value="">All categories</option><option>Provision</option><option>Production</option><option>Voice</option><option>Health</option><option>Recovery</option><option>Media</option><option>Motors</option><option>Packages</option><option>Custom</option></select>
|
|
1266
|
+
<button class="rp-btn" id="rp-command-copy-all">Copy visible commands</button><span class="rp-command-note" id="rp-command-count"></span>
|
|
1267
|
+
</div>
|
|
1268
|
+
<div class="rp-command-grid" id="rp-command-grid"></div>
|
|
1269
|
+
<form class="rp-command-custom" id="rp-command-custom-form">
|
|
1270
|
+
<h4>Add a persistent command</h4>
|
|
1271
|
+
<div class="rp-field"><label>Title</label><input id="rp-command-custom-title" required placeholder="Restart camera service"></div>
|
|
1272
|
+
<div class="rp-field"><label>Category</label><input id="rp-command-custom-category" value="Custom" placeholder="Custom"></div>
|
|
1273
|
+
<div class="rp-field wide"><label>Simple explanation</label><input id="rp-command-custom-description" placeholder="When to use this and what success looks like"></div>
|
|
1274
|
+
<div class="rp-field wide"><label>Command</label><textarea id="rp-command-custom-command" required spellcheck="false" placeholder="Paste a safe command or one-liner"></textarea></div>
|
|
1275
|
+
<div class="wide"><button class="rp-btn primary" type="submit">Save command to this browser</button> <span class="rp-command-note">Saved only in this operator browser.</span></div>
|
|
1276
|
+
</form>
|
|
1277
|
+
</div>
|
|
1278
|
+
</details>
|
|
1199
1279
|
</div>
|
|
1200
1280
|
</div>
|
|
1201
1281
|
|
|
@@ -1263,9 +1343,25 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1263
1343
|
var lastSchedulerPollAt = 0; // updated by pollRobopark() on every successful /api/telemetry fetch
|
|
1264
1344
|
var lastFedStatusAt = 0; // updated by poll() on every successful /fed/status fetch
|
|
1265
1345
|
var lastStatus = null; // most recent /fed/status snapshot (shared with Park)
|
|
1266
|
-
var roboparkByName = {}; // robot_id -> latest telemetry row (shared with Park drawer)
|
|
1346
|
+
var roboparkByName = {}; // robot_id -> latest telemetry row (shared with Park drawer)
|
|
1267
1347
|
var schedulerDevicesById = {}; // enrolled device rows + camera/audio inventory
|
|
1268
1348
|
var schedulerDevicesByName = {}; // same rows, keyed by device name (id != name, e.g. dev_8984a80e vs jaguar2)
|
|
1349
|
+
function indexRoboparkTelemetry(rows){
|
|
1350
|
+
var indexed={};
|
|
1351
|
+
(Array.isArray(rows)?rows:[]).forEach(function(row){
|
|
1352
|
+
var id=String(row.robot_id||row.id||'').trim().toLowerCase();
|
|
1353
|
+
var name=String(row.name||'').trim().toLowerCase();
|
|
1354
|
+
if(id)indexed[id]=row;
|
|
1355
|
+
if(name)indexed[name]=row;
|
|
1356
|
+
});
|
|
1357
|
+
roboparkByName=indexed;
|
|
1358
|
+
return indexed;
|
|
1359
|
+
}
|
|
1360
|
+
function schedulerDeviceByReference(reference){
|
|
1361
|
+
var key=String(reference||'').trim().toLowerCase();
|
|
1362
|
+
if(!key)return null;
|
|
1363
|
+
return schedulerDevicesById[reference]||schedulerDevicesById[key]||schedulerDevicesByName[key]||null;
|
|
1364
|
+
}
|
|
1269
1365
|
function schedulerDeviceScore(device){
|
|
1270
1366
|
if(!device) return -1;
|
|
1271
1367
|
var status=String(device.status||'').trim().toLowerCase();
|
|
@@ -1772,6 +1868,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1772
1868
|
if(!id) return;
|
|
1773
1869
|
var name = row.name || id;
|
|
1774
1870
|
var device = deviceById[id] || deviceByName[norm(name)] || {};
|
|
1871
|
+
var characterId = device.character_id || row.character_id || row.character_preset_id || '';
|
|
1775
1872
|
var status = String(device.status || row.status || '').toLowerCase();
|
|
1776
1873
|
var heartbeat = device.last_heartbeat || row.last_heartbeat;
|
|
1777
1874
|
var heartbeatAt = heartbeat ? Date.parse(heartbeat) : NaN;
|
|
@@ -1786,6 +1883,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1786
1883
|
if(existing){
|
|
1787
1884
|
existing.schedulerRobotId = id;
|
|
1788
1885
|
existing.schedulerDeviceId = device.id || id;
|
|
1886
|
+
existing.parkSlotId = characterId || existing.parkSlotId || '';
|
|
1789
1887
|
existing.displayName = existing.displayName || name;
|
|
1790
1888
|
existing.connected = !!existing.connected || connected;
|
|
1791
1889
|
existing.lastSeenAt = heartbeatAt && Number.isFinite(heartbeatAt) ? heartbeatAt : existing.lastSeenAt;
|
|
@@ -1796,6 +1894,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1796
1894
|
nodeId: 'scheduler-robot-'+id,
|
|
1797
1895
|
schedulerRobotId: id,
|
|
1798
1896
|
schedulerDeviceId: device.id || id,
|
|
1897
|
+
parkSlotId: characterId,
|
|
1799
1898
|
displayName: name,
|
|
1800
1899
|
role: 'robot',
|
|
1801
1900
|
platform: 'RoboPark robot',
|
|
@@ -1874,16 +1973,56 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1874
1973
|
});
|
|
1875
1974
|
|
|
1876
1975
|
// ── tabs: Fleet + Park + RoboPark sessions/telemetry ──
|
|
1877
|
-
var tabs = document.querySelectorAll('.tab');
|
|
1878
|
-
var activeTab = 'fleet';
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1976
|
+
var tabs = document.querySelectorAll('.tab');
|
|
1977
|
+
var activeTab = 'fleet';
|
|
1978
|
+
var globalSidebar=$('global-sidebar');
|
|
1979
|
+
var globalNavToggle=$('global-nav-toggle');
|
|
1980
|
+
var globalNavMobile=$('global-nav-mobile');
|
|
1981
|
+
var globalNavBackdrop=$('global-nav-backdrop');
|
|
1982
|
+
var globalNavStorageKey='rp-global-nav-collapsed-v1';
|
|
1983
|
+
function globalNavIsMobile(){ return !!(window.matchMedia && window.matchMedia('(max-width:760px)').matches); }
|
|
1984
|
+
function setGlobalNavCollapsed(collapsed){
|
|
1985
|
+
if(globalNavIsMobile()) collapsed=false;
|
|
1986
|
+
document.body.classList.toggle('global-nav-collapsed',!!collapsed);
|
|
1987
|
+
if(globalNavToggle){
|
|
1988
|
+
globalNavToggle.innerHTML=collapsed?'>>':'<<';
|
|
1989
|
+
globalNavToggle.setAttribute('aria-expanded',collapsed?'false':'true');
|
|
1990
|
+
globalNavToggle.setAttribute('aria-label',collapsed?'Expand navigation':'Collapse navigation');
|
|
1991
|
+
}
|
|
1992
|
+
if(!globalNavIsMobile()){ try{ localStorage.setItem(globalNavStorageKey,collapsed?'1':'0'); }catch(e){} }
|
|
1993
|
+
}
|
|
1994
|
+
function closeGlobalNavMobile(){
|
|
1995
|
+
document.body.classList.remove('global-nav-open');
|
|
1996
|
+
if(globalNavMobile) globalNavMobile.setAttribute('aria-expanded','false');
|
|
1997
|
+
}
|
|
1998
|
+
function openGlobalNavMobile(){
|
|
1999
|
+
document.body.classList.add('global-nav-open');
|
|
2000
|
+
if(globalNavMobile) globalNavMobile.setAttribute('aria-expanded','true');
|
|
2001
|
+
}
|
|
2002
|
+
var initialGlobalNavCollapsed=false;
|
|
2003
|
+
try{ initialGlobalNavCollapsed=localStorage.getItem(globalNavStorageKey)==='1'; }catch(e){}
|
|
2004
|
+
setGlobalNavCollapsed(initialGlobalNavCollapsed);
|
|
2005
|
+
if(globalNavToggle) globalNavToggle.addEventListener('click',function(){ setGlobalNavCollapsed(!document.body.classList.contains('global-nav-collapsed')); });
|
|
2006
|
+
if(globalNavMobile) globalNavMobile.addEventListener('click',function(){ if(document.body.classList.contains('global-nav-open')) closeGlobalNavMobile(); else openGlobalNavMobile(); });
|
|
2007
|
+
if(globalNavBackdrop) globalNavBackdrop.addEventListener('click',closeGlobalNavMobile);
|
|
2008
|
+
document.addEventListener('keydown',function(e){ if(e.key==='Escape') closeGlobalNavMobile(); });
|
|
2009
|
+
var globalSubNav=document.querySelectorAll('[data-global-sub]');
|
|
2010
|
+
for(var gsi=0;gsi<globalSubNav.length;gsi++){
|
|
2011
|
+
(function(btn){ btn.addEventListener('click',function(){ selectTab('robopark'); setRpSubTab(btn.getAttribute('data-global-sub')); closeGlobalNavMobile(); }); })(globalSubNav[gsi]);
|
|
2012
|
+
}
|
|
2013
|
+
for (var ti=0; ti<tabs.length; ti++){
|
|
2014
|
+
(function(btn){
|
|
2015
|
+
btn.addEventListener('click', function(){ selectTab(btn.getAttribute('data-tab')); closeGlobalNavMobile(); });
|
|
2016
|
+
})(tabs[ti]);
|
|
2017
|
+
}
|
|
1884
2018
|
function selectTab(name){
|
|
1885
|
-
activeTab = name;
|
|
1886
|
-
|
|
2019
|
+
activeTab = name;
|
|
2020
|
+
if(globalSidebar) globalSidebar.setAttribute('data-active-tab',name);
|
|
2021
|
+
for (var i=0;i<tabs.length;i++){
|
|
2022
|
+
var tabActive=tabs[i].getAttribute('data-tab')===name;
|
|
2023
|
+
tabs[i].classList.toggle('active',tabActive);
|
|
2024
|
+
if(tabActive) tabs[i].setAttribute('aria-current','page'); else tabs[i].removeAttribute('aria-current');
|
|
2025
|
+
}
|
|
1887
2026
|
$('pane-fleet').classList.toggle('active', name==='fleet');
|
|
1888
2027
|
$('pane-park').classList.toggle('active', name==='park');
|
|
1889
2028
|
$('pane-robopark').classList.toggle('active', name==='robopark');
|
|
@@ -1895,6 +2034,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1895
2034
|
initRpSubTabs();
|
|
1896
2035
|
}
|
|
1897
2036
|
if (name==='park') Park.activate(); else Park.deactivate();
|
|
2037
|
+
closeGlobalNavMobile();
|
|
1898
2038
|
}
|
|
1899
2039
|
var fleetParkBtn=$('fleet-open-park'); if(fleetParkBtn) fleetParkBtn.addEventListener('click',function(){selectTab('park');});
|
|
1900
2040
|
var fleetSessionsBtn=$('fleet-open-sessions'); if(fleetSessionsBtn) fleetSessionsBtn.addEventListener('click',function(){selectTab('robopark');setRpSubTab('live');});
|
|
@@ -1905,6 +2045,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1905
2045
|
function initTab(){
|
|
1906
2046
|
if (initHash==='robopark'||initHash.indexOf('robopark/')===0) selectTab('robopark');
|
|
1907
2047
|
else if(initHash==='park') selectTab('park');
|
|
2048
|
+
else selectTab('fleet');
|
|
1908
2049
|
}
|
|
1909
2050
|
// initTab() is called at the very bottom of this IIFE, after Park is assigned.
|
|
1910
2051
|
|
|
@@ -2167,15 +2308,26 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2167
2308
|
if(Park && Park.updateSlots) Park.updateSlots(parkSlots);
|
|
2168
2309
|
});
|
|
2169
2310
|
}
|
|
2170
|
-
function fillCharacterDropdowns(){
|
|
2311
|
+
function fillCharacterDropdowns(){
|
|
2171
2312
|
var sels=document.querySelectorAll('.rp-char');
|
|
2172
2313
|
for(var i=0;i<sels.length;i++){ var sel=sels[i]; var cur=sel.dataset.current||'';
|
|
2173
2314
|
var opts=sel.querySelectorAll('option'); for(var k=opts.length-1;k>=0;k--){ if(opts[k].value) opts[k].parentNode.removeChild(opts[k]); }
|
|
2174
2315
|
for(var j=0;j<characterPresets.length;j++){ var p=characterPresets[j];
|
|
2175
2316
|
var o=document.createElement('option'); o.value=p.id||p.preset_id||p.character_id||''; o.textContent=(p.name||p.id||p.preset_id||'preset')+(p.description?(' — '+p.description):''); if((o.value)===cur) o.selected=true; sel.appendChild(o);
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
var commandCharacter=$('rp-command-character');
|
|
2320
|
+
if(commandCharacter){
|
|
2321
|
+
var commandCurrent=commandCharacter.dataset.current||commandCharacter.value||'vixen';
|
|
2322
|
+
commandCharacter.innerHTML=characterPresets.map(function(p){
|
|
2323
|
+
var id=p.id||p.preset_id||p.character_id||'',name=p.name||id||'character';
|
|
2324
|
+
return '<option value="'+esc(id)+'"'+(id===commandCurrent?' selected':'')+'>'+esc(name)+'</option>';
|
|
2325
|
+
}).join('');
|
|
2326
|
+
if(!commandCharacter.value&&characterPresets.length)commandCharacter.value=characterPresets[0].id||characterPresets[0].preset_id||characterPresets[0].character_id||'';
|
|
2327
|
+
commandCharacter.dataset.current=commandCharacter.value;
|
|
2328
|
+
renderOneMinuteSetup();
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2179
2331
|
function fillVoiceStackDropdowns(){
|
|
2180
2332
|
var sels=document.querySelectorAll('.rp-voice');
|
|
2181
2333
|
for(var i=0;i<sels.length;i++){ var sel=sels[i]; var cur=sel.dataset.current||'';
|
|
@@ -2268,38 +2420,33 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2268
2420
|
}
|
|
2269
2421
|
function clearServerForm(){ $('srv-id').value=''; $('srv-name').value=''; $('srv-url').value=''; $('srv-key').value=''; $('srv-secret').value=''; $('srv-max').value='8'; $('srv-autonote').textContent=''; $('srv-autonote').className='rp-autonote'; }
|
|
2270
2422
|
|
|
2271
|
-
//
|
|
2272
|
-
|
|
2423
|
+
// Robust RoboVoice-first one-liners update live as the operator edits the form.
|
|
2424
|
+
function rpControlUrl(network){
|
|
2425
|
+
var env=rpCommandEnv(),host=network==='tailscale'?env.tail:env.lan;
|
|
2426
|
+
host=String(host||'').replace(/^https?:\\/\\//i,'').replace(/\\/+$/,'').replace(/:47913$/,'');
|
|
2427
|
+
return 'http://'+host+':47913';
|
|
2428
|
+
}
|
|
2273
2429
|
function buildAddRobotCommand(name, characterId){
|
|
2274
2430
|
if(!name) return '';
|
|
2275
2431
|
var network=$('rb-network')&&$('rb-network').value||'lan';
|
|
2276
|
-
var
|
|
2277
|
-
var
|
|
2278
|
-
var hubUrl=/^https?:\/\//i.test(hubHost)?hubHost.replace(/\\/+$/,''):'http://'+hubHost.replace(/\\/+$/,'')+':47913';
|
|
2279
|
-
var schedulerUrl=hubUrl+'/robopark';
|
|
2280
|
-
var argv=['robopark','add-robot',name];
|
|
2281
|
-
if(characterId) argv.push('--character', characterId);
|
|
2432
|
+
var hubUrl=rpControlUrl(network);
|
|
2433
|
+
var argv=['sudo','robopark','setup','--robot','--name',name];
|
|
2282
2434
|
var site=$('rb-site')&&$('rb-site').value.trim();if(site)argv.push('--site',site);
|
|
2283
2435
|
if(hubUrl) argv.push('--hub-url', hubUrl);
|
|
2284
2436
|
var meshToken=$('rb-token')&&$('rb-token').value.trim()||dashboardMeshToken()||'<mesh-token>';
|
|
2285
2437
|
argv.push('--token', meshToken);
|
|
2286
|
-
if(
|
|
2438
|
+
if(characterId)argv.push('--character',characterId);
|
|
2287
2439
|
argv.push(network==='tailscale'?'--tailscale':'--lan','--start','--auto-start','--yes');
|
|
2288
|
-
return argv.map(function(a){ return /[^a-zA-Z0-9_./:=,-]/.test(a) ? '"'+a.replace(/"/g,'\\"')+'"' : a; }).join(' ');
|
|
2440
|
+
return rpCompleteRobotCommand(argv.map(function(a){ return /[^a-zA-Z0-9_./:=,-]/.test(a) ? '"'+a.replace(/"/g,'\\"')+'"' : a; }).join(' '));
|
|
2289
2441
|
}
|
|
2290
2442
|
function buildPadRobotCommand(name,characterId,enrollmentToken){
|
|
2291
|
-
var env=rpCommandEnv(),
|
|
2292
|
-
var hubUrl=/^https?:\/\//i.test(hubHost)?hubHost.replace(/\\\/+$/,''):'http://'+hubHost.replace(/\\\/+$/,'')+':47913';
|
|
2443
|
+
var env=rpCommandEnv(),network=env.network||'lan',hubUrl=rpControlUrl(network);
|
|
2293
2444
|
var meshToken=env.token||dashboardMeshToken()||'<mesh-token>',site=env.site||'Tel Aviv';
|
|
2294
|
-
var argv;
|
|
2295
|
-
if(enrollmentToken)
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
if(characterId)argv.push('--character',characterId);
|
|
2300
|
-
argv.push('--site',site,'--hub-url',hubUrl,'--token',meshToken,'--scheduler-url',hubUrl+'/robopark','--lan','--start','--auto-start','--yes');
|
|
2301
|
-
}
|
|
2302
|
-
return argv.map(function(a){return /[^a-zA-Z0-9_./:=,-]/.test(a)?'"'+String(a).replace(/"/g,'\\"')+'"':String(a);}).join(' ');
|
|
2445
|
+
var argv=['sudo','robopark','setup','--robot','--name',name,'--site',site,'--hub-url',hubUrl,'--token',meshToken,'--port','47913',network==='tailscale'?'--tailscale':'--lan'];
|
|
2446
|
+
if(enrollmentToken)argv.push('--enrollment-token',enrollmentToken);
|
|
2447
|
+
if(characterId)argv.push('--character',characterId);
|
|
2448
|
+
argv.push('--start','--auto-start','--yes');
|
|
2449
|
+
return rpCompleteRobotCommand(argv.map(function(a){return /[^a-zA-Z0-9_./:=,-]/.test(a)?'"'+String(a).replace(/"/g,'\\"')+'"':String(a);}).join(' '));
|
|
2303
2450
|
}
|
|
2304
2451
|
function refreshRobotCliCommand(){
|
|
2305
2452
|
var name=$('rb-name').value.trim(); var cid=$('rb-char').value||'';
|
|
@@ -2394,7 +2541,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2394
2541
|
+'<div class="rp-config-editor-intro"><span class="mark">AI</span><div><b>'+(editing?'Edit character preset':'Create character preset')+'</b><span>The persona prompt and default voice stack are frozen into each session record for reliable history and replay.</span></div></div>'
|
|
2395
2542
|
+'<div class="rp-config-section"><div class="rp-config-section-head">Identity <span>fleet-facing appearance</span></div><div class="rp-config-fields"><div class="rp-field"><label>Preset ID</label><input id="cfg-cp-id" value="'+esc(p.id||'')+'" placeholder="jaguar"'+(editing?' disabled':'')+'></div><div class="rp-field"><label>Display name</label><input id="cfg-cp-name" value="'+esc(p.name||'')+'" placeholder="JAGUAR"></div><div class="rp-field wide"><label>Description</label><input id="cfg-cp-description" value="'+esc(p.description||'')+'" placeholder="Short operator-facing description"></div><div class="rp-field wide"><label>Avatar image path</label><input id="cfg-cp-img" value="'+esc(p.img||'')+'" placeholder="/static/robopark/jaguar.png"></div></div></div>'
|
|
2396
2543
|
+'<div class="rp-config-section"><div class="rp-config-section-head">Persona intelligence <span>ROBOVOICE system prompt</span></div><div class="rp-config-fields"><div class="rp-field wide"><label>System prompt</label><textarea id="cfg-cp-prompt" placeholder="Define personality, constraints, speaking style, and robot behavior…">'+esc(p.system_prompt||'')+'</textarea></div></div></div>'
|
|
2397
|
-
+'<div class="rp-config-section"><div class="rp-config-section-head">Voice & embodiment <span>pipeline and physical
|
|
2544
|
+
+'<div class="rp-config-section"><div class="rp-config-section-head">Voice & embodiment <span>pipeline and physical permissions</span></div><div class="rp-config-fields"><div class="rp-field"><label>Default voice stack</label><select id="cfg-cp-stack">'+stackOptions+'</select></div><div class="rp-field"><label>Agent-allowed motor sequences</label><input id="cfg-cp-motors" value="'+esc((p.motors||[]).join(', '))+'" placeholder="greeting-motion, wave"><small>Exact saved sequence IDs. Empty means this character cannot invoke motors through agent tools.</small></div><div class="rp-field"><label>Park pad X (0–1)</label><input id="cfg-cp-nx" type="number" min="0" max="1" step="0.01" value="'+esc(p.nx!=null?p.nx:0.5)+'"></div><div class="rp-field"><label>Park pad Y (0–1)</label><input id="cfg-cp-ny" type="number" min="0" max="1" step="0.01" value="'+esc(p.ny!=null?p.ny:0.5)+'"></div></div></div>'
|
|
2398
2545
|
+'<div class="rp-config-editor-foot"><button class="rp-btn" id="cfg-cp-cancel">Cancel</button><button class="rp-btn primary" id="cfg-cp-save">'+(editing?'Save changes':'Create character')+'</button></div></div>';
|
|
2399
2546
|
showModal(editing?'Edit character · '+(p.name||p.id):'New character preset',html); $('rp-modal').classList.add('config-modal'); $('cfg-cp-cancel').addEventListener('click',closeModal);
|
|
2400
2547
|
$('cfg-cp-save').addEventListener('click',function(){ var body=characterDraft(); if(!body.name){ showOut('Character name is required'); return; } if(!editing&&!body.id) body.id=body.name.toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,48); var btn=$('cfg-cp-save');btn.disabled=true;btn.textContent='Saving…';rpAction(editing?'PUT':'POST',editing?'/robopark/api/character-presets/'+encodeURIComponent(id):'/robopark/api/character-presets',body).then(function(){closeModal();showOut((editing?'Updated ':'Created ')+body.name);return Promise.all([loadCharacterPresets(),loadParkSlots()]);}).then(function(){pollRobopark();if(Park&&Park.refreshDrawer)Park.refreshDrawer();}).catch(function(e){btn.disabled=false;btn.textContent=editing?'Save changes':'Create character';showOut('character preset: '+e.message);}); });
|
|
@@ -2571,8 +2718,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2571
2718
|
lastSchedulerPollAt = Date.now();
|
|
2572
2719
|
var grid = $('rp-grid'), totals = $('rp-totals');
|
|
2573
2720
|
// stash per-robot rows so the Park drawer can show live session stats
|
|
2574
|
-
|
|
2575
|
-
robotsFrom(d).forEach(function(x){ roboparkByName[String(robotName(x)).toLowerCase()] = x; if(x.name) roboparkByName[String(x.name).toLowerCase()] = x; });
|
|
2721
|
+
indexRoboparkTelemetry(robotsFrom(d));
|
|
2576
2722
|
// C4: also feed lastRobots for the Incidents sub-tab
|
|
2577
2723
|
lastRobots = robotsFrom(d) || [];
|
|
2578
2724
|
// push the same map into the Park so the canvas can color robots
|
|
@@ -2694,43 +2840,57 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2694
2840
|
|
|
2695
2841
|
// ── Sub-tab routing ──
|
|
2696
2842
|
// Command sheet: generate production commands from one saved environment.
|
|
2697
|
-
var RP_COMMAND_ENV_KEY='rp-command-env-
|
|
2843
|
+
var RP_COMMAND_ENV_KEY='rp-command-env-v2';
|
|
2698
2844
|
var RP_CUSTOM_COMMANDS_KEY='rp-custom-commands-v1';
|
|
2699
2845
|
var rpVisibleCommands=[];
|
|
2700
|
-
function rpCommandDefaults(){return {lan:'192.168.0.9',tail:'100.84.147.24',site:'Tel Aviv',robot:'bmw',enrollment:'',token:dashboardMeshToken()};}
|
|
2846
|
+
function rpCommandDefaults(){return {lan:'192.168.0.9',tail:'100.84.147.24',site:'Tel Aviv',robot:'bmw',character:'vixen',device:'',enrollment:'',token:dashboardMeshToken(),robovoice:'C:\\ROBOVOICE-main',release:'2.8.113',network:'lan'};}
|
|
2701
2847
|
function rpLoadStoredJson(key,fallback){try{var value=JSON.parse(localStorage.getItem(key)||'null');return value&&typeof value==='object'?value:fallback;}catch(e){return fallback;}}
|
|
2702
2848
|
function rpCommandEnv(){
|
|
2703
2849
|
var d=rpCommandDefaults(),s=rpLoadStoredJson(RP_COMMAND_ENV_KEY,{});
|
|
2704
2850
|
function value(name){var input=$('rp-command-'+name),raw=input?input.value:(s[name]!=null?s[name]:d[name]);return String(raw==null?'':raw).trim();}
|
|
2705
|
-
return {lan:value('lan'),tail:value('tail'),site:value('site'),robot:value('robot'),enrollment:value('enrollment'),token:value('token')};
|
|
2851
|
+
return {lan:value('lan'),tail:value('tail'),site:value('site'),robot:value('robot'),character:value('character'),device:value('device'),enrollment:value('enrollment'),token:value('token'),robovoice:value('robovoice'),release:value('release'),network:value('network')||'lan'};
|
|
2706
2852
|
}
|
|
2707
2853
|
function rpSaveCommandEnv(){try{localStorage.setItem(RP_COMMAND_ENV_KEY,JSON.stringify(rpCommandEnv()));}catch(e){}}
|
|
2708
2854
|
function rpShellArg(value){var text=String(value||'');return /^[A-Za-z0-9._:\/-]+$/.test(text)?text:'"'+text.replace(/"/g,'\\"')+'"';}
|
|
2855
|
+
function rpCompleteRobotCommand(setupCommand){var release=rpCommandEnv().release||'2.8.113';return 'sudo npm install -g infinicode@'+rpShellArg(release)+' --force && hash -r && (sudo robopark stop || true) && '+setupCommand;}
|
|
2856
|
+
function rpRobotSetupCommand(){
|
|
2857
|
+
var e=rpCommandEnv(),control=rpControlUrl(e.network),argv=['sudo','robopark','setup','--robot','--name',e.robot,'--site',e.site,'--hub-url',control,'--token',e.token,'--port','47913',e.network==='tailscale'?'--tailscale':'--lan'];
|
|
2858
|
+
if(e.enrollment)argv.push('--enrollment-token',e.enrollment);if(e.character)argv.push('--character',e.character);argv.push('--start','--auto-start','--yes');
|
|
2859
|
+
return rpCompleteRobotCommand(argv.map(function(a){return rpShellArg(a);}).join(' '));
|
|
2860
|
+
}
|
|
2709
2861
|
function rpCommandCatalog(){
|
|
2710
|
-
var e=rpCommandEnv(),
|
|
2862
|
+
var e=rpCommandEnv(),control=rpControlUrl(e.network),device=e.device||e.robot,enroll=e.enrollment?' --enrollment-token '+rpShellArg(e.enrollment):'',auth='?token='+encodeURIComponent(e.token);
|
|
2863
|
+
var api=control+'/robopark/api',release=e.release||'2.8.113';
|
|
2711
2864
|
return [
|
|
2712
|
-
{id:'hub-up',category:'Provision',title:'
|
|
2713
|
-
{id:'robot-
|
|
2714
|
-
{id:'
|
|
2715
|
-
{id:'
|
|
2716
|
-
{id:'
|
|
2717
|
-
{id:'
|
|
2865
|
+
{id:'hub-up',category:'Provision',title:'Install and start RoboVoice-first control host',description:'Run in elevated PowerShell on livekit-1. Starts the private compatibility data service, proven RoboVoice stack, mesh relay, and full Control Center.',command:'npm install -g infinicode@'+rpShellArg(release)+' --force; robopark serve --port 8080 --no-open; robopark agent-up --path '+rpShellArg(e.robovoice)+' --scheduler-url http://127.0.0.1:8080 --token '+rpShellArg(e.token)+'; infinicode serve --hub --name livekit-1 --port 47913 --token '+rpShellArg(e.token)+' --lan --dashboard --scheduler-url http://127.0.0.1:8080 --auto-update --supervised'},
|
|
2866
|
+
{id:'robot-install',category:'Provision',title:'Install or repair selected robot',description:'Run on the Pi. One control URL, one token, one supervised runtime; camera, audio, motion, motor server, and mesh start together.',command:rpRobotSetupCommand()},
|
|
2867
|
+
{id:'operator-tail',category:'Provision',title:'Connect operator agents to control mesh',description:'Connects Codex/OpenCode tools through the livekit-1 control URL without exposing the private data service.',command:'infinicode mcp --lan --tailscale --seed '+rpControlUrl('tailscale').replace(/^http:\\/\\//,'')+' --name dev-laptop --token '+rpShellArg(e.token)+' --mesh-port 47913'},
|
|
2868
|
+
{id:'production-on',category:'Production',title:'Arm always-on motion conversations',description:'Enables local motion-triggered RoboVoice calls for the canonical device.',command:'Invoke-RestMethod -Method Patch -ContentType "application/json" -Body (@{production_mode=$true} | ConvertTo-Json) "'+api+'/devices/'+encodeURIComponent(device)+auth+'"'},
|
|
2869
|
+
{id:'production-off',category:'Production',title:'Disarm production conversations',description:'Stops new motion-triggered calls without taking camera monitoring or remote control offline.',command:'Invoke-RestMethod -Method Patch -ContentType "application/json" -Body (@{production_mode=$false} | ConvertTo-Json) "'+api+'/devices/'+encodeURIComponent(device)+auth+'"'},
|
|
2870
|
+
{id:'session-start',category:'Voice',title:'Start one production conversation',description:'Runs the same robot motion-to-RoboVoice path used onsite.',command:'Invoke-RestMethod -Method Post "'+api+'/robots/'+encodeURIComponent(device)+'/pipeline-test/start'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2871
|
+
{id:'session-stop',category:'Voice',title:'Stop current conversation',description:'Ends the selected robot session while leaving production mode armed.',command:'Invoke-RestMethod -Method Post "'+api+'/robots/'+encodeURIComponent(device)+'/pipeline-test/stop'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2872
|
+
{id:'voice-health',category:'Voice',title:'Verify RoboVoice configuration API',description:'Confirms characters and voice stacks are visible through the single control URL.',command:'Invoke-RestMethod "'+api+'/character-presets'+auth+'"; Invoke-RestMethod "'+api+'/voice-stacks'+auth+'"'},
|
|
2873
|
+
{id:'hub-ui',category:'Health',title:'Open full production control center',description:'Opens Park through the selected LAN or Tailscale control URL.',command:'Start-Process "'+control+'/?token='+encodeURIComponent(e.token)+'#park"'},
|
|
2874
|
+
{id:'hub-build',category:'Health',title:'Verify deployed Control Center release',description:'Compares installed CLI, UI build, and expected pinned release.',command:'infinicode --version; Write-Host "expected '+release+'"; Invoke-RestMethod "'+control+'/fed/ui-build'+auth+'"'},
|
|
2718
2875
|
{id:'hub-ports',category:'Health',title:'Inspect hub ports on Windows',description:'Lists processes owning the control center, scheduler, and LiveKit ports without changing them.',command:'Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Where-Object { $_.LocalPort -in 47913,8080,7880,7881,7882,7883 } | Sort-Object LocalPort | Format-Table LocalAddress,LocalPort,OwningProcess -AutoSize'},
|
|
2719
|
-
{id:'
|
|
2720
|
-
{id:'mesh-status',category:'Health',title:'Check mesh nodes',description:'Lists connected hub, robot, and operator nodes.',command:'Invoke-RestMethod "'+
|
|
2876
|
+
{id:'control-health',category:'Health',title:'Check robots through control URL',description:'Confirms the management API returns canonical robot and device inventory.',command:'Invoke-RestMethod "'+api+'/devices'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2877
|
+
{id:'mesh-status',category:'Health',title:'Check mesh nodes',description:'Lists connected hub, robot, and operator nodes.',command:'Invoke-RestMethod "'+control+'/fed/status'+auth+'" | ConvertTo-Json -Depth 6'},
|
|
2721
2878
|
{id:'pi-runtime',category:'Health',title:'Follow robot runtime logs',description:'Live systemd logs for the selected robot unified runtime.',command:'sudo journalctl -u robopark-robot-runtime-'+e.robot+'.service -f'},
|
|
2722
2879
|
{id:'pi-ports',category:'Health',title:'Inspect robot service ports',description:'Checks mesh, camera, motion webhook, and audio listeners on a Linux robot.',command:"sudo ss -ltnp | grep -E ':(47913|5000|5057|8000)'"},
|
|
2880
|
+
{id:'camera-relay',category:'Media',title:'Open always-on robot camera relay',description:'Opens RoboVision video through authenticated livekit-1 mesh relay; no voice session is required.',command:'Start-Process "'+control+'/fed/media/robots/'+encodeURIComponent(e.robot)+'/video_feed'+auth+'"'},
|
|
2723
2881
|
{id:'camera-inventory',category:'Media',title:'Read RoboVision media inventory',description:'Returns the real cameras, microphones, and speakers detected by RoboVision.',command:'curl -s http://127.0.0.1:5000/api/media/inventory'},
|
|
2724
2882
|
{id:'audio-inventory',category:'Media',title:'Read RoboVision audio devices',description:'Returns the microphone and speaker inventory used by the robot audio server.',command:'curl -s http://127.0.0.1:8000/devices'},
|
|
2725
2883
|
{id:'camera-devices',category:'Media',title:'List valid Linux capture devices',description:'Shows V4L2 devices and capabilities so metadata-only nodes are not selected as cameras.',command:'v4l2-ctl --list-devices; for d in /dev/video*; do echo "--- $d"; v4l2-ctl -d "$d" --all 2>/dev/null | grep -E "Card type|Video Capture|Device Caps"; done'},
|
|
2726
2884
|
{id:'camera-preview',category:'Media',title:'Open direct RoboVision camera feed',description:'Opens the robot MJPEG feed locally for camera-only diagnosis.',command:'xdg-open http://127.0.0.1:5000/video_feed'},
|
|
2727
|
-
{id:'
|
|
2885
|
+
{id:'motor-discover',category:'Motors',title:'Discover registered robot relays',description:'Queues safe registry discovery through the canonical robot supervisor without pulsing GPIO.',command:'Invoke-RestMethod -Method Post "'+api+'/robots/'+encodeURIComponent(device)+'/motors/discover'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2886
|
+
{id:'motor-stop',category:'Motors',title:'Emergency all motors off',description:'Immediately queues all registered relays off for the selected robot.',command:'Invoke-RestMethod -Method Post "'+api+'/robots/'+encodeURIComponent(device)+'/motors/stop'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2887
|
+
{id:'robot-restart',category:'Recovery',title:'Clean repair selected robot',description:'Stops the supervised runtime, installs the pinned release, and recreates the complete runtime from one control URL.',command:rpRobotSetupCommand()},
|
|
2728
2888
|
{id:'robot-status',category:'Recovery',title:'Inspect robot service failure',description:'Shows service state and the latest 150 log lines after a failed start.',command:'sudo systemctl status robopark-robot-runtime-'+e.robot+'.service --no-pager; sudo journalctl -u robopark-robot-runtime-'+e.robot+'.service -n 150 --no-pager'},
|
|
2729
2889
|
{id:'docker-health',category:'Health',title:'Inspect Docker and LiveKit containers',description:'Shows container state, health, ports, and names on the hub.',command:'docker ps -a --format "table {{.Names}}\\t{{.Status}}\\t{{.Ports}}\\t{{.Image}}"'},
|
|
2730
2890
|
{id:'docker-restart',category:'Recovery',title:'Restart Docker Desktop service',description:'Run from elevated PowerShell only when Docker Desktop is stuck starting.',command:'Restart-Service com.docker.service; Start-Process -FilePath "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"'},
|
|
2731
2891
|
{id:'tailscale-status',category:'Health',title:'Check Tailscale routes and Serve',description:'Confirms the node is connected and shows any active Serve proxy.',command:'tailscale status; tailscale serve status'},
|
|
2732
|
-
{id:'npm-version',category:'Packages',title:'Check installed and published versions',description:'
|
|
2733
|
-
{id:'npm-update',category:'Packages',title:'
|
|
2892
|
+
{id:'npm-version',category:'Packages',title:'Check installed, pinned, and published versions',description:'Prevents accidental downgrade to an older latest tag.',command:'infinicode --version; Write-Host "pinned '+release+'"; npm view infinicode version'},
|
|
2893
|
+
{id:'npm-update',category:'Packages',title:'Install pinned RoboPark release',description:'Installs the exact release selected above instead of an ambiguous latest tag.',command:'npm install -g infinicode@'+rpShellArg(release)+' --force'},
|
|
2734
2894
|
{id:'npm-cache',category:'Packages',title:'Repair npm metadata cache',description:'Use when a new version returns ETARGET. Verify metadata before clearing anything.',command:'npm cache verify; npm view infinicode dist-tags --json'}
|
|
2735
2895
|
];
|
|
2736
2896
|
}
|
|
@@ -2738,6 +2898,16 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2738
2898
|
function rpSaveCustomCommands(list){try{localStorage.setItem(RP_CUSTOM_COMMANDS_KEY,JSON.stringify(list));}catch(e){showOut('Could not save custom command: '+e.message);}}
|
|
2739
2899
|
function rpLegacyCopy(value){var area=document.createElement('textarea');area.value=value;area.style.position='fixed';area.style.opacity='0';document.body.appendChild(area);area.select();try{document.execCommand('copy');showOut('command copied');}catch(e){showOut('Copy failed: select the command manually');}area.remove();}
|
|
2740
2900
|
function rpCopyCommand(text){var value=String(text||'');if(navigator.clipboard&&window.isSecureContext){navigator.clipboard.writeText(value).then(function(){showOut('command copied');}).catch(function(){rpLegacyCopy(value);});}else rpLegacyCopy(value);}
|
|
2901
|
+
function renderOneMinuteSetup(){
|
|
2902
|
+
var code=$('rp-minute-command'),button=$('rp-minute-copy'),status=$('rp-minute-status');if(!code||!button||!status)return;
|
|
2903
|
+
var e=rpCommandEnv(),host=e.network==='tailscale'?e.tail:e.lan,issues=[];
|
|
2904
|
+
if(!e.robot)issues.push('Enter the robot name');else if(!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(e.robot))issues.push('Robot name may use letters, numbers, dash, and underscore only');
|
|
2905
|
+
if(!e.character)issues.push('Choose a character');
|
|
2906
|
+
if(!host)issues.push('Set the livekit-1 address under Advanced tools');
|
|
2907
|
+
if(!e.token)issues.push('Open this dashboard with its token, or enter the control token under Advanced tools');
|
|
2908
|
+
var command=rpRobotSetupCommand();code.textContent=command;button.disabled=issues.length>0;button.dataset.command=command;
|
|
2909
|
+
status.className='rp-minute-status '+(issues.length?'bad':'ok');status.textContent=issues.length?issues[0]:'Ready: copy this command and run it once on '+e.robot+'.';
|
|
2910
|
+
}
|
|
2741
2911
|
function renderCommandSheet(){
|
|
2742
2912
|
var grid=$('rp-command-grid');if(!grid)return;
|
|
2743
2913
|
var search=String(($('rp-command-search')||{}).value||'').toLowerCase().trim(),category=String(($('rp-command-category')||{}).value||'').toLowerCase();
|
|
@@ -2748,25 +2918,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2748
2918
|
grid.innerHTML=rpVisibleCommands.map(function(x){return '<article class="rp-command-card"><div class="rp-command-head"><b>'+esc(x.title)+'</b><span class="rp-command-tag">'+esc(x.category)+'</span></div><p>'+esc(x.description)+'</p><pre class="rp-command-code"><code>'+esc(x.command)+'</code></pre><div class="rp-command-actions"><button class="rp-btn primary" data-command-copy="'+esc(x.id)+'">Copy command</button>'+(x.custom?'<button class="rp-btn danger" data-command-delete="'+esc(x.id)+'">Delete</button>':'')+'</div></article>';}).join('');
|
|
2749
2919
|
}
|
|
2750
2920
|
function initCommandSheet(){
|
|
2751
|
-
var fields=['lan','tail','site','robot','enrollment','token'],defaults=rpCommandDefaults(),saved=rpLoadStoredJson(RP_COMMAND_ENV_KEY,{});
|
|
2752
|
-
fields.forEach(function(name){var input=$('rp-command-'+name);if(!input)return;
|
|
2753
|
-
var search=$('rp-command-search'),category=$('rp-command-category'),grid=$('rp-command-grid'),reset=$('rp-command-reset'),copyAll=$('rp-command-copy-all'),form=$('rp-command-custom-form');
|
|
2921
|
+
var fields=['lan','tail','site','robot','character','device','enrollment','token','robovoice','release','network'],defaults=rpCommandDefaults(),saved=rpLoadStoredJson(RP_COMMAND_ENV_KEY,{});
|
|
2922
|
+
fields.forEach(function(name){var input=$('rp-command-'+name);if(!input)return;var initial=name==='token'&&TOKEN?TOKEN:(saved[name]!=null?saved[name]:defaults[name]);if(name==='character')input.dataset.current=initial;input.value=initial;var refresh=function(){if(name==='character')input.dataset.current=input.value;rpSaveCommandEnv();renderOneMinuteSetup();renderCommandSheet();};input.addEventListener('input',refresh);input.addEventListener('change',refresh);});
|
|
2923
|
+
var search=$('rp-command-search'),category=$('rp-command-category'),grid=$('rp-command-grid'),reset=$('rp-command-reset'),copyAll=$('rp-command-copy-all'),form=$('rp-command-custom-form'),minuteCopy=$('rp-minute-copy');
|
|
2754
2924
|
if(search)search.addEventListener('input',renderCommandSheet);if(category)category.addEventListener('change',renderCommandSheet);
|
|
2755
|
-
if(reset)reset.addEventListener('click',function(){try{localStorage.removeItem(RP_COMMAND_ENV_KEY);}catch(e){}fields.forEach(function(name){var input=$('rp-command-'+name);if(input)input.value=defaults[name];});renderCommandSheet();showOut('
|
|
2925
|
+
if(reset)reset.addEventListener('click',function(){try{localStorage.removeItem(RP_COMMAND_ENV_KEY);}catch(e){}fields.forEach(function(name){var input=$('rp-command-'+name);if(input){input.value=defaults[name];if(name==='character')input.dataset.current=defaults[name];}});renderOneMinuteSetup();renderCommandSheet();showOut('setup reset');});
|
|
2926
|
+
if(minuteCopy)minuteCopy.addEventListener('click',function(){if(minuteCopy.disabled)return;rpCopyCommand(minuteCopy.dataset.command||'');minuteCopy.textContent='Copied - paste on robot';setTimeout(function(){minuteCopy.textContent='Copy setup command';},2200);});
|
|
2756
2927
|
if(copyAll)copyAll.addEventListener('click',function(){rpCopyCommand(rpVisibleCommands.map(function(x){return '# '+x.title+'\\n'+x.command;}).join('\\n\\n'));});
|
|
2757
2928
|
if(grid)grid.addEventListener('click',function(ev){var copy=ev.target.closest('[data-command-copy]'),del=ev.target.closest('[data-command-delete]');if(copy){var id=copy.getAttribute('data-command-copy'),item=rpVisibleCommands.find(function(x){return x.id===id;});if(item)rpCopyCommand(item.command);}if(del){var removeId=del.getAttribute('data-command-delete');rpSaveCustomCommands(rpCustomCommands().filter(function(x){return String(x.id)!==removeId;}));renderCommandSheet();showOut('custom command deleted');}});
|
|
2758
2929
|
if(form)form.addEventListener('submit',function(ev){ev.preventDefault();var title=$('rp-command-custom-title').value.trim(),command=$('rp-command-custom-command').value.trim();if(!title||!command)return;var list=rpCustomCommands();list.push({id:'custom-'+Date.now(),title:title,category:$('rp-command-custom-category').value.trim()||'Custom',description:$('rp-command-custom-description').value.trim(),command:command});rpSaveCustomCommands(list);$('rp-command-custom-title').value='';$('rp-command-custom-description').value='';$('rp-command-custom-command').value='';renderCommandSheet();showOut('custom command saved in this browser');});
|
|
2759
|
-
renderCommandSheet();
|
|
2930
|
+
renderOneMinuteSetup();renderCommandSheet();
|
|
2760
2931
|
}
|
|
2761
2932
|
initCommandSheet();
|
|
2762
2933
|
|
|
2763
2934
|
var rpSubTab = 'overview';
|
|
2764
2935
|
var rpIntervals = {}; // sub-tab name -> setInterval handle
|
|
2765
2936
|
var rpHistoryAll = []; // full recent-session list (we paginate client-side)
|
|
2766
|
-
function setRpSubTab(name){
|
|
2767
|
-
if(!name) return;
|
|
2768
|
-
rpSubTab = name;
|
|
2769
|
-
var
|
|
2937
|
+
function setRpSubTab(name){
|
|
2938
|
+
if(!name) return;
|
|
2939
|
+
rpSubTab = name;
|
|
2940
|
+
var globalBtns=document.querySelectorAll('[data-global-sub]');
|
|
2941
|
+
for(var gi=0;gi<globalBtns.length;gi++){
|
|
2942
|
+
var globalActive=globalBtns[gi].getAttribute('data-global-sub')===name;
|
|
2943
|
+
globalBtns[gi].classList.toggle('active',globalActive);
|
|
2944
|
+
if(globalActive) globalBtns[gi].setAttribute('aria-current','page'); else globalBtns[gi].removeAttribute('aria-current');
|
|
2945
|
+
}
|
|
2946
|
+
var btns = document.querySelectorAll('.rp-subtab');
|
|
2770
2947
|
for(var i=0;i<btns.length;i++){
|
|
2771
2948
|
var b=btns[i], sub=b.getAttribute('data-sub');
|
|
2772
2949
|
var isActive = sub===name;
|
|
@@ -2791,7 +2968,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2791
2968
|
if(name==='history'){ rpHistoryPage=0; pollHistory(); }
|
|
2792
2969
|
if(name==='audit'){ pollAuditLog(); rpIntervals.audit = setInterval(pollAuditLog, 6000); }
|
|
2793
2970
|
if(name==='fleet'){ pollFleet(); rpIntervals.fleet = setInterval(pollFleet, 5000); }
|
|
2794
|
-
if(name==='commands'){ renderCommandSheet(); }
|
|
2971
|
+
if(name==='commands'){ renderOneMinuteSetup(); renderCommandSheet(); }
|
|
2795
2972
|
if(name==='setup'){ pollSettings(); loadCharacterPresets(); loadVoiceStacks(); pollRuntimeHealth(); }
|
|
2796
2973
|
// remember last sub-tab in URL hash
|
|
2797
2974
|
try{ history.replaceState(null,'','#robopark/'+name); }catch(e){}
|
|
@@ -3055,14 +3232,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
3055
3232
|
if(!filtered.length){ tbody.innerHTML=''; wrap.style.display='none'; empty.style.display='block'; }
|
|
3056
3233
|
else { empty.style.display='none'; wrap.style.display='block'; }
|
|
3057
3234
|
var html='';
|
|
3058
|
-
for(var i=0;i<filtered.length;i++){
|
|
3059
|
-
var s = filtered[i];
|
|
3060
|
-
var rid = s.robot_id || '';
|
|
3061
|
-
var
|
|
3235
|
+
for(var i=0;i<filtered.length;i++){
|
|
3236
|
+
var s = filtered[i];
|
|
3237
|
+
var rid = s.robot_id || '';
|
|
3238
|
+
var sessionDevice=schedulerDeviceByReference(rid);
|
|
3239
|
+
var robotLabel=(sessionDevice&&sessionDevice.name)||s.robot_name||s.name||rid;
|
|
3240
|
+
var characterLabel=(sessionDevice&&sessionDevice.character_id)||s.character_name||s.character_id||'';
|
|
3241
|
+
var sid = s.session_id || '';
|
|
3062
3242
|
var started = s.started_at ? Date.parse(s.started_at) : null;
|
|
3063
3243
|
var elapsed = started ? Math.floor((Date.now()-started)/1000) : null;
|
|
3064
3244
|
html += '<tr>'
|
|
3065
|
-
+ '<td><b>'+esc(rid)+'</
|
|
3245
|
+
+ '<td><b>'+esc(robotLabel)+'</b><span class="t-abs">'+esc(rid)+(characterLabel?(' · '+esc(characterLabel)):'')+'</span></td>'
|
|
3066
3246
|
+ '<td><span class="sid" title="'+esc(sid)+'">'+esc(sid)+'</span></td>'
|
|
3067
3247
|
+ '<td><span class="t-rel"><span class="live-dot"></span>'+esc(rpFmtRelative(started))+'</span><span class="t-abs">'+esc(rpFmtAbs(started))+'</span></td>'
|
|
3068
3248
|
+ '<td data-started="'+started+'">'+esc(rpFmtDuration(elapsed))+'</td>'
|
|
@@ -3098,7 +3278,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
3098
3278
|
if(!b) return;
|
|
3099
3279
|
var act = b.getAttribute('data-act');
|
|
3100
3280
|
if(act==='end-session'){ endSessionFromTable(b.getAttribute('data-sid'), b.getAttribute('data-robot')); }
|
|
3101
|
-
if(act==='open-robot'){ var rid=b.getAttribute('data-robot'); if(rid){
|
|
3281
|
+
if(act==='open-robot'){ var rid=b.getAttribute('data-robot'); if(rid){ selectTab('park'); setTimeout(function(){Park.openDrawerForRobot(rid);},0); } }
|
|
3102
3282
|
});
|
|
3103
3283
|
})();
|
|
3104
3284
|
|
|
@@ -4319,13 +4499,19 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
4319
4499
|
// looks up each robot node by its slot aliases and attaches a
|
|
4320
4500
|
// status + telemetry field used by drawRobot() to drive the
|
|
4321
4501
|
// status animations.
|
|
4322
|
-
function updateTelemetry(map){
|
|
4323
|
-
_telemetryByName = map || {};
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4502
|
+
function updateTelemetry(map){
|
|
4503
|
+
_telemetryByName = map || {};
|
|
4504
|
+
if(lastStatus) onStatus(lastStatus);
|
|
4505
|
+
}
|
|
4506
|
+
function lookupTelemetry(node){
|
|
4507
|
+
if(!node) return null;
|
|
4327
4508
|
// The hub is never a per-robot telemetry row — only robot slots are.
|
|
4328
|
-
if(node.kind !== 'robot') return null;
|
|
4509
|
+
if(node.kind !== 'robot') return null;
|
|
4510
|
+
var exactIds=[node.schedulerDeviceId,node.schedulerRobotId];
|
|
4511
|
+
for(var ei=0;ei<exactIds.length;ei++){
|
|
4512
|
+
var exactKey=String(exactIds[ei]||'').toLowerCase();
|
|
4513
|
+
if(exactKey&&_telemetryByName[exactKey]) return _telemetryByName[exactKey];
|
|
4514
|
+
}
|
|
4329
4515
|
// Try every alias the slot was built with: slot.label, slot.id,
|
|
4330
4516
|
// slot.sub, slot.keys, and the matched mesh node's displayName.
|
|
4331
4517
|
var slot = null;
|
|
@@ -4401,11 +4587,22 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
4401
4587
|
}
|
|
4402
4588
|
|
|
4403
4589
|
function layout(src){
|
|
4404
|
-
var list=(src||[]).slice(), used={}, out=[];
|
|
4405
|
-
function prevHit(id){ for(var p=0;p<nodes.length;p++){ if(nodes[p].id===id) return nodes[p]._hit; } return null; }
|
|
4406
|
-
function bind(slot, wx, wy, kind){
|
|
4407
|
-
var match=null;
|
|
4408
|
-
|
|
4590
|
+
var list=(src||[]).slice(), used={}, out=[];
|
|
4591
|
+
function prevHit(id){ for(var p=0;p<nodes.length;p++){ if(nodes[p].id===id) return nodes[p]._hit; } return null; }
|
|
4592
|
+
function bind(slot, wx, wy, kind){
|
|
4593
|
+
var match=null;
|
|
4594
|
+
if(kind!=='hub'){
|
|
4595
|
+
for(var exactIndex=0;exactIndex<list.length;exactIndex++){
|
|
4596
|
+
var exactNode=list[exactIndex];
|
|
4597
|
+
if(used[exactNode.nodeId]) continue;
|
|
4598
|
+
if(String(exactNode.parkSlotId||'').toLowerCase()===String(slot.id||'').toLowerCase()){
|
|
4599
|
+
match=exactNode;
|
|
4600
|
+
break;
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
for(var i=0;i<list.length;i++){ var n=list[i]; if(used[n.nodeId]) continue;
|
|
4605
|
+
if(match) break;
|
|
4409
4606
|
var nm=(n.displayName||n.nodeId||'').toLowerCase(), isHub=kindOf(n)==='hub';
|
|
4410
4607
|
if(kind==='hub'){ if(isHub){ match=n; break; } }
|
|
4411
4608
|
else if(!isHub){ for(var k=0;k<slot.keys.length;k++){ if(slot.keys[k] && nm.indexOf(slot.keys[k])>-1){ match=n; break; } } if(match) break; }
|
|
@@ -4420,9 +4617,14 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
4420
4617
|
load: (match&&match.load!=null)?match.load:0,
|
|
4421
4618
|
platform: match?(match.platform||''):'', version: match?(match.version||'?'):'—',
|
|
4422
4619
|
site: match?(match.site||''):'', caps: match?(match.capabilities||[]):[],
|
|
4423
|
-
schedulerRobotId: match?(match.schedulerRobotId||match.schedulerDeviceId||''):'',
|
|
4424
|
-
schedulerDeviceId: match?(match.schedulerDeviceId||match.schedulerRobotId||''):'',
|
|
4425
|
-
|
|
4620
|
+
schedulerRobotId: match?(match.schedulerRobotId||match.schedulerDeviceId||''):'',
|
|
4621
|
+
schedulerDeviceId: match?(match.schedulerDeviceId||match.schedulerRobotId||''):'',
|
|
4622
|
+
parkSlotId: match?(match.parkSlotId||slot.id):slot.id,
|
|
4623
|
+
telemKeys: [slot.label.toLowerCase(),String(slot.id||'').toLowerCase()]
|
|
4624
|
+
.concat(slot.keys||[])
|
|
4625
|
+
.concat(match?(match.telemKeys||[]):[])
|
|
4626
|
+
.concat(match?[(match.displayName||'').toLowerCase(),String(match.schedulerDeviceId||'').toLowerCase(),String(match.schedulerRobotId||'').toLowerCase()]:[])
|
|
4627
|
+
.filter(function(value,index,all){return value&&all.indexOf(value)===index;}),
|
|
4426
4628
|
// slotId is the stable handle we use to persist operator-
|
|
4427
4629
|
// chosen positions. slotNx/slotNy are the preset's default
|
|
4428
4630
|
// coordinates so the user can always reset back.
|
|
@@ -4447,12 +4649,19 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
4447
4649
|
SLOTS.forEach(function(s){ var w=W2(s.nx,s.ny); out.push(bind(s,w[0],w[1],'robot')); });
|
|
4448
4650
|
// Any extra live node that matched no pad → line it along the near edge.
|
|
4449
4651
|
var ex=0;
|
|
4450
|
-
list.forEach(function(n){ if(used[n.nodeId]) return; used[n.nodeId]=1;
|
|
4451
|
-
var w=W2(0.20+ex*0.13, 0.90); ex++;
|
|
4452
|
-
|
|
4453
|
-
sub:n.role||'', connected:!!n.connected, load:(n.load!=null?n.load:0), platform:n.platform||'', version:n.version||'?',
|
|
4454
|
-
site:n.site||'', caps:n.capabilities||[],
|
|
4455
|
-
|
|
4652
|
+
list.forEach(function(n){ if(used[n.nodeId]) return; used[n.nodeId]=1;
|
|
4653
|
+
var w=W2(0.20+ex*0.13, 0.90); ex++;
|
|
4654
|
+
var extraNode={ id:n.nodeId, kind:kindOf(n), label:(n.displayName||n.nodeId), name:(n.displayName||n.nodeId),
|
|
4655
|
+
sub:n.role||'', connected:!!n.connected, load:(n.load!=null?n.load:0), platform:n.platform||'', version:n.version||'?',
|
|
4656
|
+
site:n.site||'', caps:n.capabilities||[], schedulerRobotId:n.schedulerRobotId||'', schedulerDeviceId:n.schedulerDeviceId||'', parkSlotId:n.parkSlotId||'',
|
|
4657
|
+
telemKeys:[(n.displayName||'').toLowerCase()].concat(n.telemKeys||[]), wx:w[0], wy:w[1], _hit:prevHit(n.nodeId) };
|
|
4658
|
+
if(extraNode.kind==='robot'){
|
|
4659
|
+
var extraTele=lookupTelemetry(extraNode), extraStatus=computeStatus(extraTele,extraNode);
|
|
4660
|
+
extraNode.telemetry=extraTele; extraNode.status=extraStatus.status; extraNode.readiness=extraStatus.readiness;
|
|
4661
|
+
extraNode.readinessMessage=extraStatus.readinessMessage; extraNode.sessionAge=extraStatus.sessionAge; extraNode.activityAge=extraStatus.activityAge;
|
|
4662
|
+
}
|
|
4663
|
+
out.push(extraNode);
|
|
4664
|
+
});
|
|
4456
4665
|
return out;
|
|
4457
4666
|
}
|
|
4458
4667
|
|
|
@@ -6795,7 +7004,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6795
7004
|
// dashboard would only know about local (this-session) drags
|
|
6796
7005
|
// and would reset to slot defaults on every reload.
|
|
6797
7006
|
try{
|
|
6798
|
-
getJson('/api/character-presets'
|
|
7007
|
+
getJson('/robopark/api/character-presets').then(function(list){
|
|
6799
7008
|
if(Array.isArray(list)){
|
|
6800
7009
|
for(var pi=0; pi<list.length; pi++){
|
|
6801
7010
|
var p = list[pi];
|
|
@@ -6894,7 +7103,22 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6894
7103
|
micAnalyser=null;
|
|
6895
7104
|
}
|
|
6896
7105
|
|
|
6897
|
-
//
|
|
7106
|
+
// Resolve a Park pad to a real character preset. Mesh and scheduler node
|
|
7107
|
+
// ids are infrastructure identities and must never be emitted as presets.
|
|
7108
|
+
function resolvePadCharacterPreset(n){
|
|
7109
|
+
var device=schedulerDevice(n)||{},row=schedRow(n)||{};
|
|
7110
|
+
var candidates=[device.character_id,row.character_id,n.character_id,n.characterId,n.label,n.name,n.id];
|
|
7111
|
+
for(var ci=0;ci<candidates.length;ci++){
|
|
7112
|
+
var candidate=String(candidates[ci]||'').trim().toLowerCase();if(!candidate)continue;
|
|
7113
|
+
for(var pi=0;pi<characterPresets.length;pi++){
|
|
7114
|
+
var preset=characterPresets[pi],presetId=String(preset.id||'').trim(),presetName=String(preset.name||'').trim().toLowerCase();
|
|
7115
|
+
if(candidate===presetId.toLowerCase()||candidate===presetName)return presetId;
|
|
7116
|
+
}
|
|
7117
|
+
}
|
|
7118
|
+
return '';
|
|
7119
|
+
}
|
|
7120
|
+
|
|
7121
|
+
// ── setup flow for unjoined park pads ──
|
|
6898
7122
|
function submitPadSetup(n){
|
|
6899
7123
|
var name=$('pk-setup-name').value.trim() || (n.label||n.id||'robot').toString().toLowerCase();
|
|
6900
7124
|
var body={
|
|
@@ -6911,7 +7135,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6911
7135
|
$('pk-setup-token').value=token;
|
|
6912
7136
|
$('pk-setup-result').style.display='block';
|
|
6913
7137
|
var command=$('pk-setup-clicmd'),label=$('pk-setup-clicmd-label');
|
|
6914
|
-
if(command)command.value=buildPadRobotCommand(name,body.character_id
|
|
7138
|
+
if(command)command.value=buildPadRobotCommand(name,body.character_id,token);
|
|
6915
7139
|
if(label)label.textContent='run this ON THE ROBOT (uses the token minted above)';
|
|
6916
7140
|
showOut('robot added: '+esc((j&&j.name)||name)+'\\ntoken: '+esc(token));
|
|
6917
7141
|
pollRoboparkQuiet();
|
|
@@ -7035,12 +7259,15 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7035
7259
|
if(visionRetryTimer){clearTimeout(visionRetryTimer);visionRetryTimer=null;}
|
|
7036
7260
|
var img=$('pk-vision-img'); if(img){ img.src=''; img.style.display='none'; }
|
|
7037
7261
|
}
|
|
7038
|
-
function pollRoboparkQuiet(){
|
|
7039
|
-
fetch('/robopark/api/telemetry'+QS,{cache:'no-store'}).then(function(r){return r.json();}).then(function(d){
|
|
7040
|
-
var list=(Array.isArray(d)?d:(d&&d.robots)||[]);
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7262
|
+
function pollRoboparkQuiet(){
|
|
7263
|
+
fetch('/robopark/api/telemetry'+QS,{cache:'no-store'}).then(function(r){return r.json();}).then(function(d){
|
|
7264
|
+
var list=(Array.isArray(d)?d:(d&&d.robots)||[]);
|
|
7265
|
+
lastSchedulerPollAt=Date.now();
|
|
7266
|
+
lastRobots=list;
|
|
7267
|
+
indexRoboparkTelemetry(list);
|
|
7268
|
+
updateTelemetry(roboparkByName);
|
|
7269
|
+
refreshDrawer();
|
|
7270
|
+
}).catch(function(){});
|
|
7044
7271
|
}
|
|
7045
7272
|
function schedulerDevice(n){
|
|
7046
7273
|
// Deliberately does not call schedRow: telemetry can contain a legacy
|
|
@@ -7437,11 +7664,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7437
7664
|
var SHELL_OUTPUT_LIMIT = 64 * 1024;
|
|
7438
7665
|
|
|
7439
7666
|
function buildDrawer(n){
|
|
7440
|
-
var b=$('pk-body');
|
|
7441
|
-
if(n.kind==='robot' && !n.connected){
|
|
7442
|
-
var defaultName=(n.label||n.id||'robot').toString().toLowerCase().replace(/\\s+/g,'');
|
|
7443
|
-
var
|
|
7444
|
-
|
|
7667
|
+
var b=$('pk-body');
|
|
7668
|
+
if(n.kind==='robot' && !n.connected){
|
|
7669
|
+
var defaultName=(n.label||n.id||'robot').toString().toLowerCase().replace(/\\s+/g,'');
|
|
7670
|
+
var defaultCharacter=resolvePadCharacterPreset(n);
|
|
7671
|
+
var presetOptions='', voiceOptions='';
|
|
7672
|
+
for(var i=0;i<characterPresets.length;i++){ var p=characterPresets[i]; presetOptions += '<option value="'+esc(p.id||'')+'"'+((p.id===defaultCharacter)?' selected':'')+'>'+esc(p.name||p.id||'')+'</option>'; }
|
|
7445
7673
|
for(var v=0;v<voiceStacks.length;v++){ var s=voiceStacks[v]; voiceOptions += '<option value="'+esc(s.id||'')+'">'+esc(s.name||s.id||'')+'</option>'; }
|
|
7446
7674
|
b.innerHTML =
|
|
7447
7675
|
sect('🅿 park pad · '+esc(n.label),
|
|
@@ -7467,9 +7695,9 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7467
7695
|
+ '</div>'
|
|
7468
7696
|
+ '<div class="pk-sect" id="pk-setup-result" style="display:none;"><h4>Enrollment token (copy once)</h4><input id="pk-setup-token" readonly style="margin-bottom:0.4rem;"><button class="rp-btn" id="pk-setup-copy">Copy</button></div>');
|
|
7469
7697
|
var setupBtn=$('pk-setup-btn'); if(setupBtn) setupBtn.addEventListener('click', function(){ submitPadSetup(n); });
|
|
7470
|
-
var copyBtn=$('pk-setup-copy'); if(copyBtn) copyBtn.addEventListener('click', function(){ var t=$('pk-setup-token'); if(t){ t.select(); try{document.execCommand('copy');}catch(e){} showOut('token copied'); } });
|
|
7698
|
+
var copyBtn=$('pk-setup-copy'); if(copyBtn) copyBtn.addEventListener('click', function(){ var t=$('pk-setup-token'); if(t){ t.select(); try{document.execCommand('copy');}catch(e){} showOut('token copied'); } });
|
|
7471
7699
|
function refreshPadCliCommand(){
|
|
7472
|
-
var nm=$('pk-setup-name').value.trim()||defaultName; var cid=$('pk-setup-char').value||
|
|
7700
|
+
var nm=$('pk-setup-name').value.trim()||defaultName; var cid=$('pk-setup-char').value||'';
|
|
7473
7701
|
var el=$('pk-setup-clicmd'),label=$('pk-setup-clicmd-label');if(el)el.value=buildPadRobotCommand(nm,cid,'');if(label)label.textContent='or run this ON THE ROBOT (mints its own token)';
|
|
7474
7702
|
}
|
|
7475
7703
|
var padNameEl=$('pk-setup-name'); if(padNameEl) padNameEl.addEventListener('input', refreshPadCliCommand);
|
|
@@ -7519,7 +7747,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7519
7747
|
+ buildShellSection(schedId(n))
|
|
7520
7748
|
+ sect('🎭 character / voice','<div class="pk-note" id="pk-eff-note" style="margin-bottom:0.45rem;">loading effective config…</div><div class="rp-field"><label>Character preset</label><select class="rp-char" id="pk-eff-char" data-robot="'+esc(schedId(n))+'">'+effPresetOptions+'</select></div><div class="rp-field"><label>Voice stack</label><select class="rp-voice" id="pk-eff-voice" data-robot="'+esc(schedId(n))+'">'+effVoiceOptions+'</select></div><div class="rp-config-actions" style="margin-top:.45rem"><button class="rp-btn primary" id="pk-eff-save" data-robot="'+esc(schedId(n))+'">Apply to robot</button><button class="rp-btn" id="pk-edit-character">Edit character</button><button class="rp-btn" id="pk-edit-stack">Edit stack</button><button class="rp-btn" id="pk-new-stack">New stack</button></div>');
|
|
7521
7749
|
b.innerHTML += sect('Microphone setup suite','<div class="pk-mic-suite"><div class="pk-mic-health" id="pk-mic-health"><div class="pk-mic-check"><div class="k">Hardware lock</div><div class="v">USB mic hw:3,0</div></div><div class="pk-mic-check"><div class="k">PCM capture</div><div class="v">not tested</div></div><div class="pk-mic-check"><div class="k">Production track</div><div class="v">waiting</div></div></div><div class="rp-form" style="grid-template-columns:1fr 1fr;gap:.4rem"><div class="rp-field"><label>Ground-truth capture</label><select id="pk-mic-duration"><option value="2">2 seconds</option><option value="4" selected>4 seconds</option><option value="8">8 seconds</option></select></div><div class="rp-field"><label>Locked input</label><div class="pk-note" style="margin:.45rem 0 0">USB microphone (plughw:3,0)</div></div></div><div class="pk-ctl"><button class="rp-btn primary" id="pk-mic-groundtruth">Run raw mic capture</button><button class="rp-btn" id="pk-mic-roundtrip">Run speaker + mic test</button><button class="rp-btn" id="pk-mic-apply">Reapply production profile</button></div><div class="pk-note" id="pk-mic-test-state">Raw capture bypasses LiveKit and tests the exact ALSA path. Round-trip then verifies the physical speaker-to-mic path.</div><div id="pk-mic-test-result"></div></div>');
|
|
7522
|
-
b.innerHTML += sect('Motor registry +
|
|
7750
|
+
b.innerHTML += sect('Motor registry + sequences','<div class="pk-note">Safe workflow: discover wired relays, build a short sequence, save, then test. Unknown GPIO pins are never pulsed.</div><div class="rp-field"><label>Robot-local motor server</label><input id="pk-motor-url" value="http://127.0.0.1:8001" readonly></div><div class="pk-ctl"><button class="rp-btn primary" id="pk-motor-discover">Discover wired relays</button><button class="rp-btn" id="pk-motor-add">Add relay manually</button></div><div class="pk-motor-registry" id="pk-motor-registry"></div><div class="rp-form" style="grid-template-columns:1fr 1fr;margin-top:.7rem"><div class="rp-field"><label>Saved sequence</label><select id="pk-motor-sequence"></select></div><div class="rp-field"><label>Sequence name</label><input id="pk-motor-sequence-name" placeholder="Greeting motion"></div></div><div class="pk-ctl"><button class="rp-btn" id="pk-motor-duplicate">Duplicate sequence</button><button class="rp-btn danger" id="pk-motor-delete">Delete sequence</button></div><div class="pk-motor-canvas" id="pk-motor-canvas"></div><div class="pk-ctl"><button class="rp-btn" id="pk-motor-step-add">+ Add pulse</button><button class="rp-btn primary" id="pk-motor-save">Save</button><button class="rp-btn" id="pk-motor-run">Run sequence</button><button class="rp-btn danger" id="pk-motor-stop">STOP ALL</button><button class="rp-btn danger" id="pk-motor-test-all">Test every registered relay</button></div><label class="pk-prodtoggle"><input type="checkbox" id="pk-motor-greeting"><span>Run this sequence with the greeting</span></label><div class="pk-note">Safety limits: registered BCM 2-27 only, one relay at a time, maximum 60 seconds per sequence.</div><div class="pk-motor-status" id="pk-motor-status">Loading motor profile...</div>');
|
|
7523
7751
|
b.innerHTML += sect('Production camera + audio','<div class="pk-note" id="pk-device-search" style="margin-bottom:.55rem">Hardware locked for launch. Camera, microphone, and speaker cannot drift between restarts.</div><div class="rp-field"><label>Camera input</label><select id="pk-video-device" disabled></select></div><div class="rp-field"><label>Microphone input</label><select id="pk-audio-input" disabled></select></div><div class="rp-field"><label>Speaker output</label><select id="pk-audio-output" disabled></select></div><button class="rp-btn primary" id="pk-device-test-speaker" style="margin-top:0.35rem">Test USB speaker now</button><div class="rp-field"><label>Preset greeting phrases <span class="pk-note">one per line, cached per voice</span></label><textarea id="pk-greeting-phrases" rows="3" placeholder="Hi, I am Jaguar! / Welcome to RoboPark."></textarea></div><button class="rp-btn" id="pk-device-save" style="margin-top:0.35rem">Save greetings + enforce hardware</button><div class="pk-note" id="pk-device-note">waiting for robot heartbeat inventory</div>');
|
|
7524
7752
|
// Put live hardware selection beside the camera and audio tests,
|
|
7525
7753
|
// ahead of persona and service administration in the Park drawer.
|
|
@@ -7546,13 +7774,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7546
7774
|
var micRound=$('pk-mic-roundtrip');if(micRound)micRound.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}runMicRoundtrip(n);});
|
|
7547
7775
|
var micApply=$('pk-mic-apply');if(micApply)micApply.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}saveDeviceSelectors(n);});
|
|
7548
7776
|
var motorAdd=$('pk-motor-add');if(motorAdd)motorAdd.addEventListener('click',function(){motorEditorAddMotor();});
|
|
7777
|
+
var motorDiscover=$('pk-motor-discover');if(motorDiscover)motorDiscover.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}discoverMotorRelays(n,false);});
|
|
7549
7778
|
var motorStep=$('pk-motor-step-add');if(motorStep)motorStep.addEventListener('click',function(){motorEditorAddStep();});
|
|
7779
|
+
var motorDuplicate=$('pk-motor-duplicate');if(motorDuplicate)motorDuplicate.addEventListener('click',function(){duplicateMotorSequence();});
|
|
7780
|
+
var motorDelete=$('pk-motor-delete');if(motorDelete)motorDelete.addEventListener('click',function(){deleteMotorSequence();});
|
|
7550
7781
|
var motorSave=$('pk-motor-save');if(motorSave)motorSave.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}saveMotorProfile(n);});
|
|
7551
7782
|
var motorRun=$('pk-motor-run');if(motorRun)motorRun.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}runMotorSequence(n);});
|
|
7552
7783
|
var motorTestAll=$('pk-motor-test-all');if(motorTestAll)motorTestAll.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}testAllMotorRelays(n);});
|
|
7784
|
+
var motorStop=$('pk-motor-stop');if(motorStop)motorStop.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}stopAllMotors(n);});
|
|
7553
7785
|
var motorSeq=$('pk-motor-sequence');if(motorSeq)motorSeq.addEventListener('change',function(){selectMotorSequence(motorSeq.value);});
|
|
7554
7786
|
var motorRegistry=$('pk-motor-registry');if(motorRegistry)motorRegistry.addEventListener('click',function(ev){var button=ev.target.closest('[data-motor-action]');if(!button)return;var index=Number(button.dataset.index);if(button.dataset.motorAction==='remove'){motorProfile.registry.splice(index,1);renderMotorEditor();}else if(button.dataset.motorAction==='test'){testMotorRelay(n,index);}});
|
|
7555
|
-
var motorCanvas=$('pk-motor-canvas');if(motorCanvas)motorCanvas.addEventListener('click',function(ev){var button=ev.target.closest('[data-step-remove]');if(button)
|
|
7787
|
+
var motorCanvas=$('pk-motor-canvas');if(motorCanvas)motorCanvas.addEventListener('click',function(ev){var button=ev.target.closest('[data-step-remove],[data-step-move]');if(!button)return;captureMotorEditor();var sequence=motorEditorSequence(),index=Number(button.dataset.stepRemove!=null?button.dataset.stepRemove:button.dataset.stepIndex);if(button.dataset.stepRemove!=null)sequence.steps.splice(index,1);else{var direction=button.dataset.stepMove==='up'?-1:1,target=index+direction;if(target>=0&&target<sequence.steps.length){var item=sequence.steps.splice(index,1)[0];sequence.steps.splice(target,0,item);}}renderMotorEditor();});
|
|
7556
7788
|
loadMotorProfile(n);
|
|
7557
7789
|
startCam(n); startMeters(n); loadEffectiveConfig(schedId(n)); refreshSupervisorPanel(n);
|
|
7558
7790
|
_bindShellSection(n);
|
|
@@ -7631,7 +7863,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7631
7863
|
var sequence=motorEditorSequence(),name=$('pk-motor-sequence-name'),greeting=$('pk-motor-greeting'),url=$('pk-motor-url');
|
|
7632
7864
|
if(name)name.value=sequence.name||'';if(greeting)greeting.checked=motorProfile.greeting_sequence_id===sequence.id;if(url)url.value=motorProfile.motor_server_url||'http://127.0.0.1:8001';
|
|
7633
7865
|
var motorOptions=(motorProfile.registry||[]).map(function(motor){return '<option value="'+esc(motor.id)+'">'+esc(motor.name+' | BCM '+motor.gpio)+'</option>';}).join('');
|
|
7634
|
-
canvas.innerHTML=(sequence.steps||[]).map(function(step,index){return '<div class="pk-motor-node"><div class="pk-motor-node-num">'+(index+1)+'</div><label>Relay<select data-step-motor>'+motorOptions.replace('value="'+esc(step.motor_id)+'"','value="'+esc(step.motor_id)+'" selected')+'</select></label><label>
|
|
7866
|
+
canvas.innerHTML=(sequence.steps||[]).map(function(step,index){return '<div class="pk-motor-node"><div class="pk-motor-node-num">Step '+(index+1)+'</div><label>Relay<select data-step-motor>'+motorOptions.replace('value="'+esc(step.motor_id)+'"','value="'+esc(step.motor_id)+'" selected')+'</select></label><label>Wait before pulse (ms)<input data-step-delay type="number" min="0" max="30000" step="50" value="'+esc(step.delay_ms||0)+'"></label><label>Pulse length (ms)<input data-step-duration type="number" min="50" max="10000" step="50" value="'+esc(step.duration_ms||300)+'"></label><div class="pk-ctl"><button class="rp-btn" data-step-move="up" data-step-index="'+index+'" '+(index===0?'disabled':'')+'>Left</button><button class="rp-btn" data-step-move="down" data-step-index="'+index+'" '+(index===(sequence.steps.length-1)?'disabled':'')+'>Right</button><button class="rp-btn danger" data-step-remove="'+index+'">Remove</button></div></div>';}).join('')||'<div class="pk-note">No pulses yet. Click + Add pulse.</div>';
|
|
7635
7867
|
}
|
|
7636
7868
|
function selectMotorSequence(id){
|
|
7637
7869
|
captureMotorEditor();
|
|
@@ -7640,20 +7872,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7640
7872
|
}
|
|
7641
7873
|
function motorEditorAddMotor(){captureMotorEditor();var number=motorProfile.registry.length+1;motorProfile.registry.push({id:'motor-'+number,name:'Motor '+number,gpio:Math.min(27,Math.max(2,number+4)),active_high:true,max_duration_ms:3000});renderMotorEditor();}
|
|
7642
7874
|
function motorEditorAddStep(){captureMotorEditor();if(!motorProfile.registry.length){motorStatus('Register a motor before adding a timed node','bad');return;}motorEditorSequence().steps.push({motor_id:motorProfile.registry[0].id,delay_ms:0,duration_ms:300});renderMotorEditor();}
|
|
7643
|
-
function
|
|
7875
|
+
function duplicateMotorSequence(){captureMotorEditor();var source=motorEditorSequence(),copy=JSON.parse(JSON.stringify(source));copy.id=slugify(source.name+' copy')+'-'+Date.now();copy.name=(source.name||'Sequence')+' copy';motorProfile.sequences.push(copy);motorSequenceId=copy.id;renderMotorEditor();motorStatus('Sequence duplicated. Save to persist it.');}
|
|
7876
|
+
function deleteMotorSequence(){captureMotorEditor();if(motorProfile.sequences.length<=1){motorStatus('Keep at least one sequence. Clear its pulses instead.','bad');return;}if(!window.confirm('Delete this saved sequence?'))return;var index=motorProfile.sequences.findIndex(function(item){return item.id===motorSequenceId;});if(index<0)return;motorProfile.sequences.splice(index,1);if(motorProfile.greeting_sequence_id===motorSequenceId)motorProfile.greeting_sequence_id=null;motorSequenceId=motorProfile.sequences[0].id;renderMotorEditor();motorStatus('Sequence removed. Save to persist the change.');}
|
|
7877
|
+
function validateMotorProfile(requireRunnable){
|
|
7878
|
+
var ids={},gpios={},errors=[];
|
|
7879
|
+
(motorProfile.registry||[]).forEach(function(motor){if(!motor.id)errors.push('Every relay needs an id');if(ids[motor.id])errors.push('Duplicate relay id: '+motor.id);ids[motor.id]=true;if(!Number.isInteger(motor.gpio)||motor.gpio<2||motor.gpio>27)errors.push('GPIO for '+motor.name+' must be BCM 2-27');if(gpios[motor.gpio])errors.push('GPIO '+motor.gpio+' is assigned twice');gpios[motor.gpio]=true;});
|
|
7880
|
+
(motorProfile.sequences||[]).forEach(function(sequence){var total=0;(sequence.steps||[]).forEach(function(step){if(!ids[step.motor_id])errors.push(sequence.name+' uses an unknown relay');total+=Number(step.delay_ms||0)+Number(step.duration_ms||0);});if(total>60000)errors.push(sequence.name+' exceeds 60 seconds');});
|
|
7881
|
+
if(requireRunnable&&!motorEditorSequence().steps.length)errors.push('Add at least one pulse before running this sequence');
|
|
7882
|
+
if(errors.length){motorStatus(errors[0],'bad');throw new Error(errors[0]);}
|
|
7883
|
+
return true;
|
|
7884
|
+
}
|
|
7885
|
+
function motorStatus(message,kind){['pk-motor-status','pk-camera-ops-motor-status'].forEach(function(id){var host=$(id);if(host){host.textContent=message;host.className='pk-motor-status '+(kind||'');}});}
|
|
7886
|
+
function motorErrorMessage(error){var message=error&&error.message?error.message:String(error||'unknown error');return /(?:\s|^)404(?:\s|$)|not found/i.test(message)?'Motor API is not loaded on the running livekit-1 scheduler. Update and restart the scheduler, then retry.':message;}
|
|
7644
7887
|
function loadMotorProfile(n){
|
|
7645
7888
|
var robotId=schedId(n);motorStatus('Loading saved motor profile...');
|
|
7646
|
-
getJson('/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-profile').then(function(data){motorProfile={registry:data.registry||[],sequences:data.sequences||[],greeting_sequence_id:data.greeting_sequence_id||null,motor_server_url:(schedulerDevice(n)||{}).motor_server_url||data.motor_server_url||'http://127.0.0.1:8001'};if(!motorProfile.sequences.length)motorProfile.sequences=[{id:'greeting-motion',name:'Greeting motion',steps:[]}];motorSequenceId=motorProfile.greeting_sequence_id||motorProfile.sequences[0].id;renderMotorEditor();if(motorProfile.registry.length){motorStatus('Saved profile loaded | '+motorProfile.registry.length+' relay(s), '+motorProfile.sequences.length+' sequence(s)','ok');return;}motorStatus('No central registry; discovering robot-local relays...');return discoverMotorRelays(n,true);}).catch(function(error){motorStatus('Motor profile unavailable: '+error
|
|
7889
|
+
getJson('/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-profile').then(function(data){motorProfile={registry:data.registry||[],sequences:data.sequences||[],greeting_sequence_id:data.greeting_sequence_id||null,motor_server_url:(schedulerDevice(n)||{}).motor_server_url||data.motor_server_url||'http://127.0.0.1:8001'};if(!motorProfile.sequences.length)motorProfile.sequences=[{id:'greeting-motion',name:'Greeting motion',steps:[]}];motorSequenceId=motorProfile.greeting_sequence_id||motorProfile.sequences[0].id;renderMotorEditor();if(motorProfile.registry.length){motorStatus('Saved profile loaded | '+motorProfile.registry.length+' relay(s), '+motorProfile.sequences.length+' sequence(s)','ok');return;}motorStatus('No central registry; discovering robot-local relays...');return discoverMotorRelays(n,true);}).catch(function(error){motorStatus('Motor profile unavailable: '+motorErrorMessage(error),'bad');});
|
|
7647
7890
|
}
|
|
7648
7891
|
function saveMotorProfile(n){
|
|
7649
7892
|
captureMotorEditor();var robotId=schedId(n),url=motorProfile.motor_server_url;
|
|
7893
|
+
try{validateMotorProfile(false);}catch(error){return Promise.reject(error);}
|
|
7650
7894
|
var parsedUrl=null;try{parsedUrl=new URL(url);}catch(e){}
|
|
7651
7895
|
if(!parsedUrl||parsedUrl.protocol!=='http:'||(parsedUrl.hostname!=='127.0.0.1'&&parsedUrl.hostname!=='localhost')||!parsedUrl.port){motorStatus('Motor server must be robot-local: http://127.0.0.1:PORT','bad');return Promise.reject(new Error('motor server must be localhost'));}
|
|
7652
7896
|
motorStatus('Validating and saving motor profile...');
|
|
7653
7897
|
return rpAction('PATCH','/robopark/api/devices/'+encodeURIComponent(robotId),{motor_server_url:url}).then(function(){return rpAction('PUT','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-profile',{registry:motorProfile.registry,sequences:motorProfile.sequences,greeting_sequence_id:motorProfile.greeting_sequence_id});}).then(function(data){motorProfile.registry=data.registry||[];motorProfile.sequences=data.sequences||[];motorProfile.greeting_sequence_id=data.greeting_sequence_id||null;renderMotorEditor();motorStatus('Profile saved. Greeting motion will start on first TTS playback.','ok');return data;}).catch(function(error){motorStatus('Save failed: '+error.message,'bad');throw error;});
|
|
7654
7898
|
}
|
|
7655
7899
|
function runMotorSequence(n){
|
|
7656
|
-
var robotId=schedId(n),sequence=motorEditorSequence();motorStatus('Saving and queueing '+sequence.name+'...');
|
|
7900
|
+
var robotId=schedId(n),sequence=motorEditorSequence();try{captureMotorEditor();validateMotorProfile(true);}catch(error){return;}motorStatus('Saving and queueing '+sequence.name+'...');
|
|
7657
7901
|
saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-sequences/'+encodeURIComponent(sequence.id)+'/run',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','motor-sequence');}).then(function(result){motorStatus(result.ok?'Sequence completed on robot.':'Sequence failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');}).catch(function(error){motorStatus('Run failed: '+error.message,'bad');});
|
|
7658
7902
|
}
|
|
7659
7903
|
function testMotorRelay(n,index){
|
|
@@ -7665,12 +7909,16 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7665
7909
|
return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/discover',{}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','motor-discover');}).then(function(result){
|
|
7666
7910
|
if(!result.ok)throw new Error(result.error||'robot motor discovery failed');
|
|
7667
7911
|
if(!result.registry||!result.registry.length)throw new Error('Robot motor server has no registered relays. Register the wired BCM pins once; unknown pins will not be pulsed.');
|
|
7668
|
-
motorProfile.registry=result.registry;renderMotorEditor();motorStatus('Discovered '+result.registry.length+'
|
|
7912
|
+
motorProfile.registry=result.registry;motorProfile.sequences=result.sequences||motorProfile.sequences;motorProfile.greeting_sequence_id=result.greeting_sequence_id||null;renderMotorEditor();motorStatus('Discovered and saved '+result.registry.length+' wired relay(s).','ok');return result;
|
|
7669
7913
|
});
|
|
7670
7914
|
}
|
|
7915
|
+
function stopAllMotors(n){
|
|
7916
|
+
var robotId=schedId(n);motorStatus('Sending all-off command...');
|
|
7917
|
+
return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/stop',{}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','motor-stop');}).then(function(result){motorStatus(result.ok?'All registered relays are OFF.':'Stop failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');}).catch(function(error){motorStatus('Stop failed: '+motorErrorMessage(error),'bad');});
|
|
7918
|
+
}
|
|
7671
7919
|
function testAllMotorRelays(n){
|
|
7672
7920
|
captureMotorEditor();var ready=motorProfile.registry.length?Promise.resolve():discoverMotorRelays(n,true);
|
|
7673
|
-
ready.then(function(){if(!window.confirm('Pulse all '+motorProfile.registry.length+' registered relays one at a time? Ensure the motor area is clear.'))return null;var robotId=schedId(n);motorStatus('Saving profile and testing '+motorProfile.registry.length+' registered GPIO relay(s), one at a time...');return saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/test-all?duration_ms=300&pause_ms=200',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','gpio-test-all');}).then(function(result){motorStatus(result.ok?'Full registered GPIO test completed: '+(result.completed_steps||[]).length+' relay(s).':'GPIO test failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');});}).catch(function(error){motorStatus('GPIO test failed: '+error
|
|
7921
|
+
ready.then(function(){if(!window.confirm('Pulse all '+motorProfile.registry.length+' registered relays one at a time? Ensure the motor area is clear.'))return null;var robotId=schedId(n);motorStatus('Saving profile and testing '+motorProfile.registry.length+' registered GPIO relay(s), one at a time...');return saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/test-all?duration_ms=300&pause_ms=200',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','gpio-test-all');}).then(function(result){motorStatus(result.ok?'Full registered GPIO test completed: '+(result.completed_steps||[]).length+' relay(s).':'GPIO test failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');});}).catch(function(error){motorStatus('GPIO test failed: '+motorErrorMessage(error),'bad');});
|
|
7674
7922
|
}
|
|
7675
7923
|
|
|
7676
7924
|
var pipelineHistoryPage=1;
|
|
@@ -7862,20 +8110,22 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7862
8110
|
var stages=[[currentStageLabel,true,'ACTIVE'],['Robot online',!!n.connected,'ready'],['Camera streaming',cameraLive,'ready'],['Motion detector armed',!!device.production_mode,'armed'],['Scheduler session',active,'active']],pipe=$('pk-camera-ops-pipeline');if(pipe)pipe.innerHTML=stages.map(function(s,index){return '<div class="'+(index===0?'fleet-stage-focus '+esc(currentStageStatus):'')+'"><i class="'+(s[1]?'ok':'warn')+'"></i><span>'+esc(s[0])+'</span><b>'+(s[1]?s[2]:'waiting')+'</b></div>';}).join('');
|
|
7863
8111
|
var inv=device.device_inventory||{},mh=inv.media_health||{},hardware=$('pk-camera-ops-hardware');if(hardware)hardware.innerHTML='<span>CPU</span><b>'+(hw.cpu&&hw.cpu.percent!=null?Math.round(hw.cpu.percent)+'%':'--')+'</b><span>Memory</span><b>'+(hw.memory&&hw.memory.percent!=null?Math.round(hw.memory.percent)+'%':'--')+'</b><span>Temperature</span><b>'+(hw.temperatureC!=null?Math.round(hw.temperatureC)+'C':'--')+'</b><span>Camera inputs</span><b>'+((inv.video||[]).length||0)+'</b><span>Microphones</span><b>'+((inv.audio_input||[]).length||0)+'</b><span>Speaker outputs</span><b>'+((inv.audio_output||[]).length||0)+'</b><span>Camera permission</span><b class="'+(mh.camera_access===false?'bad':'ok')+'">'+(mh.camera_access===false?'DENIED':mh.camera_access===true?'granted':'--')+'</b><span>Audio permission</span><b class="'+(mh.audio_access===false?'bad':'ok')+'">'+(mh.audio_access===false?'DENIED':mh.audio_access===true?'granted':'--')+'</b><span>Camera worker</span><b class="'+(mh.camera_stalled?'bad':'ok')+'">'+(mh.camera_stalled?'STALLED':mh.camera_worker?'streaming':'--')+'</b><span>Frame age</span><b>'+(mh.camera_frame_age_seconds!=null?Number(mh.camera_frame_age_seconds).toFixed(1)+'s':'--')+'</b><span>Selected camera</span><b>'+esc(device.video_device||'auto')+'</b><span>Selected microphone</span><b>'+esc(device.audio_device||'default')+'</b>';
|
|
7864
8112
|
var prod=$('pk-camera-ops-production');if(prod){prod.textContent=device.production_mode?'Disable production':'Enable production';prod.classList.toggle('primary',!!device.production_mode);}
|
|
8113
|
+
var motorSelect=$('pk-camera-ops-motor-sequence');if(motorSelect){var selected=motorSelect.value||motorSequenceId;motorSelect.innerHTML=(motorProfile.sequences||[]).map(function(sequence){return '<option value="'+esc(sequence.id)+'">'+esc(sequence.name||sequence.id)+'</option>';}).join('')||'<option value="">No saved sequences</option>';if(selected)motorSelect.value=selected;}
|
|
7865
8114
|
}
|
|
7866
8115
|
function reloadCameraOperations(){
|
|
7867
8116
|
if(!cameraOpsNode)return;var img=$('pk-camera-ops-img'),empty=$('pk-camera-ops-empty');if(!img)return;img.dataset.live='false';if(empty){empty.style.display='block';empty.textContent='opening RoboVision camera...';}
|
|
7868
8117
|
img.onload=function(){img.dataset.live='true';if(cameraOpsRetryTimer){clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=null;}if(empty)empty.style.display='none';refreshCameraOperations();};img.onerror=function(){var failedNode=cameraOpsNode;img.dataset.live='false';if(empty){empty.style.display='block';empty.textContent='camera relay unavailable / retrying';}refreshCameraOperations();if(cameraOpsRetryTimer)clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=setTimeout(function(){if(cameraOpsNode&&cameraOpsNode===failedNode)reloadCameraOperations();},3000);};img.src=cameraOpsFeedUrl(cameraOpsNode);refreshCameraOperations();
|
|
7869
8118
|
}
|
|
7870
8119
|
function openCameraOperations(n){
|
|
7871
|
-
cameraOpsNode=n;var host=$('pk-camera-ops');if(!host)return;host.classList.add('open');document.body.classList.add('camera-ops-open');var name=n.displayName||n.name||'robot',avatar=rpAvatarFor(name),av=$('pk-camera-ops-avatar');$('pk-camera-ops-name').textContent=name+' / live operations';$('pk-camera-ops-sub').textContent='RoboVision mesh relay / '+(n.connected?'connected':'offline');if(av){if(avatar){av.src=avatar+QS;av.style.display='block';}else{av.removeAttribute('src');av.style.display='none';}}reloadCameraOperations();if(cameraOpsTimer)clearInterval(cameraOpsTimer);cameraOpsTimer=setInterval(refreshCameraOperations,1000);
|
|
8120
|
+
cameraOpsNode=n;var host=$('pk-camera-ops');if(!host)return;host.classList.add('open');document.body.classList.add('camera-ops-open');var name=n.displayName||n.name||'robot',avatar=rpAvatarFor(name),av=$('pk-camera-ops-avatar');$('pk-camera-ops-name').textContent=name+' / live operations';$('pk-camera-ops-sub').textContent='RoboVision mesh relay / '+(n.connected?'connected':'offline');if(av){if(avatar){av.src=avatar+QS;av.style.display='block';}else{av.removeAttribute('src');av.style.display='none';}}loadMotorProfile(n);reloadCameraOperations();if(cameraOpsTimer)clearInterval(cameraOpsTimer);cameraOpsTimer=setInterval(refreshCameraOperations,1000);
|
|
7872
8121
|
}
|
|
7873
8122
|
function closeCameraOperations(){
|
|
7874
8123
|
var host=$('pk-camera-ops'),img=$('pk-camera-ops-img');if(host)host.classList.remove('open');document.body.classList.remove('camera-ops-open');if(cameraOpsRetryTimer){clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=null;}if(img){img.src='';img.dataset.live='false';}if(cameraOpsTimer){clearInterval(cameraOpsTimer);cameraOpsTimer=null;}cameraOpsNode=null;if(document.fullscreenElement&&document.exitFullscreen)document.exitFullscreen().catch(function(){});
|
|
7875
8124
|
}
|
|
7876
8125
|
(function bindCameraOperations(){
|
|
7877
|
-
var close=$('pk-camera-ops-close'),refresh=$('pk-camera-ops-refresh'),nativeBtn=$('pk-camera-ops-native'),trigger=$('pk-camera-ops-trigger'),prod=$('pk-camera-ops-production'),end=$('pk-camera-ops-end');
|
|
8126
|
+
var close=$('pk-camera-ops-close'),refresh=$('pk-camera-ops-refresh'),nativeBtn=$('pk-camera-ops-native'),trigger=$('pk-camera-ops-trigger'),prod=$('pk-camera-ops-production'),end=$('pk-camera-ops-end'),motorDiscover=$('pk-camera-ops-motor-discover'),motorRun=$('pk-camera-ops-motor-run'),motorStop=$('pk-camera-ops-motor-stop'),motorTest=$('pk-camera-ops-motor-test'),motorSelect=$('pk-camera-ops-motor-sequence');
|
|
7878
8127
|
if(close)close.addEventListener('click',closeCameraOperations);if(refresh)refresh.addEventListener('click',reloadCameraOperations);if(nativeBtn)nativeBtn.addEventListener('click',function(){var host=$('pk-camera-ops');if(host&&host.requestFullscreen)host.requestFullscreen().catch(function(e){showOut('fullscreen: '+e.message);});});if(trigger)trigger.addEventListener('click',function(){if(cameraOpsNode)triggerSession(schedId(cameraOpsNode));});if(prod)prod.addEventListener('click',function(){if(cameraOpsNode){var d=schedulerDevice(cameraOpsNode)||{};setProductionMode(cameraOpsNode,!d.production_mode);setTimeout(refreshCameraOperations,400);}});if(end)end.addEventListener('click',function(){if(cameraOpsNode)endSession(schedId(cameraOpsNode));});document.addEventListener('keydown',function(e){if(e.key==='Escape'&&cameraOpsNode)closeCameraOperations();});
|
|
8128
|
+
if(motorDiscover)motorDiscover.addEventListener('click',function(){if(cameraOpsNode)discoverMotorRelays(cameraOpsNode,false).catch(function(error){motorStatus('Discovery failed: '+error.message,'bad');});});if(motorRun)motorRun.addEventListener('click',function(){if(cameraOpsNode)runMotorSequence(cameraOpsNode);});if(motorStop)motorStop.addEventListener('click',function(){if(cameraOpsNode)stopAllMotors(cameraOpsNode);});if(motorTest)motorTest.addEventListener('click',function(){if(cameraOpsNode)testAllMotorRelays(cameraOpsNode);});if(motorSelect)motorSelect.addEventListener('change',function(){motorSequenceId=motorSelect.value;renderMotorEditor();refreshCameraOperations();});
|
|
7879
8129
|
})();
|
|
7880
8130
|
|
|
7881
8131
|
// ── cam: target a LiveKit track; fall back to a styled preview ──
|
|
@@ -8262,12 +8512,15 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
8262
8512
|
}
|
|
8263
8513
|
|
|
8264
8514
|
$('pk-close') && $('pk-close').addEventListener('click', closeDrawer);
|
|
8265
|
-
return { onStatus:onStatus, activate:activate, deactivate:deactivate, refreshDrawer:refreshDrawer, openDrawerForRobot:openDrawerForRobot, updateSlots:updateSlots, updateTelemetry:updateTelemetry };
|
|
8515
|
+
return { onStatus:onStatus, activate:activate, deactivate:deactivate, refreshDrawer:refreshDrawer, refreshTelemetry:pollRoboparkQuiet, openDrawerForRobot:openDrawerForRobot, updateSlots:updateSlots, updateTelemetry:updateTelemetry };
|
|
8266
8516
|
})();
|
|
8267
8517
|
|
|
8268
8518
|
poll();
|
|
8269
8519
|
setInterval(poll, 2000);
|
|
8270
|
-
setInterval(function(){
|
|
8520
|
+
setInterval(function(){
|
|
8521
|
+
if(activeTab==='robopark') pollRobopark();
|
|
8522
|
+
else if(activeTab==='park' && Park && Park.refreshTelemetry) Park.refreshTelemetry();
|
|
8523
|
+
}, 3000);
|
|
8271
8524
|
setInterval(loadParkSlots, 7000);
|
|
8272
8525
|
// Pre-load character presets and voice stacks on startup so the Park
|
|
8273
8526
|
// robot-detail drawer has populated dropdowns even if the user never visits the robopark tab.
|