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.
Files changed (133) hide show
  1. devagent_hmi.py +471 -0
  2. devagent_ignition-0.1.0.dist-info/METADATA +204 -0
  3. devagent_ignition-0.1.0.dist-info/RECORD +133 -0
  4. devagent_ignition-0.1.0.dist-info/WHEEL +4 -0
  5. devagent_ignition-0.1.0.dist-info/entry_points.txt +2 -0
  6. devagent_spatial/__init__.py +1 -0
  7. devagent_spatial/_resources/README.md +187 -0
  8. devagent_spatial/_resources/RELEASE_MANIFEST.json +312 -0
  9. devagent_spatial/_resources/SHA256SUMS.txt +270 -0
  10. devagent_spatial/_resources/SOURCE_PROVENANCE.json +186 -0
  11. devagent_spatial/_resources/assets/agent.js +102 -0
  12. devagent_spatial/_resources/assets/creator.css +247 -0
  13. devagent_spatial/_resources/assets/creator_2d.mjs +16 -0
  14. devagent_spatial/_resources/assets/creator_original_r9.mjs +58 -0
  15. devagent_spatial/_resources/assets/creator_original_v8.mjs +120 -0
  16. devagent_spatial/_resources/assets/creator_r9_diagnostics.mjs +53 -0
  17. devagent_spatial/_resources/assets/creator_scene.mjs +128 -0
  18. devagent_spatial/_resources/assets/creator_ui.mjs +60 -0
  19. devagent_spatial/_resources/assets/customer_documents.js +110 -0
  20. devagent_spatial/_resources/assets/equipment_controls.mjs +27 -0
  21. devagent_spatial/_resources/assets/facility2d.css +24 -0
  22. devagent_spatial/_resources/assets/facility2d_app.mjs +45 -0
  23. devagent_spatial/_resources/assets/facility2d_core.mjs +114 -0
  24. devagent_spatial/_resources/assets/facility2d_simulation.mjs +167 -0
  25. devagent_spatial/_resources/assets/facility2d_viewer.html +7 -0
  26. devagent_spatial/_resources/assets/fallback_scene.js +17 -0
  27. devagent_spatial/_resources/assets/flow_geometry.mjs +218 -0
  28. devagent_spatial/_resources/assets/harness.js +78 -0
  29. devagent_spatial/_resources/assets/live_design_review.js +129 -0
  30. devagent_spatial/_resources/assets/operations_ui.js +305 -0
  31. devagent_spatial/_resources/assets/operator_viewer.html +64 -0
  32. devagent_spatial/_resources/assets/project_package.js +52 -0
  33. devagent_spatial/_resources/assets/r9_transport.mjs +70 -0
  34. devagent_spatial/_resources/assets/runtime_feed.js +30 -0
  35. devagent_spatial/_resources/assets/studio.html +139 -0
  36. devagent_spatial/_resources/assets/style.css +191 -0
  37. devagent_spatial/_resources/assets/test_workspace.html +195 -0
  38. devagent_spatial/_resources/assets/three_scene.js +442 -0
  39. devagent_spatial/_resources/assets/unified_engineer.js +101 -0
  40. devagent_spatial/_resources/assets/v076_fat_ui.js +40 -0
  41. devagent_spatial/_resources/assets/v078_command_ui.js +44 -0
  42. devagent_spatial/_resources/assets/viewer.html +45 -0
  43. devagent_spatial/_resources/assets/viewer.js +112 -0
  44. devagent_spatial/_resources/design-system.json +130 -0
  45. devagent_spatial/_resources/examples/CV101-command-contract-PENDING.json +30 -0
  46. devagent_spatial/_resources/examples/cv101_guided.json +272 -0
  47. devagent_spatial/_resources/examples/make_examples.py +36 -0
  48. devagent_spatial/_resources/examples/single_floor.json +444 -0
  49. devagent_spatial/_resources/examples/v0771_training_wizard.json +11 -0
  50. devagent_spatial/_resources/examples/warehouse-3floor-standard-2d.json +708 -0
  51. devagent_spatial/_resources/examples/warehouse-operations-synthetic.json +1186 -0
  52. devagent_spatial/_resources/examples/warehouse-v07-lab-bindings-PENDING.json +372 -0
  53. devagent_spatial/_resources/examples/warehouse.json +708 -0
  54. devagent_spatial/_resources/examples/water.json +288 -0
  55. devagent_spatial/_resources/reference/r9-golden/REFERENCE_SHA256.json +13 -0
  56. devagent_spatial/_resources/reference/r9-golden/web/app.mjs +745 -0
  57. devagent_spatial/_resources/reference/r9-golden/web/index.html +213 -0
  58. devagent_spatial/_resources/reference/r9-golden/web/lab_panel.mjs +53 -0
  59. devagent_spatial/_resources/reference/r9-golden/web/model.mjs +367 -0
  60. devagent_spatial/_resources/reference/r9-golden/web/state_adapter.mjs +78 -0
  61. devagent_spatial/_resources/reference/r9-golden/web/styles.css +76 -0
  62. devagent_spatial/_resources/reference/r9-original/README_R9.md +27 -0
  63. devagent_spatial/_resources/reference/r9-original/SHA256SUMS.txt +63 -0
  64. devagent_spatial/_resources/reference/r9-original/SNAPSHOT_SHA256SUMS.txt +16 -0
  65. devagent_spatial/_resources/reference/r9-original/flow_routes.json +527 -0
  66. devagent_spatial/_resources/reference/r9-original/flow_sim.py +117 -0
  67. devagent_spatial/_resources/reference/r9-original/lab_control.py +73 -0
  68. devagent_spatial/_resources/reference/r9-original/web/app.mjs +745 -0
  69. devagent_spatial/_resources/reference/r9-original/web/index.html +213 -0
  70. devagent_spatial/_resources/reference/r9-original/web/lab_panel.mjs +53 -0
  71. devagent_spatial/_resources/reference/r9-original/web/model.mjs +367 -0
  72. devagent_spatial/_resources/reference/r9-original/web/state_adapter.mjs +78 -0
  73. devagent_spatial/_resources/reference/r9-original/web/styles.css +76 -0
  74. devagent_spatial/_resources/reference/r9-original/web/vendor/LICENSE-three.txt +21 -0
  75. devagent_spatial/_resources/reference/r9-original/web/vendor/SHA256SUMS.txt +4 -0
  76. devagent_spatial/_resources/reference/r9-original/web/vendor/addons/controls/OrbitControls.js +1860 -0
  77. devagent_spatial/_resources/reference/r9-original/web/vendor/three.core.js +58773 -0
  78. devagent_spatial/_resources/reference/r9-original/web/vendor/three.module.js +18251 -0
  79. devagent_spatial/_resources/reference/v8-original/README.md +36 -0
  80. devagent_spatial/_resources/reference/v8-original/SHA256SUMS.txt +12 -0
  81. devagent_spatial/_resources/reference/v8-original/model.py +124 -0
  82. devagent_spatial/_resources/reference/v8-original/run_demo.sh +4 -0
  83. devagent_spatial/_resources/reference/v8-original/server.py +100 -0
  84. devagent_spatial/_resources/reference/v8-original/tests/browser_smoke.py +50 -0
  85. devagent_spatial/_resources/reference/v8-original/tests/test_v8.py +144 -0
  86. devagent_spatial/_resources/reference/v8-original/topology.json +24 -0
  87. devagent_spatial/_resources/reference/v8-original/web/app.js +114 -0
  88. devagent_spatial/_resources/reference/v8-original/web/index.html +22 -0
  89. devagent_spatial/_resources/reference/v8-original/web/simulator.html +1 -0
  90. devagent_spatial/_resources/reference/v8-original/web/simulator.js +6 -0
  91. devagent_spatial/_resources/reference/v8-original/web/style.css +16 -0
  92. devagent_spatial/_resources/sample_customer_inputs/Plant_Equipment_Register.csv +4 -0
  93. devagent_spatial/_resources/sample_customer_inputs/Plant_Flow_Requirements.txt +5 -0
  94. devagent_spatial/_resources/sample_customer_inputs/Raw_PLC_Tag_Export.csv +4 -0
  95. devagent_spatial/_resources/templates/warehouse_connections.csv +12 -0
  96. devagent_spatial/_resources/templates/warehouse_equipment.csv +21 -0
  97. devagent_spatial/_resources/templates/warehouse_signals.csv +34 -0
  98. devagent_spatial/_resources/templates/water_connections.csv +7 -0
  99. devagent_spatial/_resources/templates/water_equipment.csv +10 -0
  100. devagent_spatial/_resources/templates/water_signals.csv +12 -0
  101. devagent_spatial/agent_harness.py +299 -0
  102. devagent_spatial/agent_runtime.py +314 -0
  103. devagent_spatial/contract_opc_fat.py +216 -0
  104. devagent_spatial/contract_opc_source.py +265 -0
  105. devagent_spatial/control_intents.py +38 -0
  106. devagent_spatial/customer_intake.py +235 -0
  107. devagent_spatial/engineering_expertise.py +114 -0
  108. devagent_spatial/generator.py +201 -0
  109. devagent_spatial/ignition_native.py +234 -0
  110. devagent_spatial/ignition_project.py +144 -0
  111. devagent_spatial/live_lab_binding.py +128 -0
  112. devagent_spatial/model.py +297 -0
  113. devagent_spatial/native_reference.py +69 -0
  114. devagent_spatial/opc_snapshot.py +87 -0
  115. devagent_spatial/operations.py +284 -0
  116. devagent_spatial/operations_capture.py +47 -0
  117. devagent_spatial/operations_lab.py +64 -0
  118. devagent_spatial/original_r9.py +169 -0
  119. devagent_spatial/original_v8.py +81 -0
  120. devagent_spatial/preflight.py +140 -0
  121. devagent_spatial/preview_simulation.py +189 -0
  122. devagent_spatial/project_package.py +101 -0
  123. devagent_spatial/qualification.py +152 -0
  124. devagent_spatial/strict_json.py +29 -0
  125. devagent_spatial/studio.py +894 -0
  126. devagent_spatial/studio_workflow.py +277 -0
  127. devagent_spatial/test_reporting.py +87 -0
  128. devagent_spatial/v076_opc_fat.py +180 -0
  129. devagent_spatial/v078_commands.py +277 -0
  130. devagent_spatial/v078_native_commands.py +161 -0
  131. devagent_spatial/v07_fat.py +110 -0
  132. devagent_spatial/v07_native.py +318 -0
  133. 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()