infinicode 2.8.103 → 2.8.104
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.
|
@@ -1132,7 +1132,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1132
1132
|
<div class="rp-quickrow">
|
|
1133
1133
|
<div class="rp-field"><label>Robot name</label><input id="rb-name" placeholder="jaguar"></div>
|
|
1134
1134
|
<div class="rp-field"><label>Character preset</label><select id="rb-char"><option value="">— auto (matched from name) —</option></select></div>
|
|
1135
|
-
<div class="rp-field"><label>Robot network</label><select id="rb-network"><option value="
|
|
1135
|
+
<div class="rp-field"><label>Robot network</label><select id="rb-network"><option value="lan">LAN (onsite default)</option><option value="tailscale">Tailscale</option></select></div>
|
|
1136
1136
|
<div class="rp-field"><label>Mesh token</label><input id="rb-token" spellcheck="false" autocomplete="off" placeholder="required in generated command"></div>
|
|
1137
1137
|
<button class="rp-btn primary" id="rb-add" style="height:fit-content">Add robot</button>
|
|
1138
1138
|
</div>
|
|
@@ -1175,7 +1175,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1175
1175
|
<div class="rp-config-hero-actions"><button class="rp-btn" id="rp-command-reset">Reset environment</button><button class="rp-btn primary" id="rp-command-copy-all">Copy visible commands</button></div>
|
|
1176
1176
|
</div>
|
|
1177
1177
|
<div class="rp-command-env">
|
|
1178
|
-
<div class="rp-field"><label>Hub LAN IP</label><input id="rp-command-lan" value="192.168.
|
|
1178
|
+
<div class="rp-field"><label>Hub LAN IP</label><input id="rp-command-lan" value="192.168.0.9" spellcheck="false"></div>
|
|
1179
1179
|
<div class="rp-field"><label>Hub Tailscale IP / DNS</label><input id="rp-command-tail" value="100.84.147.24" spellcheck="false"></div>
|
|
1180
1180
|
<div class="rp-field"><label>Site</label><input id="rp-command-site" value="Tel Aviv"></div>
|
|
1181
1181
|
<div class="rp-field"><label>Robot name</label><input id="rp-command-robot" value="bmw" spellcheck="false"></div>
|
|
@@ -2262,10 +2262,11 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2262
2262
|
// needed to see it; 'robopark add-robot' mints its own token when run ──
|
|
2263
2263
|
function buildAddRobotCommand(name, characterId){
|
|
2264
2264
|
if(!name) return '';
|
|
2265
|
-
var
|
|
2265
|
+
var network=$('rb-network')&&$('rb-network').value||'lan';
|
|
2266
|
+
var commandEnv=rpCommandEnv();
|
|
2267
|
+
var hubHost=network==='tailscale'?commandEnv.tail:commandEnv.lan;
|
|
2268
|
+
var hubUrl=/^https?:\/\//i.test(hubHost)?hubHost.replace(/\\/+$/,''):'http://'+hubHost.replace(/\\/+$/,'')+':47913';
|
|
2266
2269
|
var schedulerUrl=hubUrl+'/robopark';
|
|
2267
|
-
var requestedNetwork=$('rb-network')&&$('rb-network').value||'auto';
|
|
2268
|
-
var network=requestedNetwork==='auto'?(/^100\\./.test(location.hostname)||/\\.ts\\.net$/i.test(location.hostname)?'tailscale':'lan'):requestedNetwork;
|
|
2269
2270
|
var argv=['robopark','add-robot',name];
|
|
2270
2271
|
if(characterId) argv.push('--character', characterId);
|
|
2271
2272
|
var site=$('rb-site')&&$('rb-site').value.trim();if(site)argv.push('--site',site);
|
|
@@ -2672,7 +2673,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2672
2673
|
var RP_COMMAND_ENV_KEY='rp-command-env-v1';
|
|
2673
2674
|
var RP_CUSTOM_COMMANDS_KEY='rp-custom-commands-v1';
|
|
2674
2675
|
var rpVisibleCommands=[];
|
|
2675
|
-
function rpCommandDefaults(){return {lan:'192.168.
|
|
2676
|
+
function rpCommandDefaults(){return {lan:'192.168.0.9',tail:'100.84.147.24',site:'Tel Aviv',robot:'bmw',enrollment:'',token:dashboardMeshToken()};}
|
|
2676
2677
|
function rpLoadStoredJson(key,fallback){try{var value=JSON.parse(localStorage.getItem(key)||'null');return value&&typeof value==='object'?value:fallback;}catch(e){return fallback;}}
|
|
2677
2678
|
function rpCommandEnv(){
|
|
2678
2679
|
var d=rpCommandDefaults(),s=rpLoadStoredJson(RP_COMMAND_ENV_KEY,{});
|
|
@@ -7611,7 +7612,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7611
7612
|
function motorStatus(message,kind){var host=$('pk-motor-status');if(host){host.textContent=message;host.className='pk-motor-status '+(kind||'');}}
|
|
7612
7613
|
function loadMotorProfile(n){
|
|
7613
7614
|
var robotId=schedId(n);motorStatus('Loading saved motor profile...');
|
|
7614
|
-
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||'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();motorStatus('Saved profile loaded | '+motorProfile.registry.length+' relay(s), '+motorProfile.sequences.length+' sequence(s)','ok');}).catch(function(error){motorStatus('Motor profile unavailable: '+error.message,'bad');});
|
|
7615
|
+
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.message,'bad');});
|
|
7615
7616
|
}
|
|
7616
7617
|
function saveMotorProfile(n){
|
|
7617
7618
|
captureMotorEditor();var robotId=schedId(n),url=motorProfile.motor_server_url;
|
|
@@ -7628,11 +7629,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7628
7629
|
captureMotorEditor();var motor=motorProfile.registry[index];if(!motor)return;var robotId=schedId(n);motorStatus('Saving profile before safe 300 ms '+motor.name+' test...');
|
|
7629
7630
|
saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/'+encodeURIComponent(motor.id)+'/test?duration_ms=300',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','motor-test');}).then(function(result){motorStatus(result.ok?motor.name+' relay test completed.':'Relay test failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');}).catch(function(error){motorStatus('Relay test failed: '+error.message,'bad');});
|
|
7630
7631
|
}
|
|
7632
|
+
function discoverMotorRelays(n,automatic){
|
|
7633
|
+
var robotId=schedId(n);motorStatus((automatic?'Automatically discovering':'Discovering')+' relays from the robot motor server...');
|
|
7634
|
+
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){
|
|
7635
|
+
if(!result.ok)throw new Error(result.error||'robot motor discovery failed');
|
|
7636
|
+
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.');
|
|
7637
|
+
motorProfile.registry=result.registry;renderMotorEditor();motorStatus('Discovered '+result.registry.length+' robot-local relay(s); saving centrally...','ok');return saveMotorProfile(n);
|
|
7638
|
+
});
|
|
7639
|
+
}
|
|
7631
7640
|
function testAllMotorRelays(n){
|
|
7632
|
-
captureMotorEditor();
|
|
7633
|
-
if(!window.confirm('Pulse all '+motorProfile.registry.length+' registered relays one at a time? Ensure the motor area is clear.'))return;
|
|
7634
|
-
var robotId=schedId(n);motorStatus('Saving profile and testing '+motorProfile.registry.length+' registered GPIO relay(s), one at a time...');
|
|
7635
|
-
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.message,'bad');});
|
|
7641
|
+
captureMotorEditor();var ready=motorProfile.registry.length?Promise.resolve():discoverMotorRelays(n,true);
|
|
7642
|
+
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.message,'bad');});
|
|
7636
7643
|
}
|
|
7637
7644
|
|
|
7638
7645
|
var pipelineHistoryPage=1;
|
|
@@ -50,11 +50,18 @@ function schedulerApiUrl(schedulerUrl, path, token) {
|
|
|
50
50
|
}
|
|
51
51
|
async function mintEnrollmentToken(schedulerUrl, name, characterId, token) {
|
|
52
52
|
const url = schedulerApiUrl(schedulerUrl, '/api/devices', token);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
let res;
|
|
54
|
+
try {
|
|
55
|
+
res = await fetch(url, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'content-type': 'application/json' },
|
|
58
|
+
body: JSON.stringify({ name, character_id: characterId }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const cause = error.cause;
|
|
63
|
+
throw new Error(`cannot reach scheduler through ${schedulerUrl}: ${cause?.message ?? error.message}`);
|
|
64
|
+
}
|
|
58
65
|
if (!res.ok) {
|
|
59
66
|
const text = await res.text().catch(() => '');
|
|
60
67
|
throw new Error(`${res.status}: ${text || res.statusText}`);
|
package/package.json
CHANGED
|
@@ -4131,6 +4131,22 @@ async def test_robot_motor(robot_id: str, motor_id: str, duration_ms: int = 300)
|
|
|
4131
4131
|
})
|
|
4132
4132
|
return {"queued": True, "request_id": request_id, "device_id": model.id}
|
|
4133
4133
|
|
|
4134
|
+
@app.post("/api/robots/{robot_id}/motors/discover")
|
|
4135
|
+
async def discover_robot_motors(robot_id: str):
|
|
4136
|
+
"""Import relays already registered by the robot-local motor server.
|
|
4137
|
+
|
|
4138
|
+
This is read-only on the robot. It never scans or pulses unregistered GPIO.
|
|
4139
|
+
The dashboard validates and persists the returned registry before testing.
|
|
4140
|
+
"""
|
|
4141
|
+
device = await _resolve_device_by_id_or_name(robot_id)
|
|
4142
|
+
model = _device_row_to_model(device)
|
|
4143
|
+
if not model.motor_server_url:
|
|
4144
|
+
raise HTTPException(409, "Robot has no motor_server_url")
|
|
4145
|
+
request_id = await _queue_shell_request_async(model.id, "motor_discover", "motor_server", {
|
|
4146
|
+
"motor_server_url": model.motor_server_url,
|
|
4147
|
+
})
|
|
4148
|
+
return {"queued": True, "request_id": request_id, "device_id": model.id}
|
|
4149
|
+
|
|
4134
4150
|
@app.post("/api/robots/{robot_id}/motors/test-all")
|
|
4135
4151
|
async def test_all_robot_motors(robot_id: str, duration_ms: int = 300, pause_ms: int = 200):
|
|
4136
4152
|
device = await _resolve_device_by_id_or_name(robot_id)
|
|
@@ -427,6 +427,8 @@ def _handle_shell_command(identity, cmd: dict) -> None:
|
|
|
427
427
|
result = _run_shell_command(params.get("name", ""), params)
|
|
428
428
|
elif kind == "speaker_test":
|
|
429
429
|
result = _speaker_roundtrip_test(params)
|
|
430
|
+
elif kind == "motor_discover":
|
|
431
|
+
result = _discover_motor_registry(params)
|
|
430
432
|
elif kind == "motor_sequence":
|
|
431
433
|
result = _run_motor_sequence(params)
|
|
432
434
|
else:
|
|
@@ -434,6 +436,45 @@ def _handle_shell_command(identity, cmd: dict) -> None:
|
|
|
434
436
|
_post_supervisor_output(identity, kind, service, result, request_id)
|
|
435
437
|
|
|
436
438
|
|
|
439
|
+
def _discover_motor_registry(params: dict) -> dict:
|
|
440
|
+
"""Read the robot-local motor registry without touching any GPIO output."""
|
|
441
|
+
import httpx
|
|
442
|
+
import re
|
|
443
|
+
|
|
444
|
+
base = str(params.get("motor_server_url") or "http://127.0.0.1:8001").rstrip("/")
|
|
445
|
+
if not (base.startswith("http://127.0.0.1:") or base.startswith("http://localhost:")):
|
|
446
|
+
return {"ok": False, "error": "motor server must be robot-local"}
|
|
447
|
+
try:
|
|
448
|
+
with httpx.Client(timeout=5.0) as client:
|
|
449
|
+
response = client.get(f"{base}/list-motors")
|
|
450
|
+
response.raise_for_status()
|
|
451
|
+
raw_motors = response.json().get("motors", [])
|
|
452
|
+
registry, used_ids, used_gpios = [], set(), set()
|
|
453
|
+
for index, raw in enumerate(raw_motors[:32]):
|
|
454
|
+
gpio = int(raw.get("gpio", -1))
|
|
455
|
+
if gpio < 2 or gpio > 27 or gpio in used_gpios:
|
|
456
|
+
continue
|
|
457
|
+
name = str(raw.get("name") or f"Relay {index + 1}").strip()[:60]
|
|
458
|
+
base_id = re.sub(r"[^a-z0-9_-]+", "-", name.lower()).strip("-") or f"relay-{index + 1}"
|
|
459
|
+
motor_id, suffix = base_id[:32], 2
|
|
460
|
+
while motor_id in used_ids:
|
|
461
|
+
tail = f"-{suffix}"
|
|
462
|
+
motor_id = f"{base_id[:32-len(tail)]}{tail}"
|
|
463
|
+
suffix += 1
|
|
464
|
+
used_ids.add(motor_id)
|
|
465
|
+
used_gpios.add(gpio)
|
|
466
|
+
registry.append({
|
|
467
|
+
"id": motor_id,
|
|
468
|
+
"name": name,
|
|
469
|
+
"gpio": gpio,
|
|
470
|
+
"active_high": bool(raw.get("active_high", True)),
|
|
471
|
+
"max_duration_ms": 3000,
|
|
472
|
+
})
|
|
473
|
+
return {"ok": True, "registry": registry, "count": len(registry), "motor_server_url": base}
|
|
474
|
+
except Exception as exc:
|
|
475
|
+
return {"ok": False, "error": f"motor registry discovery failed: {type(exc).__name__}: {exc}"}
|
|
476
|
+
|
|
477
|
+
|
|
437
478
|
def _run_motor_sequence(params: dict) -> dict:
|
|
438
479
|
"""Run one validated scheduler sequence against the robot-local motor API."""
|
|
439
480
|
import httpx
|