devagent-ignition 0.1.0__py3-none-any.whl
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.
- devagent_hmi.py +471 -0
- devagent_ignition-0.1.0.dist-info/METADATA +204 -0
- devagent_ignition-0.1.0.dist-info/RECORD +133 -0
- devagent_ignition-0.1.0.dist-info/WHEEL +4 -0
- devagent_ignition-0.1.0.dist-info/entry_points.txt +2 -0
- devagent_spatial/__init__.py +1 -0
- devagent_spatial/_resources/README.md +187 -0
- devagent_spatial/_resources/RELEASE_MANIFEST.json +312 -0
- devagent_spatial/_resources/SHA256SUMS.txt +270 -0
- devagent_spatial/_resources/SOURCE_PROVENANCE.json +186 -0
- devagent_spatial/_resources/assets/agent.js +102 -0
- devagent_spatial/_resources/assets/creator.css +247 -0
- devagent_spatial/_resources/assets/creator_2d.mjs +16 -0
- devagent_spatial/_resources/assets/creator_original_r9.mjs +58 -0
- devagent_spatial/_resources/assets/creator_original_v8.mjs +120 -0
- devagent_spatial/_resources/assets/creator_r9_diagnostics.mjs +53 -0
- devagent_spatial/_resources/assets/creator_scene.mjs +128 -0
- devagent_spatial/_resources/assets/creator_ui.mjs +60 -0
- devagent_spatial/_resources/assets/customer_documents.js +110 -0
- devagent_spatial/_resources/assets/equipment_controls.mjs +27 -0
- devagent_spatial/_resources/assets/facility2d.css +24 -0
- devagent_spatial/_resources/assets/facility2d_app.mjs +45 -0
- devagent_spatial/_resources/assets/facility2d_core.mjs +114 -0
- devagent_spatial/_resources/assets/facility2d_simulation.mjs +167 -0
- devagent_spatial/_resources/assets/facility2d_viewer.html +7 -0
- devagent_spatial/_resources/assets/fallback_scene.js +17 -0
- devagent_spatial/_resources/assets/flow_geometry.mjs +218 -0
- devagent_spatial/_resources/assets/harness.js +78 -0
- devagent_spatial/_resources/assets/live_design_review.js +129 -0
- devagent_spatial/_resources/assets/operations_ui.js +305 -0
- devagent_spatial/_resources/assets/operator_viewer.html +64 -0
- devagent_spatial/_resources/assets/project_package.js +52 -0
- devagent_spatial/_resources/assets/r9_transport.mjs +70 -0
- devagent_spatial/_resources/assets/runtime_feed.js +30 -0
- devagent_spatial/_resources/assets/studio.html +139 -0
- devagent_spatial/_resources/assets/style.css +191 -0
- devagent_spatial/_resources/assets/test_workspace.html +195 -0
- devagent_spatial/_resources/assets/three_scene.js +442 -0
- devagent_spatial/_resources/assets/unified_engineer.js +101 -0
- devagent_spatial/_resources/assets/v076_fat_ui.js +40 -0
- devagent_spatial/_resources/assets/v078_command_ui.js +44 -0
- devagent_spatial/_resources/assets/viewer.html +45 -0
- devagent_spatial/_resources/assets/viewer.js +112 -0
- devagent_spatial/_resources/design-system.json +130 -0
- devagent_spatial/_resources/examples/CV101-command-contract-PENDING.json +30 -0
- devagent_spatial/_resources/examples/cv101_guided.json +272 -0
- devagent_spatial/_resources/examples/make_examples.py +36 -0
- devagent_spatial/_resources/examples/single_floor.json +444 -0
- devagent_spatial/_resources/examples/v0771_training_wizard.json +11 -0
- devagent_spatial/_resources/examples/warehouse-3floor-standard-2d.json +708 -0
- devagent_spatial/_resources/examples/warehouse-operations-synthetic.json +1186 -0
- devagent_spatial/_resources/examples/warehouse-v07-lab-bindings-PENDING.json +372 -0
- devagent_spatial/_resources/examples/warehouse.json +708 -0
- devagent_spatial/_resources/examples/water.json +288 -0
- devagent_spatial/_resources/reference/r9-golden/REFERENCE_SHA256.json +13 -0
- devagent_spatial/_resources/reference/r9-golden/web/app.mjs +745 -0
- devagent_spatial/_resources/reference/r9-golden/web/index.html +213 -0
- devagent_spatial/_resources/reference/r9-golden/web/lab_panel.mjs +53 -0
- devagent_spatial/_resources/reference/r9-golden/web/model.mjs +367 -0
- devagent_spatial/_resources/reference/r9-golden/web/state_adapter.mjs +78 -0
- devagent_spatial/_resources/reference/r9-golden/web/styles.css +76 -0
- devagent_spatial/_resources/reference/r9-original/README_R9.md +27 -0
- devagent_spatial/_resources/reference/r9-original/SHA256SUMS.txt +63 -0
- devagent_spatial/_resources/reference/r9-original/SNAPSHOT_SHA256SUMS.txt +16 -0
- devagent_spatial/_resources/reference/r9-original/flow_routes.json +527 -0
- devagent_spatial/_resources/reference/r9-original/flow_sim.py +117 -0
- devagent_spatial/_resources/reference/r9-original/lab_control.py +73 -0
- devagent_spatial/_resources/reference/r9-original/web/app.mjs +745 -0
- devagent_spatial/_resources/reference/r9-original/web/index.html +213 -0
- devagent_spatial/_resources/reference/r9-original/web/lab_panel.mjs +53 -0
- devagent_spatial/_resources/reference/r9-original/web/model.mjs +367 -0
- devagent_spatial/_resources/reference/r9-original/web/state_adapter.mjs +78 -0
- devagent_spatial/_resources/reference/r9-original/web/styles.css +76 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/LICENSE-three.txt +21 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/SHA256SUMS.txt +4 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/addons/controls/OrbitControls.js +1860 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/three.core.js +58773 -0
- devagent_spatial/_resources/reference/r9-original/web/vendor/three.module.js +18251 -0
- devagent_spatial/_resources/reference/v8-original/README.md +36 -0
- devagent_spatial/_resources/reference/v8-original/SHA256SUMS.txt +12 -0
- devagent_spatial/_resources/reference/v8-original/model.py +124 -0
- devagent_spatial/_resources/reference/v8-original/run_demo.sh +4 -0
- devagent_spatial/_resources/reference/v8-original/server.py +100 -0
- devagent_spatial/_resources/reference/v8-original/tests/browser_smoke.py +50 -0
- devagent_spatial/_resources/reference/v8-original/tests/test_v8.py +144 -0
- devagent_spatial/_resources/reference/v8-original/topology.json +24 -0
- devagent_spatial/_resources/reference/v8-original/web/app.js +114 -0
- devagent_spatial/_resources/reference/v8-original/web/index.html +22 -0
- devagent_spatial/_resources/reference/v8-original/web/simulator.html +1 -0
- devagent_spatial/_resources/reference/v8-original/web/simulator.js +6 -0
- devagent_spatial/_resources/reference/v8-original/web/style.css +16 -0
- devagent_spatial/_resources/sample_customer_inputs/Plant_Equipment_Register.csv +4 -0
- devagent_spatial/_resources/sample_customer_inputs/Plant_Flow_Requirements.txt +5 -0
- devagent_spatial/_resources/sample_customer_inputs/Raw_PLC_Tag_Export.csv +4 -0
- devagent_spatial/_resources/templates/warehouse_connections.csv +12 -0
- devagent_spatial/_resources/templates/warehouse_equipment.csv +21 -0
- devagent_spatial/_resources/templates/warehouse_signals.csv +34 -0
- devagent_spatial/_resources/templates/water_connections.csv +7 -0
- devagent_spatial/_resources/templates/water_equipment.csv +10 -0
- devagent_spatial/_resources/templates/water_signals.csv +12 -0
- devagent_spatial/agent_harness.py +299 -0
- devagent_spatial/agent_runtime.py +314 -0
- devagent_spatial/contract_opc_fat.py +216 -0
- devagent_spatial/contract_opc_source.py +265 -0
- devagent_spatial/control_intents.py +38 -0
- devagent_spatial/customer_intake.py +235 -0
- devagent_spatial/engineering_expertise.py +114 -0
- devagent_spatial/generator.py +201 -0
- devagent_spatial/ignition_native.py +234 -0
- devagent_spatial/ignition_project.py +144 -0
- devagent_spatial/live_lab_binding.py +128 -0
- devagent_spatial/model.py +297 -0
- devagent_spatial/native_reference.py +69 -0
- devagent_spatial/opc_snapshot.py +87 -0
- devagent_spatial/operations.py +284 -0
- devagent_spatial/operations_capture.py +47 -0
- devagent_spatial/operations_lab.py +64 -0
- devagent_spatial/original_r9.py +169 -0
- devagent_spatial/original_v8.py +81 -0
- devagent_spatial/preflight.py +140 -0
- devagent_spatial/preview_simulation.py +189 -0
- devagent_spatial/project_package.py +101 -0
- devagent_spatial/qualification.py +152 -0
- devagent_spatial/strict_json.py +29 -0
- devagent_spatial/studio.py +894 -0
- devagent_spatial/studio_workflow.py +277 -0
- devagent_spatial/test_reporting.py +87 -0
- devagent_spatial/v076_opc_fat.py +180 -0
- devagent_spatial/v078_commands.py +277 -0
- devagent_spatial/v078_native_commands.py +161 -0
- devagent_spatial/v07_fat.py +110 -0
- devagent_spatial/v07_native.py +318 -0
- devagent_spatial/v083_native.py +349 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""V1J deterministic LAB parcel timeline. isolated concept assets, no real PLC controls."""
|
|
2
|
+
import json, math, time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from threading import RLock
|
|
5
|
+
from collections import deque
|
|
6
|
+
MODEL=json.loads((Path(__file__).with_name('flow_routes.json')).read_text())
|
|
7
|
+
IDS=tuple(MODEL['ids'])
|
|
8
|
+
ORIGINAL=tuple(MODEL['original'])
|
|
9
|
+
SCANNERS={'DWS01':('CV202',0.50),'DEST_SCAN':('CV302',0.50)}
|
|
10
|
+
SPEED=2.05 # representative scene units per second; NOT a measured belt speed
|
|
11
|
+
BEAM_HALF_WIDTH=.53 # parcel geometry + illustrative detector window
|
|
12
|
+
|
|
13
|
+
class ParcelFlow:
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self._lock=RLock();self.wave=-1;self.cycle=0;self.parcels=[];self.delivered=0
|
|
16
|
+
self.sensors={eid:{'PE_IN':False,'PE_OUT':False,'IN_COUNT':0,'OUT_COUNT':0} for eid in IDS}
|
|
17
|
+
self.scanners={k:{'LastBarcode':'','LastParcelId':'','LastResult':'NO_SCAN','ScanCount':0,'NoReadCount':0,'Revision':0} for k in SCANNERS}
|
|
18
|
+
self.events=deque(maxlen=32);self.sequence=0;self.timestamp=0.0;self.last_scan=None
|
|
19
|
+
def _make_wave(self,w):
|
|
20
|
+
self.wave=w;self.cycle+=1;self.parcels=[]
|
|
21
|
+
for i in range(11):
|
|
22
|
+
# Keep original six + R6 one-time return; add four demonstration parcels
|
|
23
|
+
# each starting on a connected R9 compact sorter station.
|
|
24
|
+
route=MODEL['routes'][i]
|
|
25
|
+
length=sum(MODEL['lengths'][eid] for eid in route)
|
|
26
|
+
# Evenly stagger baseline routes, but seed the *single* added
|
|
27
|
+
# recirculation demo parcel just upstream of the new Floor-3
|
|
28
|
+
# discharge so an operator can inspect it without waiting for a
|
|
29
|
+
# full multi-floor traversal. The position moves continuously
|
|
30
|
+
# thereafter, with no teleport at any junction.
|
|
31
|
+
initial=(i/8)*length if i<6 else 0.0
|
|
32
|
+
if 'R6_CHUTE_F2' in route:
|
|
33
|
+
index=route.index('SORT301')
|
|
34
|
+
initial=sum(MODEL['lengths'][eid] for eid in route[:index])+MODEL['lengths']['SORT301']*.05
|
|
35
|
+
if i>=7:
|
|
36
|
+
# Position a parcel near its assigned R8 mainline station.
|
|
37
|
+
# It still follows every physical segment of its route.
|
|
38
|
+
chute=f'R9_CHUTE_{i-6:02d}'
|
|
39
|
+
index=route.index(f'R9_SORT_{i-6:02d}')
|
|
40
|
+
initial=sum(MODEL['lengths'][eid] for eid in route[:index])+MODEL['lengths'][route[index]]*.01
|
|
41
|
+
self.parcels.append({'id':'PKG-%06d'%(w*100000+self.cycle*100+i+1),'route':route,'offset':initial,'distance':initial,'done':False})
|
|
42
|
+
def _locate_with_index(self,p):
|
|
43
|
+
d=p['distance']
|
|
44
|
+
for i,eid in enumerate(p['route']):
|
|
45
|
+
ln=MODEL['lengths'][eid]
|
|
46
|
+
if d<=ln:return i,eid,max(0.0,min(1.0,d/ln))
|
|
47
|
+
d-=ln
|
|
48
|
+
return len(p['route'])-1,p['route'][-1],1.0
|
|
49
|
+
def _locate(self,p):
|
|
50
|
+
_,eid,progress=self._locate_with_index(p)
|
|
51
|
+
return eid,progress
|
|
52
|
+
def _cross(self,p,old,new,index,at):
|
|
53
|
+
# Route positions, NOT equipment names: a re-sort parcel traverses
|
|
54
|
+
# CV302 and SORT301 twice. Counting only the first occurrence silently
|
|
55
|
+
# loses scanner/PE events on recirculated parcels.
|
|
56
|
+
length=MODEL['lengths'][p['route'][index]]
|
|
57
|
+
offset=sum(MODEL['lengths'][eid] for eid in p['route'][:index])
|
|
58
|
+
return old < offset+at*length <= new
|
|
59
|
+
def _event(self,kind,eid,pid):
|
|
60
|
+
self.sequence+=1
|
|
61
|
+
event={'sequence':self.sequence,'kind':kind,'equipmentId':eid,'parcelId':pid,'timestamp':self.timestamp}
|
|
62
|
+
self.events.append(event)
|
|
63
|
+
if kind.startswith('SCAN_'):self.last_scan=event
|
|
64
|
+
def step(self,state,wave,dt,now=None):
|
|
65
|
+
with self._lock:
|
|
66
|
+
self.timestamp=round(float(now if now is not None else time.time()),3)
|
|
67
|
+
if wave != self.wave:
|
|
68
|
+
self._make_wave(wave) if state=='RUNNING' else self._reset(wave)
|
|
69
|
+
if state=='RUNNING' and (not self.parcels or all(p['done'] for p in self.parcels)):
|
|
70
|
+
self._make_wave(wave)
|
|
71
|
+
for p in self.parcels:
|
|
72
|
+
if p['done']:continue
|
|
73
|
+
old=p['distance'];total=sum(MODEL['lengths'][x] for x in p['route'])
|
|
74
|
+
new=min(total,old+max(0,min(dt,.5))*SPEED) if state=='RUNNING' else old
|
|
75
|
+
if state=='RUNNING':
|
|
76
|
+
for index,eid in enumerate(p['route']):
|
|
77
|
+
for direction,frac,key in [('PE_IN',.08,'IN_COUNT'),('PE_OUT',.92,'OUT_COUNT')]:
|
|
78
|
+
if self._cross(p,old,new,index,frac):
|
|
79
|
+
self.sensors[eid][key]+=1;self._event(direction,eid,p['id'])
|
|
80
|
+
for scanner,(eid,frac) in SCANNERS.items():
|
|
81
|
+
for index,current in enumerate(p['route']):
|
|
82
|
+
if current != eid or not self._cross(p,old,new,index,frac):continue
|
|
83
|
+
s=self.scanners[scanner];s['ScanCount']+=1
|
|
84
|
+
serial=int(p['id'].split('-')[-1]);read='NO_READ' if serial%4==0 else 'GOOD_READ'
|
|
85
|
+
if read=='NO_READ':s['NoReadCount']+=1
|
|
86
|
+
s['LastBarcode']=p['id'] if read=='GOOD_READ' else ''
|
|
87
|
+
s['LastParcelId']=p['id'];s['LastResult']=read;s['Revision']+=1
|
|
88
|
+
self._event('SCAN_'+read,scanner,p['id'])
|
|
89
|
+
p['distance']=new
|
|
90
|
+
if new>=total and state=='RUNNING':p['done']=True;self.delivered+=1;self._event('DELIVERED',p['route'][-1],p['id'])
|
|
91
|
+
# Derived instantaneous beam states; frozen while stopped/faulted.
|
|
92
|
+
for eid,s in self.sensors.items():s['PE_IN']=False;s['PE_OUT']=False
|
|
93
|
+
for p in self.parcels:
|
|
94
|
+
if p['done']:continue
|
|
95
|
+
e,t=self._locate(p);dist=t*MODEL['lengths'][e]
|
|
96
|
+
for key,frac in (('PE_IN',.08),('PE_OUT',.92)):
|
|
97
|
+
if abs(dist-frac*MODEL['lengths'][e])<=BEAM_HALF_WIDTH:self.sensors[e][key]=True
|
|
98
|
+
return self.snapshot()
|
|
99
|
+
def _reset(self,wave):
|
|
100
|
+
self.wave=wave;self.cycle=0;self.parcels=[];self.delivered=0
|
|
101
|
+
self.events.clear();self.sequence=0;self.last_scan=None
|
|
102
|
+
self.sensors={eid:{'PE_IN':False,'PE_OUT':False,'IN_COUNT':0,'OUT_COUNT':0} for eid in IDS}
|
|
103
|
+
self.scanners={k:{'LastBarcode':'','LastParcelId':'','LastResult':'NO_SCAN','ScanCount':0,'NoReadCount':0,'Revision':0} for k in SCANNERS}
|
|
104
|
+
def snapshot(self):
|
|
105
|
+
with self._lock:
|
|
106
|
+
poses=[]
|
|
107
|
+
for p in self.parcels:
|
|
108
|
+
if not p['done']:
|
|
109
|
+
index,eid,t=self._locate_with_index(p)
|
|
110
|
+
poses.append({'parcelId':p['id'],'equipmentId':eid,'progress':round(t,5),
|
|
111
|
+
'nextEquipmentId':p['route'][index+1] if index+1<len(p['route']) else None})
|
|
112
|
+
return {'sensor_quality':'ISOLATED_SIMULATOR','timestamp':self.timestamp,
|
|
113
|
+
'equipment':{k:dict(v) for k,v in self.sensors.items()},
|
|
114
|
+
'scanners':{k:dict(v) for k,v in self.scanners.items()},
|
|
115
|
+
'parcels':poses,'parcels_in_transit':len(poses),'delivered':self.delivered,
|
|
116
|
+
'blocked_sensors':sum(int(v['PE_IN'])+int(v['PE_OUT']) for v in self.sensors.values()),
|
|
117
|
+
'sensor_events':list(self.events)[-16:],'last_scan':self.last_scan}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Isolated, in-memory simulator command model. No OPC UA / V1C writes."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
from collections import deque
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
VALID = frozenset({'start', 'stop', 'reset', 'ack', 'inject_jam'})
|
|
9
|
+
|
|
10
|
+
class CommandConflict(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class LabController:
|
|
15
|
+
state: str = 'STOPPED'
|
|
16
|
+
revision: int = 0
|
|
17
|
+
wave: int = 0
|
|
18
|
+
fault: str | None = None
|
|
19
|
+
alarm_acknowledged: bool = False
|
|
20
|
+
updated: float = field(default_factory=time.time)
|
|
21
|
+
events: deque = field(default_factory=lambda: deque(maxlen=100))
|
|
22
|
+
_replays: dict = field(default_factory=dict)
|
|
23
|
+
sensor_data: dict = field(default_factory=dict)
|
|
24
|
+
_lock: threading.RLock = field(default_factory=threading.RLock)
|
|
25
|
+
|
|
26
|
+
def update_sensors(self, data):
|
|
27
|
+
with self._lock:
|
|
28
|
+
self.sensor_data = data
|
|
29
|
+
|
|
30
|
+
def snapshot(self):
|
|
31
|
+
with self._lock:
|
|
32
|
+
return {'mode':'ISOLATED_SIMULATOR','run_state':self.state,
|
|
33
|
+
'revision':self.revision, 'wave':self.wave,
|
|
34
|
+
'fault':self.fault,'ack_required':bool(self.fault and not self.alarm_acknowledged),
|
|
35
|
+
'alarm_acknowledged':bool(self.fault and self.alarm_acknowledged),
|
|
36
|
+
'updated_at':self.updated,
|
|
37
|
+
'equipment_id':'CV208' if self.fault else None,
|
|
38
|
+
'writes_to_v1c':False,'writes_to_opcua':False,'events':list(self.events)[-8:],
|
|
39
|
+
'sensor_data': self.sensor_data}
|
|
40
|
+
|
|
41
|
+
def command(self, name: str, request_id: str):
|
|
42
|
+
if name not in VALID: raise ValueError('unsupported command')
|
|
43
|
+
with self._lock:
|
|
44
|
+
if request_id in self._replays:
|
|
45
|
+
if self._replays[request_id] != name:
|
|
46
|
+
raise CommandConflict('request_id reused for different command')
|
|
47
|
+
return self.snapshot()
|
|
48
|
+
if name == 'start':
|
|
49
|
+
if self.state != 'STOPPED' or self.fault:
|
|
50
|
+
raise CommandConflict('START requires STOPPED state and no active fault')
|
|
51
|
+
self.state='RUNNING'
|
|
52
|
+
elif name == 'stop':
|
|
53
|
+
if self.state != 'RUNNING': raise CommandConflict('STOP requires RUNNING state')
|
|
54
|
+
self.state='STOPPED'
|
|
55
|
+
elif name == 'inject_jam':
|
|
56
|
+
if self.state != 'RUNNING': raise CommandConflict('JAM TEST requires RUNNING state')
|
|
57
|
+
self.state='FAULT'; self.fault='SIMULATED_JAM_CV208'; self.alarm_acknowledged=False
|
|
58
|
+
elif name == 'ack':
|
|
59
|
+
if not self.fault or self.alarm_acknowledged:
|
|
60
|
+
raise CommandConflict('ACK requires an unacknowledged simulated alarm')
|
|
61
|
+
self.alarm_acknowledged=True
|
|
62
|
+
elif name == 'reset':
|
|
63
|
+
if self.state == 'RUNNING': raise CommandConflict('STOP before RESET')
|
|
64
|
+
if self.fault and not self.alarm_acknowledged:
|
|
65
|
+
raise CommandConflict('ACK the simulated jam before RESET')
|
|
66
|
+
self.state='STOPPED'; self.fault=None; self.alarm_acknowledged=False; self.wave+=1
|
|
67
|
+
self.revision+=1; self.updated=time.time()
|
|
68
|
+
self.events.append({'command':name.upper(),'revision':self.revision,'state':self.state,
|
|
69
|
+
'time':self.updated,'request_id':request_id})
|
|
70
|
+
self._replays[request_id]=name
|
|
71
|
+
# Bound memory growth without allowing stale repeated IDs to mutate silently within the recent window.
|
|
72
|
+
if len(self._replays)>512:self._replays.pop(next(iter(self._replays)))
|
|
73
|
+
return self.snapshot()
|