openpectus 0.1.10__tar.gz

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 (260) hide show
  1. openpectus-0.1.10/PKG-INFO +35 -0
  2. openpectus-0.1.10/README.md +6 -0
  3. openpectus-0.1.10/openpectus/__init__.py +28 -0
  4. openpectus-0.1.10/openpectus/aggregator/__init__.py +0 -0
  5. openpectus-0.1.10/openpectus/aggregator/aggregator.py +334 -0
  6. openpectus-0.1.10/openpectus/aggregator/aggregator_message_handlers.py +128 -0
  7. openpectus-0.1.10/openpectus/aggregator/aggregator_server.py +69 -0
  8. openpectus-0.1.10/openpectus/aggregator/alembic.ini +116 -0
  9. openpectus-0.1.10/openpectus/aggregator/command_examples.py +200 -0
  10. openpectus-0.1.10/openpectus/aggregator/command_util.py +128 -0
  11. openpectus-0.1.10/openpectus/aggregator/csv_generator.py +64 -0
  12. openpectus-0.1.10/openpectus/aggregator/data/__init__.py +0 -0
  13. openpectus-0.1.10/openpectus/aggregator/data/alembic/README +1 -0
  14. openpectus-0.1.10/openpectus/aggregator/data/alembic/env.py +92 -0
  15. openpectus-0.1.10/openpectus/aggregator/data/alembic/script.py.mako +26 -0
  16. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/05c4bf88d0bb_add_recentengine.py +42 -0
  17. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/5dbc834cf70e_added_aggregator_computer_name_and_.py +39 -0
  18. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/646d56d49b76_bootstrap.py +100 -0
  19. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/72449425de7c_change_computer_name_to_engine_computer_.py +30 -0
  20. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/774e4df9ce40_add_uod_fields.py +38 -0
  21. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/95bfa6af85ea_add_engine_hardware_str_to_recentrun_.py +35 -0
  22. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/b792689d4cd4_change_errorlog_to_aggregatederrorlog.py +30 -0
  23. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/bcda1e0dd461_removed_processunits_table.py +38 -0
  24. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/d517729a9be0_added_errorlog_to_recentrun.py +35 -0
  25. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/d53dfbe96f00_introduce_required_roles.py +39 -0
  26. openpectus-0.1.10/openpectus/aggregator/data/alembic/versions/ec559277c8da_add_engine_version_to_recent_run.py +35 -0
  27. openpectus-0.1.10/openpectus/aggregator/data/database.py +95 -0
  28. openpectus-0.1.10/openpectus/aggregator/data/models.py +134 -0
  29. openpectus-0.1.10/openpectus/aggregator/data/repository.py +228 -0
  30. openpectus-0.1.10/openpectus/aggregator/deps.py +21 -0
  31. openpectus-0.1.10/openpectus/aggregator/frontend-dist/.gitkeep +0 -0
  32. openpectus-0.1.10/openpectus/aggregator/frontend-dist/01cd0f50f18fa8b0.json +1 -0
  33. openpectus-0.1.10/openpectus/aggregator/frontend-dist/063fe22fc098362e.json +1 -0
  34. openpectus-0.1.10/openpectus/aggregator/frontend-dist/11.ff0cfa45c1a3cb96.js +8 -0
  35. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1231.5ca29ab7188040de.js +7 -0
  36. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1391.a9e0ec7083e3c96f.js +8 -0
  37. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1463.9f3526e6c4351022.js +8 -0
  38. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1487.e230c3d3a0ec6b7d.js +8 -0
  39. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1527.3f4c8f28025f0331.js +8 -0
  40. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1559.d9998019e80ec328.js +8 -0
  41. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1565.0791799366c24c84.js +12 -0
  42. openpectus-0.1.10/openpectus/aggregator/frontend-dist/163.9f09f9be5a049a57.js +8 -0
  43. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1767.0c8c5fca4bc73254.js +8 -0
  44. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1779.6f887eea291f87ab.js +7 -0
  45. openpectus-0.1.10/openpectus/aggregator/frontend-dist/18d828edb3a5432b.json +1 -0
  46. openpectus-0.1.10/openpectus/aggregator/frontend-dist/1927.f8704e501e824236.js +7 -0
  47. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2121.e134891bd29f76b9.js +8 -0
  48. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2183.370bc0b6c35657fc.js +8 -0
  49. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2218.6d01c0073475c9c5.js +600 -0
  50. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2293.c7e5905056387edc.js +8 -0
  51. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2407.44e6f7acb83a2eac.js +8 -0
  52. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2545.18e26589920529b5.js +8 -0
  53. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2579.888b55420f408742.js +8 -0
  54. openpectus-0.1.10/openpectus/aggregator/frontend-dist/25dd2e6f4f6bb57b.wasm +0 -0
  55. openpectus-0.1.10/openpectus/aggregator/frontend-dist/263.23947fbff77164ee.js +8 -0
  56. openpectus-0.1.10/openpectus/aggregator/frontend-dist/26f02aa1eb5d7e87.svg +3 -0
  57. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2759.20f77866e8c2d5d9.js +8 -0
  58. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2775.e192cb83d986f240.js +8 -0
  59. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2947.dcc2586fa910d7d3.js +8 -0
  60. openpectus-0.1.10/openpectus/aggregator/frontend-dist/2f68dc993ac33358.json +1 -0
  61. openpectus-0.1.10/openpectus/aggregator/frontend-dist/3047.c03d64a899d6d1c0.js +8 -0
  62. openpectus-0.1.10/openpectus/aggregator/frontend-dist/3095.ec3f2351eb9cc4c3.js +8 -0
  63. openpectus-0.1.10/openpectus/aggregator/frontend-dist/3163.a8dda7a07ebe01eb.js +8 -0
  64. openpectus-0.1.10/openpectus/aggregator/frontend-dist/3175e9ac30563f77.json +1 -0
  65. openpectus-0.1.10/openpectus/aggregator/frontend-dist/3359.34afef50c40263ec.js +8 -0
  66. openpectus-0.1.10/openpectus/aggregator/frontend-dist/342848b6abc793c0.json +1 -0
  67. openpectus-0.1.10/openpectus/aggregator/frontend-dist/345.f2fa272ceca6b486.js +8 -0
  68. openpectus-0.1.10/openpectus/aggregator/frontend-dist/3700.0d5ef582c4a0bbb5.js +7 -0
  69. openpectus-0.1.10/openpectus/aggregator/frontend-dist/3787.ebed2f9cd0a53262.js +8 -0
  70. openpectus-0.1.10/openpectus/aggregator/frontend-dist/3db9e4085e6f7566.svg +3 -0
  71. openpectus-0.1.10/openpectus/aggregator/frontend-dist/3e5cab7c367952da.json +1 -0
  72. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4035.2250ff985d6fb19e.js +8 -0
  73. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4113.1a2e5bf0e3c8f4fe.js +8 -0
  74. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4211.21bb71d03e50ebe9.js +8 -0
  75. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4231.c6d37184dfcfc7ed.js +8 -0
  76. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4303.a70c30160f8e0b87.js +8 -0
  77. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4334.11181c2144dc26d3.js +7 -0
  78. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4359.64320bb966a5bba1.js +7 -0
  79. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4405.6e123938e5743dcb.js +7 -0
  80. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4519.b7c155caf62e2141.js +8 -0
  81. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4719.f7c6d5372bdab6ba.js +8 -0
  82. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4727.ad166e4014a5f90f.js +8 -0
  83. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4779.9398374a40adab75.js +8 -0
  84. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4823.eb0d238db4ce5703.js +8 -0
  85. openpectus-0.1.10/openpectus/aggregator/frontend-dist/487.fa773d0172005f9e.js +8 -0
  86. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4951.df9cf6f7c9f572c9.js +8 -0
  87. openpectus-0.1.10/openpectus/aggregator/frontend-dist/4f04ecbf20c9c20c.json +1 -0
  88. openpectus-0.1.10/openpectus/aggregator/frontend-dist/50e2d8f6067cd824.json +1 -0
  89. openpectus-0.1.10/openpectus/aggregator/frontend-dist/515.ba6ff850cba2c044.js +8 -0
  90. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5166.ee5112abc0dc7185.js +7 -0
  91. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5271.09a305ebd8a3305e.js +7 -0
  92. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5372.4e26060b1137e889.js +7 -0
  93. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5383.e6d32f4215eee44d.js +7 -0
  94. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5447.40ee42ef92db61cb.js +8 -0
  95. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5457.d0f2f07f4120b6e8.js +8 -0
  96. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5591.5569fffbbc79b367.js +8 -0
  97. openpectus-0.1.10/openpectus/aggregator/frontend-dist/57552f2ebe0f0696.json +1 -0
  98. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5861.7cd66943d357412f.js +8 -0
  99. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5967.84883a8372a67aa0.js +8 -0
  100. openpectus-0.1.10/openpectus/aggregator/frontend-dist/59a2f126e3e45d27.svg +3 -0
  101. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5c73f8ba5f60f3a2.json +1 -0
  102. openpectus-0.1.10/openpectus/aggregator/frontend-dist/5f84911a0a3564a6.svg +3 -0
  103. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6039.aab73d4b4a4e8959.js +8 -0
  104. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6081.7865f0e834a5075a.js +8 -0
  105. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6085.bf0780646692e0dd.js +8 -0
  106. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6217.a0bed12bb62404d6.js +8 -0
  107. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6311.c6675bc2be7fcd64.js +8 -0
  108. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6403.adf0a6806326e7fd.js +8 -0
  109. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6423.95b1c82cc075d7c6.js +8 -0
  110. openpectus-0.1.10/openpectus/aggregator/frontend-dist/67f329d51ecf4c74.svg +5 -0
  111. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6855.25addbbab63a8c7e.js +8 -0
  112. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6887.0b935367b73e6c79.js +8 -0
  113. openpectus-0.1.10/openpectus/aggregator/frontend-dist/6969.ff37f0e9e6ad6902.js +8 -0
  114. openpectus-0.1.10/openpectus/aggregator/frontend-dist/7111.cbe1ae374f5b8e1f.js +8 -0
  115. openpectus-0.1.10/openpectus/aggregator/frontend-dist/7191.f1e744cb8e86f2a7.js +8 -0
  116. openpectus-0.1.10/openpectus/aggregator/frontend-dist/7333.ffd2adc5049b8378.js +7 -0
  117. openpectus-0.1.10/openpectus/aggregator/frontend-dist/7623.92b9c7f80d2c0441.js +8 -0
  118. openpectus-0.1.10/openpectus/aggregator/frontend-dist/7655.861b067d51f9c2c7.js +8 -0
  119. openpectus-0.1.10/openpectus/aggregator/frontend-dist/7864.13a2f5429526d31f.js +1 -0
  120. openpectus-0.1.10/openpectus/aggregator/frontend-dist/7927.35b5d86c44f72f47.js +8 -0
  121. openpectus-0.1.10/openpectus/aggregator/frontend-dist/8271.f7824233fd3ed6b2.js +8 -0
  122. openpectus-0.1.10/openpectus/aggregator/frontend-dist/8471.2aaea08c54289ca7.js +8 -0
  123. openpectus-0.1.10/openpectus/aggregator/frontend-dist/849.671d268c3ee3a2de.js +7 -0
  124. openpectus-0.1.10/openpectus/aggregator/frontend-dist/8503.f4337b6576285775.js +8 -0
  125. openpectus-0.1.10/openpectus/aggregator/frontend-dist/8631.0d334387d9db7154.js +11 -0
  126. openpectus-0.1.10/openpectus/aggregator/frontend-dist/8759.a80d2a8ee7ba62e2.js +8 -0
  127. openpectus-0.1.10/openpectus/aggregator/frontend-dist/881c595b719cb1a5.json +1 -0
  128. openpectus-0.1.10/openpectus/aggregator/frontend-dist/8881.f738bb87048a077b.js +8 -0
  129. openpectus-0.1.10/openpectus/aggregator/frontend-dist/8899.f5e53397a335cfbb.js +8 -0
  130. openpectus-0.1.10/openpectus/aggregator/frontend-dist/8917.5679ad3f0df455c3.js +8 -0
  131. openpectus-0.1.10/openpectus/aggregator/frontend-dist/923.060ab05056e54cff.js +8 -0
  132. openpectus-0.1.10/openpectus/aggregator/frontend-dist/9559.c8082321e028bdfa.js +8 -0
  133. openpectus-0.1.10/openpectus/aggregator/frontend-dist/9636795e44a0db5a.json +1 -0
  134. openpectus-0.1.10/openpectus/aggregator/frontend-dist/9645.5b56a2344bfbd10e.js +8 -0
  135. openpectus-0.1.10/openpectus/aggregator/frontend-dist/9686.d9e5a398477474d4.js +1 -0
  136. openpectus-0.1.10/openpectus/aggregator/frontend-dist/9831.5ebfd462178e7753.js +8 -0
  137. openpectus-0.1.10/openpectus/aggregator/frontend-dist/9867.5885e41addd90142.js +7 -0
  138. openpectus-0.1.10/openpectus/aggregator/frontend-dist/9fb5f5764acec207.json +1 -0
  139. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/fonts/OpenSans-Italic-VariableFont.ttf +0 -0
  140. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/fonts/OpenSans-VariableFont.ttf +0 -0
  141. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/images/Diagram 1-A.svg +1 -0
  142. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/images/Diagram 1-B.svg +1 -0
  143. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/images/Diagram 1.svg +1 -0
  144. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/images/Diagram 2.svg +1 -0
  145. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/index.d.ts +2 -0
  146. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/index.d.ts.map +1 -0
  147. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/index.js +50 -0
  148. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/index.js.map +1 -0
  149. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/cssWorker-es.js +38929 -0
  150. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/cssWorker-iife.js +84 -0
  151. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/editorWorker-es.js +7352 -0
  152. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/editorWorker-iife.js +11 -0
  153. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/htmlWorker-es.js +20557 -0
  154. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/htmlWorker-iife.js +458 -0
  155. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/jsonWorker-es.js +12369 -0
  156. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/jsonWorker-iife.js +42 -0
  157. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/tsWorker-es.js +145755 -0
  158. openpectus-0.1.10/openpectus/aggregator/frontend-dist/assets/monaco-editor-workers/workers/tsWorker-iife.js +37021 -0
  159. openpectus-0.1.10/openpectus/aggregator/frontend-dist/b53687f2b68f09f1.json +1 -0
  160. openpectus-0.1.10/openpectus/aggregator/frontend-dist/c41032800378b064.html +123 -0
  161. openpectus-0.1.10/openpectus/aggregator/frontend-dist/c4986cd9e95c7fe9.json +1 -0
  162. openpectus-0.1.10/openpectus/aggregator/frontend-dist/ca6ab87326ed6420.svg +5 -0
  163. openpectus-0.1.10/openpectus/aggregator/frontend-dist/codicon.81ea7998906c6b7c.ttf +0 -0
  164. openpectus-0.1.10/openpectus/aggregator/frontend-dist/codicon.ttf +0 -0
  165. openpectus-0.1.10/openpectus/aggregator/frontend-dist/common.789d1d67a7c72cf1.js +7 -0
  166. openpectus-0.1.10/openpectus/aggregator/frontend-dist/e2b596c3b5c310df.svg +4 -0
  167. openpectus-0.1.10/openpectus/aggregator/frontend-dist/eade5345c958f59a.svg +4 -0
  168. openpectus-0.1.10/openpectus/aggregator/frontend-dist/eaf474c3aab5a73c.json +1 -0
  169. openpectus-0.1.10/openpectus/aggregator/frontend-dist/ebce7b0b44cad8d7.svg +5 -0
  170. openpectus-0.1.10/openpectus/aggregator/frontend-dist/eca1c8feaf9235e8.svg +5 -0
  171. openpectus-0.1.10/openpectus/aggregator/frontend-dist/ef8dfbb9d11844c8.json +1 -0
  172. openpectus-0.1.10/openpectus/aggregator/frontend-dist/f079808011976925.js +1 -0
  173. openpectus-0.1.10/openpectus/aggregator/frontend-dist/f1c9615d89775192.ttf +0 -0
  174. openpectus-0.1.10/openpectus/aggregator/frontend-dist/favicon.ico +0 -0
  175. openpectus-0.1.10/openpectus/aggregator/frontend-dist/index.html +13 -0
  176. openpectus-0.1.10/openpectus/aggregator/frontend-dist/main.2d961af7e87c9467.js +77 -0
  177. openpectus-0.1.10/openpectus/aggregator/frontend-dist/mockServiceWorker.js +284 -0
  178. openpectus-0.1.10/openpectus/aggregator/frontend-dist/runtime.603128b4d98381ce.js +1 -0
  179. openpectus-0.1.10/openpectus/aggregator/frontend-dist/styles.40cfc284e358e52a.css +1 -0
  180. openpectus-0.1.10/openpectus/aggregator/frontend_publisher.py +62 -0
  181. openpectus-0.1.10/openpectus/aggregator/generate_openapi_spec_and_typescript_interfaces.py +35 -0
  182. openpectus-0.1.10/openpectus/aggregator/main.py +47 -0
  183. openpectus-0.1.10/openpectus/aggregator/models.py +173 -0
  184. openpectus-0.1.10/openpectus/aggregator/routers/__init__.py +0 -0
  185. openpectus-0.1.10/openpectus/aggregator/routers/auth.py +92 -0
  186. openpectus-0.1.10/openpectus/aggregator/routers/dto.py +400 -0
  187. openpectus-0.1.10/openpectus/aggregator/routers/process_unit.py +302 -0
  188. openpectus-0.1.10/openpectus/aggregator/routers/recent_runs.py +133 -0
  189. openpectus-0.1.10/openpectus/aggregator/routers/serialization.py +58 -0
  190. openpectus-0.1.10/openpectus/aggregator/routers/version.py +18 -0
  191. openpectus-0.1.10/openpectus/aggregator/spa.py +34 -0
  192. openpectus-0.1.10/openpectus/build.json +1 -0
  193. openpectus-0.1.10/openpectus/c4-documentation.dsl +79 -0
  194. openpectus-0.1.10/openpectus/dist/openpectus-0.1.10.tar.gz +0 -0
  195. openpectus-0.1.10/openpectus/engine/__init__.py +0 -0
  196. openpectus-0.1.10/openpectus/engine/archiver.py +151 -0
  197. openpectus-0.1.10/openpectus/engine/commands.py +73 -0
  198. openpectus-0.1.10/openpectus/engine/composite_hardware.py +99 -0
  199. openpectus-0.1.10/openpectus/engine/configuration/demo_uod.py +190 -0
  200. openpectus-0.1.10/openpectus/engine/configuration/opcua_test.py +91 -0
  201. openpectus-0.1.10/openpectus/engine/engine.py +826 -0
  202. openpectus-0.1.10/openpectus/engine/engine_message_builder.py +150 -0
  203. openpectus-0.1.10/openpectus/engine/engine_message_handlers.py +80 -0
  204. openpectus-0.1.10/openpectus/engine/engine_runner.py +381 -0
  205. openpectus-0.1.10/openpectus/engine/hardware.py +203 -0
  206. openpectus-0.1.10/openpectus/engine/hardware_recovery.py +382 -0
  207. openpectus-0.1.10/openpectus/engine/internal_commands.py +125 -0
  208. openpectus-0.1.10/openpectus/engine/internal_commands_impl.py +320 -0
  209. openpectus-0.1.10/openpectus/engine/labjack/LICENSE +81 -0
  210. openpectus-0.1.10/openpectus/engine/labjack/LabJackM.dll +0 -0
  211. openpectus-0.1.10/openpectus/engine/labjack/LabJackWUSB.dll +0 -0
  212. openpectus-0.1.10/openpectus/engine/labjack/libLabJackM.so +0 -0
  213. openpectus-0.1.10/openpectus/engine/labjack/ljm_constants.json +7509 -0
  214. openpectus-0.1.10/openpectus/engine/labjack_hardware.py +233 -0
  215. openpectus-0.1.10/openpectus/engine/main.py +308 -0
  216. openpectus-0.1.10/openpectus/engine/method_model.py +88 -0
  217. openpectus-0.1.10/openpectus/engine/models.py +47 -0
  218. openpectus-0.1.10/openpectus/engine/opcua_hardware.py +341 -0
  219. openpectus-0.1.10/openpectus/engine/uod_builder_api.py +20 -0
  220. openpectus-0.1.10/openpectus/lang/__init__.py +4 -0
  221. openpectus-0.1.10/openpectus/lang/exec/__init__.py +0 -0
  222. openpectus-0.1.10/openpectus/lang/exec/analyzer.py +421 -0
  223. openpectus-0.1.10/openpectus/lang/exec/base_unit.py +20 -0
  224. openpectus-0.1.10/openpectus/lang/exec/clock.py +12 -0
  225. openpectus-0.1.10/openpectus/lang/exec/commands.py +140 -0
  226. openpectus-0.1.10/openpectus/lang/exec/errors.py +75 -0
  227. openpectus-0.1.10/openpectus/lang/exec/pinterpreter.py +709 -0
  228. openpectus-0.1.10/openpectus/lang/exec/readings.py +233 -0
  229. openpectus-0.1.10/openpectus/lang/exec/runlog.py +539 -0
  230. openpectus-0.1.10/openpectus/lang/exec/tag_lifetime.py +130 -0
  231. openpectus-0.1.10/openpectus/lang/exec/tags.py +354 -0
  232. openpectus-0.1.10/openpectus/lang/exec/tags_impl.py +152 -0
  233. openpectus-0.1.10/openpectus/lang/exec/timer.py +87 -0
  234. openpectus-0.1.10/openpectus/lang/exec/units.py +269 -0
  235. openpectus-0.1.10/openpectus/lang/exec/uod.py +938 -0
  236. openpectus-0.1.10/openpectus/lang/grammar/__init__.py +0 -0
  237. openpectus-0.1.10/openpectus/lang/grammar/codegen/__init__.py +0 -0
  238. openpectus-0.1.10/openpectus/lang/grammar/codegen/pcode.tokens +46 -0
  239. openpectus-0.1.10/openpectus/lang/grammar/codegen/pcodeLexer.py +168 -0
  240. openpectus-0.1.10/openpectus/lang/grammar/codegen/pcodeLexer.tokens +46 -0
  241. openpectus-0.1.10/openpectus/lang/grammar/codegen/pcodeListener.py +318 -0
  242. openpectus-0.1.10/openpectus/lang/grammar/codegen/pcodeParser.py +2689 -0
  243. openpectus-0.1.10/openpectus/lang/grammar/pcode.g4 +113 -0
  244. openpectus-0.1.10/openpectus/lang/grammar/pgrammar.py +56 -0
  245. openpectus-0.1.10/openpectus/lang/grammar/pprogrambuilder.py +292 -0
  246. openpectus-0.1.10/openpectus/lang/grammar/pprogramformatter.py +59 -0
  247. openpectus-0.1.10/openpectus/lang/model/__init__.py +0 -0
  248. openpectus-0.1.10/openpectus/lang/model/pprogram.py +552 -0
  249. openpectus-0.1.10/openpectus/protocol/__init__.py +0 -0
  250. openpectus-0.1.10/openpectus/protocol/aggregator_dispatcher.py +246 -0
  251. openpectus-0.1.10/openpectus/protocol/aggregator_messages.py +48 -0
  252. openpectus-0.1.10/openpectus/protocol/dispatch_interface.py +16 -0
  253. openpectus-0.1.10/openpectus/protocol/engine_dispatcher.py +264 -0
  254. openpectus-0.1.10/openpectus/protocol/engine_messages.py +124 -0
  255. openpectus-0.1.10/openpectus/protocol/exceptions.py +16 -0
  256. openpectus-0.1.10/openpectus/protocol/messages.py +43 -0
  257. openpectus-0.1.10/openpectus/protocol/models.py +170 -0
  258. openpectus-0.1.10/openpectus/protocol/serialization.py +39 -0
  259. openpectus-0.1.10/openpectus/sentry.py +126 -0
  260. openpectus-0.1.10/pyproject.toml +88 -0
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.3
2
+ Name: openpectus
3
+ Version: 0.1.10
4
+ Summary: Pectus is a process control system for Unit Operations such as filtration, chromatography, precipitation,
5
+ Author-email: Eskild Schroll-Fleischer <eyfl@novonordisk.com>, Morten Passow Odgaard <mpo@mjolner.dk>, "Jan H. Knudsen" <jhk@mjolner.dk>
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: antlr4-python3-runtime==4.13.1
8
+ Requires-Dist: pint==0.22
9
+ Requires-Dist: typing_extensions==4.8.0
10
+ Requires-Dist: pydantic==1.10.13
11
+ Requires-Dist: fastapi==0.103.2
12
+ Requires-Dist: uvicorn[standard]==0.23.2
13
+ Requires-Dist: httpx==0.25.0
14
+ Requires-Dist: fastapi_websocket_rpc==0.1.25
15
+ Requires-Dist: fastapi_websocket_pubsub==0.3.8
16
+ Requires-Dist: colorlog==6.7.0
17
+ Requires-Dist: sqlalchemy==2.0.23
18
+ Requires-Dist: asyncua==1.0.6
19
+ Requires-Dist: labjack-ljm==1.23.0
20
+ Requires-Dist: pyright==1.1.381
21
+ Requires-Dist: alembic==1.13.1
22
+ Requires-Dist: sentry-sdk==2.1.1
23
+ Requires-Dist: PyJWT==2.10.0
24
+ Requires-Dist: flake8==6.1.0 ; extra == "development"
25
+ Requires-Dist: black==23.3.0 ; extra == "development"
26
+ Requires-Dist: build==0.10.0 ; extra == "development"
27
+ Provides-Extra: development
28
+
29
+ # Open Pectus
30
+ Pectus is a process control system for Unit Operations such as filtration, chromatography, precipitation, solubilization and refolding. Pectus implements a language called P-code which is used to write methods and control components on the Unit Operation.
31
+
32
+ General documentation about case, architecture and P-Code is available at [Read the Docs](https://open-pectus.readthedocs.io/en/latest/).
33
+
34
+ Documentation for developers is available in the [DEVELOPMENT.md](DEVELOPMENT.md) file.
35
+
@@ -0,0 +1,6 @@
1
+ # Open Pectus
2
+ Pectus is a process control system for Unit Operations such as filtration, chromatography, precipitation, solubilization and refolding. Pectus implements a language called P-code which is used to write methods and control components on the Unit Operation.
3
+
4
+ General documentation about case, architecture and P-Code is available at [Read the Docs](https://open-pectus.readthedocs.io/en/latest/).
5
+
6
+ Documentation for developers is available in the [DEVELOPMENT.md](DEVELOPMENT.md) file.
@@ -0,0 +1,28 @@
1
+ """ Pectus is a process control system for Unit Operations such as filtration, chromatography, precipitation,
2
+ solubilization and refolding. Pectus implements a language called P-code which is used to write methods and
3
+ control components on the Unit Operation. """
4
+ __version__ = "0.1.10"
5
+
6
+ import logging
7
+ import json
8
+ import os
9
+
10
+ import colorlog
11
+
12
+ build_number = "build_number not set"
13
+ build_info = {}
14
+
15
+ path = os.path.dirname(os.path.realpath(__file__))
16
+ with open(os.path.join(path, "build.json")) as file:
17
+ dct = json.load(file)
18
+ build_info = dct
19
+ build_number = dct["build_number"]
20
+
21
+
22
+ def log_setup_colorlog(root_loglevel: int = logging.INFO):
23
+ handler = colorlog.StreamHandler()
24
+ handler.setFormatter(colorlog.ColoredFormatter('%(log_color)s%(levelname)s:%(name)s:%(message)s'))
25
+
26
+ logging.root.setLevel(root_loglevel)
27
+ logging.root.handlers.clear()
28
+ logging.root.addHandler(handler)
File without changes
@@ -0,0 +1,334 @@
1
+ import asyncio
2
+ import logging
3
+ from datetime import datetime, timezone
4
+
5
+ import openpectus.aggregator.models as Mdl
6
+ from openpectus.protocol.aggregator_dispatcher import AggregatorDispatcher
7
+ import openpectus.protocol.aggregator_messages as AM
8
+ import openpectus.protocol.engine_messages as EM
9
+ import openpectus.protocol.messages as M
10
+ from openpectus.aggregator.data import database
11
+ from openpectus.aggregator.data.repository import RecentRunRepository, PlotLogRepository, RecentEngineRepository
12
+ from openpectus.aggregator.frontend_publisher import FrontendPublisher
13
+ from openpectus.aggregator.models import EngineData
14
+ from openpectus.protocol.models import SystemTagName, MethodStatusEnum
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ EngineDataMap = dict[str, EngineData]
19
+ persistance_threshold_seconds = 5
20
+
21
+
22
+ class FromEngine:
23
+ def __init__(self, engine_data_map: EngineDataMap, publisher: FrontendPublisher):
24
+ self._engine_data_map = engine_data_map
25
+ self.publisher = publisher
26
+
27
+ def register_engine_data(self, engine_data: EngineData):
28
+ logger.debug(f"Data for engine {engine_data.engine_id} registered")
29
+ self._engine_data_map[engine_data.engine_id] = engine_data
30
+ asyncio.create_task(self.publisher.publish_process_units_changed())
31
+ asyncio.create_task(self.publisher.publish_control_state_changed(engine_data.engine_id))
32
+
33
+ def engine_connected(self, engine_id: str):
34
+ logger.debug("engine_connected")
35
+
36
+ def engine_disconnected(self, engine_id: str):
37
+ logger.debug("engine_disconnected")
38
+ engine_data = self._engine_data_map.get(engine_id)
39
+ if engine_data is not None:
40
+ with database.create_scope():
41
+ repo = RecentEngineRepository(database.scoped_session())
42
+ repo.store_recent_engine(engine_data)
43
+ logger.info("Recent engine saved")
44
+ del self._engine_data_map[engine_id]
45
+ asyncio.create_task(self.publisher.publish_process_units_changed())
46
+ else:
47
+ logger.warning("No data to save for engine " + engine_id)
48
+
49
+ def engine_reconnected(self, msg: EM.ReconnectedMsg):
50
+ logger.info(f"Engine_reconnected. Processing ReconnectedMsg {msg.ident}")
51
+ engine_id = msg.engine_id
52
+ engine_data = self._engine_data_map.get(engine_id)
53
+ if engine_data is None:
54
+ logger.error("No engine data available on reconnect for engine " + engine_id)
55
+ return
56
+
57
+ # Use this to debug reconnect msg timestamps/older tags
58
+ # created_time = datetime.fromtimestamp(msg.created_tick).strftime("%H:%M:%S")
59
+ # logger.debug(f"ReconnectedMsg created_tick_time: {created_time})")
60
+
61
+ # ft02 = next((tag for tag in msg.tags if tag.name == "FT02"))
62
+ # ft02_time = datetime.fromtimestamp(ft02.tick_time).strftime("%H:%M:%S")
63
+ # logger.debug(f"ReconnectedMsg ft02_time: {ft02_time})")
64
+
65
+
66
+ # apply the state from msg to the current state
67
+ engine_data.method = msg.method
68
+ run_id_tag = next((tag for tag in msg.tags if tag.name == SystemTagName.RUN_ID), None)
69
+ # verify consistent message
70
+ if msg.run_id is None:
71
+ if run_id_tag is not None and run_id_tag.value is not None:
72
+ logger.error(f"Mismatch in ReconnectedMsg, tag run_id {run_id_tag.value}, msg run_id {msg.run_id}")
73
+ return
74
+ else:
75
+ if run_id_tag is None or run_id_tag.value is None:
76
+ logger.error(f"Mismatch in ReconnectedMsg, tag run_id {None}, msg run_id {msg.run_id}")
77
+ return
78
+ engine_data.run_data.run_started = datetime.fromtimestamp(msg.run_started_tick) \
79
+ if msg.run_started_tick is not None else None
80
+ engine_data.tags_info.upsert(run_id_tag) # sets engine_data.run_id
81
+
82
+ # handle possible change to recent engine
83
+ with database.create_scope():
84
+ repo = RecentEngineRepository(database.scoped_session())
85
+ recent_engine = repo.get_recent_engine_by_engine_id(engine_id)
86
+ if recent_engine is None:
87
+ logger.info(f"Reconnected engine {engine_id} has no recent engine data")
88
+ else:
89
+ if recent_engine.run_id != msg.run_id and recent_engine.run_id is not None:
90
+ logger.info("Marking recent engine stopped ")
91
+ repo.mark_recent_engine_stopped(engine_data)
92
+
93
+ # process tag values normally
94
+ self.tag_values_changed(engine_id, msg.tags)
95
+ logger.info(f"Done processing ReconnectedMsg {msg.ident}")
96
+
97
+ def run_started(self): # to replace run_id_changed guessing
98
+ raise NotImplementedError()
99
+
100
+ def run_stopped(self):
101
+ raise NotImplementedError()
102
+
103
+ def uod_info_changed(
104
+ self,
105
+ engine_id: str,
106
+ readings: list[Mdl.ReadingInfo],
107
+ commands: list[Mdl.CommandInfo],
108
+ plot_configuration: Mdl.PlotConfiguration,
109
+ hardware_str: str,
110
+ required_roles: set[str]):
111
+ try:
112
+ self._engine_data_map[engine_id].readings = readings
113
+ self._engine_data_map[engine_id].commands = commands
114
+ self._engine_data_map[engine_id].plot_configuration = plot_configuration
115
+ self._engine_data_map[engine_id].hardware_str = hardware_str
116
+ self._engine_data_map[engine_id].required_roles = required_roles
117
+ except KeyError:
118
+ logger.error(f'No engine registered under id {engine_id} when trying to set uod info.')
119
+
120
+ def tag_values_changed(self, engine_id: str, changed_tag_values: list[Mdl.TagValue]):
121
+ with database.create_scope():
122
+ plot_log_repo = PlotLogRepository(database.scoped_session())
123
+ recent_run_repo = RecentRunRepository(database.scoped_session())
124
+
125
+ engine_data = self._engine_data_map.get(engine_id)
126
+ if engine_data is None:
127
+ logger.error(f'No engine registered under id {engine_id} when trying to upsert tag values.')
128
+ return
129
+
130
+ for changed_tag_value in changed_tag_values:
131
+ if changed_tag_value.name == SystemTagName.METHOD_STATUS.value:
132
+ if changed_tag_value.value == MethodStatusEnum.ERROR:
133
+ engine_data.run_data.interrupted_by_error = True
134
+ else:
135
+ engine_data.run_data.interrupted_by_error = False
136
+
137
+ if changed_tag_value.name == SystemTagName.RUN_ID.value:
138
+ self._run_id_changed(plot_log_repo, recent_run_repo, engine_data, changed_tag_value)
139
+
140
+ was_inserted = engine_data.tags_info.upsert(changed_tag_value)
141
+
142
+ if changed_tag_value.name in [
143
+ SystemTagName.METHOD_STATUS.value,
144
+ SystemTagName.SYSTEM_STATE.value,
145
+ SystemTagName.RUN_ID.value,
146
+ ]:
147
+ asyncio.create_task(self.publisher.publish_process_units_changed())
148
+
149
+ # if a tag doesn't appear with value until after start and run_id, we need to store the info here
150
+ if was_inserted and engine_data.run_id is not None:
151
+ plot_log_repo.store_new_tag_info(engine_data.engine_id, engine_data.run_id, changed_tag_value)
152
+
153
+ self._persist_tag_values(engine_data, plot_log_repo)
154
+
155
+ def _run_id_changed(
156
+ self,
157
+ plot_log_repo: PlotLogRepository,
158
+ recent_run_repo: RecentRunRepository,
159
+ engine_data: EngineData,
160
+ run_id_tag: Mdl.TagValue):
161
+ """ Handles persistance related to start and end of a run """
162
+
163
+ logger.info(f"RunId changed from {engine_data.run_id} to {run_id_tag.value}, Engine: {engine_data.engine_id}")
164
+ if run_id_tag.value is None and engine_data.run_id is None:
165
+ logger.info("Engine has no active run")
166
+ elif run_id_tag.value is None and engine_data.run_id is not None:
167
+ # Run stopped
168
+ logger.info(f"Run was stopped. Saving Recent Run. Engine: {engine_data.engine_id}")
169
+ recent_run_repo.store_recent_run(engine_data)
170
+ engine_data.run_data.error_log = Mdl.AggregatedErrorLog.empty()
171
+ asyncio.create_task(self.publisher.publish_error_log_changed(engine_data.engine_id))
172
+ elif run_id_tag.value is not None and engine_data.run_id is not None:
173
+ # Ongoing run. run_id was only detected as changed because of complete tags set,
174
+ # caused by e.g. an aggregator restart
175
+ if run_id_tag.value != engine_data.run_id:
176
+ logger.error(f"Run_Id inconsistent, run_id tag {run_id_tag.value}, engine_data {engine_data.run_id}")
177
+ else:
178
+ logger.info(f"Run {run_id_tag.value} resumed")
179
+ else:
180
+ # Run started
181
+ logger.info(f"A new Run was started, Engine: {engine_data.engine_id}, Run_Id: {str(run_id_tag.value)}")
182
+ engine_data.run_data.run_started = datetime.fromtimestamp(run_id_tag.tick_time, timezone.utc)
183
+ plot_log_repo.create_plot_log(engine_data, str(run_id_tag.value))
184
+
185
+ def _persist_tag_values(self, engine_data: EngineData, plot_log_repo: PlotLogRepository):
186
+ latest_persisted_tick_time = engine_data.run_data.latest_persisted_tick_time
187
+ tag_values = engine_data.tags_info.map.values()
188
+ latest_tag_tick_time = max([tag.tick_time for tag in tag_values]) if len(tag_values) > 0 else 0
189
+ time_threshold_exceeded = latest_persisted_tick_time is None \
190
+ or latest_tag_tick_time - latest_persisted_tick_time > persistance_threshold_seconds
191
+ # time_threshold_exceeded = latest_persisted_tick_time is None \
192
+ # or time.time() - latest_persisted_tick_time > persistance_threshold_seconds
193
+ if engine_data.run_id is not None and time_threshold_exceeded:
194
+ tag_values_to_persist = [tag_value.copy() for tag_value in engine_data.tags_info.map.values()
195
+ if latest_persisted_tick_time is None
196
+ or tag_value.tick_time > latest_persisted_tick_time]
197
+ """
198
+ We manipulate the tick_time of the tagValues we persist. But it's not changing it to something that didn't
199
+ exist in the engine, because we change the tick_time to a tick_time that comes from an actual later reading
200
+ where those tagValues we manipulate was read and simply had not changed value since the value we have been
201
+ reported.
202
+ It's difficult to explain, but after this manipulation, the tagValues will still match an actual tagValue
203
+ read in the engine, just not one reported.
204
+ We do this because it solves a lot of issues when we later try to match values based on the tick_time.
205
+ """
206
+ highest_tick_time_to_persist = max([tag_Value.tick_time for tag_Value in tag_values_to_persist])
207
+ for tag_value_to_persist in tag_values_to_persist:
208
+ tag_value_to_persist.tick_time = highest_tick_time_to_persist
209
+
210
+ # Note: to store tag values, the run_id is needed
211
+ plot_log_repo.store_tag_values(engine_data.engine_id, engine_data.run_id, tag_values_to_persist)
212
+ engine_data.run_data.latest_persisted_tick_time = highest_tick_time_to_persist
213
+ engine_data.run_data.latest_tag_time = highest_tick_time_to_persist
214
+
215
+ def runlog_changed(self, engine_id: str, runlog: Mdl.RunLog):
216
+ try:
217
+ engine_data = self._engine_data_map[engine_id]
218
+ if engine_data.run_data.runlog != runlog:
219
+ engine_data.run_data.runlog = runlog
220
+ asyncio.create_task(self.publisher.publish_run_log_changed(engine_id))
221
+ except KeyError:
222
+ logger.error(f'No engine registered under id {engine_id} when trying to set run log.')
223
+
224
+ def control_state_changed(self, engine_id: str, control_state: Mdl.ControlState):
225
+ try:
226
+ engine_data = self._engine_data_map[engine_id]
227
+ if engine_data.control_state != control_state:
228
+ engine_data.control_state = control_state
229
+ asyncio.create_task(self.publisher.publish_control_state_changed(engine_id))
230
+ except KeyError:
231
+ logger.error(f'No engine registered under id {engine_id} when trying to set control state.')
232
+
233
+ def method_state_changed(self, engine_id: str, method_state: Mdl.MethodState):
234
+ try:
235
+ engine_data = self._engine_data_map[engine_id]
236
+ if engine_data.run_data.method_state != method_state:
237
+ engine_data.run_data.method_state = method_state
238
+ asyncio.create_task(self.publisher.publish_method_state_changed(engine_id))
239
+ except KeyError:
240
+ logger.error(f'No engine registered under id {engine_id} when trying to set control state.')
241
+
242
+ def error_log_changed(self, engine_id: str, error_log: Mdl.ErrorLog):
243
+ try:
244
+ engine_data = self._engine_data_map[engine_id]
245
+ engine_data.run_data.error_log.aggregate_with(error_log)
246
+ asyncio.create_task(self.publisher.publish_error_log_changed(engine_id))
247
+ except KeyError:
248
+ logger.error(f'No engine registered under id {engine_id} when trying to set error log.')
249
+
250
+
251
+ class FromFrontend:
252
+ def __init__(self, engine_data_map: EngineDataMap, dispatcher: AggregatorDispatcher):
253
+ self._engine_data_map = engine_data_map
254
+ self.dispatcher = dispatcher
255
+
256
+ async def method_saved(self, engine_id: str, method: Mdl.Method, user_name: str) -> bool:
257
+ try:
258
+ response = await self.dispatcher.rpc_call(engine_id, message=AM.MethodMsg(method=method))
259
+ if isinstance(response, M.ErrorMessage):
260
+ logger.error(f"Failed to set method. Engine response: {response.message}")
261
+ return False
262
+ except Exception:
263
+ logger.error("Failed to set method", exc_info=True)
264
+ return False
265
+
266
+ # update local method state
267
+ engine_data = self._engine_data_map.get(engine_id)
268
+ if engine_data is not None:
269
+ engine_data.method = method
270
+ engine_data.contributors.add(user_name)
271
+ return True
272
+
273
+ async def request_cancel(self, engine_id, line_id: str, user_name: str) -> bool:
274
+ engine_data = self._engine_data_map.get(engine_id)
275
+ if engine_data is None:
276
+ logger.warning(f"Cannot request cancel, engine {engine_id} not found")
277
+ return False
278
+ try:
279
+ response = await self.dispatcher.rpc_call(engine_id, message=AM.CancelMsg(exec_id=line_id))
280
+ if isinstance(response, M.ErrorMessage):
281
+ logger.error(f"Cancel request failed. Engine response: {response.message}")
282
+ return False
283
+ except Exception:
284
+ logger.error("Cancel request failed with exception", exc_info=True)
285
+ return False
286
+ engine_data.contributors.add(user_name)
287
+ return True
288
+
289
+ async def request_force(self, engine_id, line_id: str, user_name: str) -> bool:
290
+ engine_data = self._engine_data_map.get(engine_id)
291
+ if engine_data is None:
292
+ logger.warning(f"Cannot request force, engine {engine_id} not found")
293
+ return False
294
+ try:
295
+ response = await self.dispatcher.rpc_call(engine_id, message=AM.ForceMsg(exec_id=line_id))
296
+ if isinstance(response, M.ErrorMessage):
297
+ logger.error(f"Force request failed. Engine response: {response.message}")
298
+ return False
299
+ except Exception:
300
+ logger.error("Force request failed with exception", exc_info=True)
301
+ return False
302
+ engine_data.contributors.add(user_name)
303
+ return True
304
+
305
+
306
+ class Aggregator:
307
+ def __init__(self, dispatcher: AggregatorDispatcher, publisher: FrontendPublisher) -> None:
308
+ self._engine_data_map: EngineDataMap = {}
309
+ """ all client data except channels, indexed by engine_id """
310
+ self.dispatcher = dispatcher
311
+ self.from_frontend = FromFrontend(self._engine_data_map, dispatcher)
312
+ self.from_engine = FromEngine(self._engine_data_map, publisher)
313
+
314
+ def create_engine_id(self, register_engine_msg: EM.RegisterEngineMsg):
315
+ """ Defines the generation of the engine_id that is uniquely assigned to each engine.
316
+
317
+ TODO: Considerations:
318
+ - engine name should be machine name
319
+ - uod hash should probably be included
320
+
321
+ Implications of the registration process
322
+ - historical data; we should not corrupt historical data by accidentially reusing engine_id
323
+ - number of cards shown; we should not show many irrelevant cards in frontend of superseeded engine_ids
324
+ """
325
+ return register_engine_msg.computer_name + "_" + register_engine_msg.uod_name
326
+
327
+ def get_registered_engine_data(self, engine_id: str):
328
+ return self._engine_data_map.get(engine_id)
329
+
330
+ def get_all_registered_engine_data(self) -> list[EngineData]:
331
+ return list(self._engine_data_map.values())
332
+
333
+ def has_registered_engine_id(self, engine_id: str) -> bool:
334
+ return engine_id in self._engine_data_map.keys()
@@ -0,0 +1,128 @@
1
+ import logging
2
+
3
+ import openpectus.protocol.aggregator_messages as AM
4
+ import openpectus.protocol.engine_messages as EM
5
+ import openpectus.aggregator.models as Mdl
6
+ from openpectus.aggregator.aggregator import Aggregator
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class AggregatorMessageHandlers:
12
+ def __init__(self, aggregator: Aggregator):
13
+ self.aggregator = aggregator
14
+ aggregator.dispatcher.set_register_handler(self.handle_RegisterEngineMsg)
15
+ aggregator.dispatcher.set_disconnect_handler(self.handle_EngineDisconnected)
16
+ aggregator.dispatcher.set_connect_handler(self.handle_EngineConnected)
17
+ aggregator.dispatcher.set_message_handler(EM.ReconnectedMsg, self.handle_ReconnectedMsg)
18
+ aggregator.dispatcher.set_message_handler(EM.UodInfoMsg, self.handle_UodInfoMsg)
19
+ aggregator.dispatcher.set_message_handler(EM.TagsUpdatedMsg, self.handle_TagsUpdatedMsg)
20
+ aggregator.dispatcher.set_message_handler(EM.RunLogMsg, self.handle_RunLogMsg)
21
+ aggregator.dispatcher.set_message_handler(EM.ControlStateMsg, self.handle_ControlStateMsg)
22
+ aggregator.dispatcher.set_message_handler(EM.MethodStateMsg, self.handle_MethodStateMsg)
23
+ aggregator.dispatcher.set_message_handler(EM.ErrorLogMsg, self.handle_ErrorLogMsg)
24
+
25
+ async def handle_RegisterEngineMsg(self, register_engine_msg: EM.RegisterEngineMsg) -> AM.RegisterEngineReplyMsg:
26
+ """ Registers engine """
27
+ engine_id = self.aggregator.create_engine_id(register_engine_msg)
28
+ if self.aggregator.dispatcher.has_connected_engine_id(engine_id):
29
+ logger.error(
30
+ f"""Registration failed for engine_id {engine_id}. An engine with that engine_id already
31
+ has a websocket connection. """
32
+ )
33
+ return AM.RegisterEngineReplyMsg(success=False, engine_id=engine_id)
34
+
35
+ # TODO consider how to handle registrations
36
+ # - disconnect/reconnect should work
37
+ # - client kill/reconnect should work
38
+ # - engine_id reused with "same uod" should take over session, else fail as misconfigured client
39
+ # - add machine name + uod secret
40
+
41
+ # initialize client data
42
+ if not self.aggregator.has_registered_engine_id(engine_id):
43
+ self.aggregator.from_engine.register_engine_data(
44
+ Mdl.EngineData(
45
+ engine_id=engine_id,
46
+ computer_name=register_engine_msg.computer_name,
47
+ uod_name=register_engine_msg.uod_name,
48
+ uod_author_name=register_engine_msg.uod_author_name,
49
+ uod_author_email=register_engine_msg.uod_author_email,
50
+ uod_filename=register_engine_msg.uod_filename,
51
+ location=register_engine_msg.location,
52
+ engine_version=register_engine_msg.engine_version
53
+ )
54
+ )
55
+
56
+ logger.info(f"Registration of engine {engine_id} successful")
57
+ return AM.RegisterEngineReplyMsg(success=True, engine_id=engine_id)
58
+
59
+ async def handle_EngineConnected(self, engine_id: str):
60
+ self.aggregator.from_engine.engine_connected(engine_id)
61
+
62
+ async def handle_EngineDisconnected(self, engine_id: str):
63
+ self.aggregator.from_engine.engine_disconnected(engine_id)
64
+
65
+ async def handle_ReconnectedMsg(self, msg: EM.ReconnectedMsg):
66
+ self.aggregator.from_engine.engine_reconnected(msg)
67
+ return AM.SuccessMessage()
68
+
69
+ def validate_msg(self, msg: EM.EngineMessage):
70
+ if not self.aggregator.has_registered_engine_id(msg.engine_id):
71
+ return AM.ErrorMessage(message=f'No engine registered under id {msg.engine_id}')
72
+ # possibly more validations...
73
+ return None
74
+
75
+ async def handle_UodInfoMsg(self, msg: EM.UodInfoMsg) -> AM.SuccessMessage | AM.ErrorMessage:
76
+ validation_errors = self.validate_msg(msg)
77
+ if validation_errors is not None:
78
+ return validation_errors
79
+
80
+ logger.debug(f"Got UodInfo from client: {str(msg)}")
81
+ self.aggregator.from_engine.uod_info_changed(
82
+ msg.engine_id, msg.readings, msg.commands, msg.plot_configuration, msg.hardware_str, msg.required_roles)
83
+ return AM.SuccessMessage()
84
+
85
+ async def handle_TagsUpdatedMsg(self, msg: EM.TagsUpdatedMsg) -> AM.SuccessMessage | AM.ErrorMessage:
86
+ validation_errors = self.validate_msg(msg)
87
+ if validation_errors is not None:
88
+ return validation_errors
89
+
90
+ logger.debug(f"Got tags update from client: {str(msg)}")
91
+ self.aggregator.from_engine.tag_values_changed(msg.engine_id, msg.tags)
92
+ return AM.SuccessMessage()
93
+
94
+ async def handle_RunLogMsg(self, msg: EM.RunLogMsg) -> AM.SuccessMessage | AM.ErrorMessage:
95
+ validation_errors = self.validate_msg(msg)
96
+ if validation_errors is not None:
97
+ return validation_errors
98
+
99
+ logger.debug(f"Got run log from client: {str(msg)}")
100
+ self.aggregator.from_engine.runlog_changed(msg.engine_id, msg.runlog)
101
+ return AM.SuccessMessage()
102
+
103
+ async def handle_ControlStateMsg(self, msg: EM.ControlStateMsg) -> AM.SuccessMessage | AM.ErrorMessage:
104
+ validation_errors = self.validate_msg(msg)
105
+ if validation_errors is not None:
106
+ return validation_errors
107
+
108
+ logger.debug(f"Got control state from client: {str(msg)}")
109
+ self.aggregator.from_engine.control_state_changed(msg.engine_id, msg.control_state)
110
+ return AM.SuccessMessage()
111
+
112
+ async def handle_MethodStateMsg(self, msg: EM.MethodStateMsg):
113
+ validation_errors = self.validate_msg(msg)
114
+ if validation_errors is not None:
115
+ return validation_errors
116
+
117
+ logger.debug(f"Got method state from client: {str(msg)}")
118
+ self.aggregator.from_engine.method_state_changed(msg.engine_id, msg.method_state)
119
+ return AM.SuccessMessage()
120
+
121
+ async def handle_ErrorLogMsg(self, msg: EM.ErrorLogMsg):
122
+ validation_errors = self.validate_msg(msg)
123
+ if validation_errors is not None:
124
+ return validation_errors
125
+
126
+ logger.debug(f'error log message from engine: {msg.log}')
127
+ self.aggregator.from_engine.error_log_changed(msg.engine_id, msg.log)
128
+ return AM.SuccessMessage()
@@ -0,0 +1,69 @@
1
+ import logging
2
+ import os
3
+
4
+ import uvicorn
5
+ from openpectus.aggregator.routers.auth import UserRolesDependency
6
+ from alembic.config import Config
7
+ from fastapi import FastAPI, APIRouter
8
+ from fastapi.routing import APIRoute
9
+ from openpectus.aggregator.aggregator_message_handlers import AggregatorMessageHandlers
10
+ from openpectus.aggregator.data import database
11
+ from openpectus.aggregator.deps import _create_aggregator
12
+ from openpectus.aggregator.frontend_publisher import FrontendPublisher
13
+ from openpectus.aggregator.routers import process_unit, recent_runs, auth, version
14
+ from openpectus.aggregator.spa import SinglePageApplication
15
+ from openpectus.protocol.aggregator_dispatcher import AggregatorDispatcher
16
+
17
+
18
+ class AggregatorServer:
19
+ default_title = "Pectus Aggregator"
20
+ default_frontend_dist_dir = os.path.join(os.path.dirname(__file__), "frontend-dist")
21
+ # default_frontend_dist_dir = ".\\openpectus\\frontend\\dist"
22
+ default_host = "127.0.0.1"
23
+ default_port = 9800
24
+
25
+ def __init__(self, title: str = default_title, host: str = default_host, port: int = default_port,
26
+ frontend_dist_dir: str = default_frontend_dist_dir):
27
+ self.title = title
28
+ self.host = host
29
+ self.port = port
30
+ self.frontend_dist_dir = frontend_dist_dir
31
+ self.dispatcher = AggregatorDispatcher()
32
+ self.publisher = FrontendPublisher()
33
+ self.aggregator = _create_aggregator(self.dispatcher, self.publisher)
34
+ _ = AggregatorMessageHandlers(self.aggregator)
35
+ self.setup_fastapi([self.dispatcher.router, self.publisher.router, version.router])
36
+ self.init_db()
37
+
38
+ def setup_fastapi(self, additional_routers: list[APIRouter] = []):
39
+ api_prefix = "/api"
40
+
41
+ def custom_generate_unique_id(route: APIRoute):
42
+ return f"{route.name}"
43
+
44
+ self.fastapi = FastAPI(title=self.title,
45
+ generate_unique_id_function=custom_generate_unique_id,
46
+ on_shutdown=[self.on_shutdown])
47
+ self.fastapi.include_router(process_unit.router, prefix=api_prefix, dependencies=[UserRolesDependency])
48
+ self.fastapi.include_router(recent_runs.router, prefix=api_prefix, dependencies=[UserRolesDependency])
49
+ self.fastapi.include_router(auth.router, prefix='/auth')
50
+ for route in additional_routers:
51
+ self.fastapi.include_router(route)
52
+ if not os.path.exists(self.frontend_dist_dir):
53
+ raise FileNotFoundError("frontend_dist_dir not found: " + self.frontend_dist_dir)
54
+ self.fastapi.mount("/", SinglePageApplication(directory=self.frontend_dist_dir))
55
+
56
+ def init_db(self):
57
+ alembic_ini_file_path = os.path.join(os.path.dirname(__file__), "alembic.ini")
58
+ sqlalchemy_url = Config(alembic_ini_file_path).get_main_option('sqlalchemy.url')
59
+ if sqlalchemy_url is None:
60
+ raise ValueError('sqlalchemy.url not set in alembic.ini file')
61
+ database.configure_db(sqlalchemy_url)
62
+ self.fastapi.add_middleware(database.DBSessionMiddleware)
63
+
64
+ def start(self):
65
+ print(f"Serving frontend at http://{self.host}:{self.port}")
66
+ uvicorn.run(self.fastapi, host=self.host, port=self.port, log_level=logging.WARNING)
67
+
68
+ async def on_shutdown(self):
69
+ await self.dispatcher.shutdown()