gea-program 0.22.0__tar.gz → 0.23.0__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 (259) hide show
  1. {gea_program-0.22.0 → gea_program-0.23.0}/PKG-INFO +17 -2
  2. {gea_program-0.22.0 → gea_program-0.23.0}/README.md +16 -1
  3. {gea_program-0.22.0 → gea_program-0.23.0}/gea/__init__.py +1 -1
  4. {gea_program-0.22.0 → gea_program-0.23.0}/gea/__main__.py +79 -1
  5. {gea_program-0.22.0 → gea_program-0.23.0}/gea/acceptance_tests.py +180 -0
  6. gea_program-0.23.0/gea/dnp3.py +614 -0
  7. gea_program-0.23.0/gea/hartip.py +513 -0
  8. gea_program-0.23.0/gea/help/field-ports.md +11 -0
  9. {gea_program-0.22.0 → gea_program-0.23.0}/gea/helplib.py +1 -0
  10. gea_program-0.23.0/gea/modbus_rtu.py +439 -0
  11. {gea_program-0.22.0 → gea_program-0.23.0}/gea/patches.py +66 -3
  12. {gea_program-0.22.0 → gea_program-0.23.0}/gea/service.py +6 -2
  13. {gea_program-0.22.0 → gea_program-0.23.0}/gea/web/app.html +1 -1
  14. {gea_program-0.22.0 → gea_program-0.23.0}/gea/workspace.py +1 -1
  15. {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/PKG-INFO +17 -2
  16. {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/SOURCES.txt +4 -0
  17. {gea_program-0.22.0 → gea_program-0.23.0}/pyproject.toml +1 -1
  18. {gea_program-0.22.0 → gea_program-0.23.0}/LICENSE +0 -0
  19. {gea_program-0.22.0 → gea_program-0.23.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  20. {gea_program-0.22.0 → gea_program-0.23.0}/gea/accuracy_statement.py +0 -0
  21. {gea_program-0.22.0 → gea_program-0.23.0}/gea/alarm_engine.py +0 -0
  22. {gea_program-0.22.0 → gea_program-0.23.0}/gea/backup.py +0 -0
  23. {gea_program-0.22.0 → gea_program-0.23.0}/gea/bench.py +0 -0
  24. {gea_program-0.22.0 → gea_program-0.23.0}/gea/blind_harness.py +0 -0
  25. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  26. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  27. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  28. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  29. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  30. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  31. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  32. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  33. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  34. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  35. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  36. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  37. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  38. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  39. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  40. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  41. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  42. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  43. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  44. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  45. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  46. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  47. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  48. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  49. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  50. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  51. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  52. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  53. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  54. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  55. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  56. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  57. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  58. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  59. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  60. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  61. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  62. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  63. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  64. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  65. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  66. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  67. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  68. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  69. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  70. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  71. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  72. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  73. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  74. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  75. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  76. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  77. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  78. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  79. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  80. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  81. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  82. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  83. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  84. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  85. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  86. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  87. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  88. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  89. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  90. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  91. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  92. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/med_160_sapropels.txt +0 -0
  93. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  94. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  95. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  96. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  97. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  98. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  99. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  100. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  101. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  102. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  103. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  104. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  105. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  106. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  107. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  108. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  109. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  110. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  111. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  112. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  113. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  114. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  115. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  116. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  117. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  118. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  119. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  120. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  121. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  122. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  123. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  124. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  125. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  126. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  127. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  128. {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  129. {gea_program-0.22.0 → gea_program-0.23.0}/gea/certificates.py +0 -0
  130. {gea_program-0.22.0 → gea_program-0.23.0}/gea/cli.py +0 -0
  131. {gea_program-0.22.0 → gea_program-0.23.0}/gea/client_reports.py +0 -0
  132. {gea_program-0.22.0 → gea_program-0.23.0}/gea/config_versioning.py +0 -0
  133. {gea_program-0.22.0 → gea_program-0.23.0}/gea/conformance.py +0 -0
  134. {gea_program-0.22.0 → gea_program-0.23.0}/gea/correlation.py +0 -0
  135. {gea_program-0.22.0 → gea_program-0.23.0}/gea/dashboard.py +0 -0
  136. {gea_program-0.22.0 → gea_program-0.23.0}/gea/deviation.py +0 -0
  137. {gea_program-0.22.0 → gea_program-0.23.0}/gea/doctor.py +0 -0
  138. {gea_program-0.22.0 → gea_program-0.23.0}/gea/downhole_engine.py +0 -0
  139. {gea_program-0.22.0 → gea_program-0.23.0}/gea/drift_monitor.py +0 -0
  140. {gea_program-0.22.0 → gea_program-0.23.0}/gea/earth_model.py +0 -0
  141. {gea_program-0.22.0 → gea_program-0.23.0}/gea/elevation.py +0 -0
  142. {gea_program-0.22.0 → gea_program-0.23.0}/gea/etp.py +0 -0
  143. {gea_program-0.22.0 → gea_program-0.23.0}/gea/etp12_schemas.json +0 -0
  144. {gea_program-0.22.0 → gea_program-0.23.0}/gea/example_register_map.json +0 -0
  145. {gea_program-0.22.0 → gea_program-0.23.0}/gea/fat_sat.py +0 -0
  146. {gea_program-0.22.0 → gea_program-0.23.0}/gea/files.py +0 -0
  147. {gea_program-0.22.0 → gea_program-0.23.0}/gea/follower.py +0 -0
  148. {gea_program-0.22.0 → gea_program-0.23.0}/gea/forward_model.py +0 -0
  149. {gea_program-0.22.0 → gea_program-0.23.0}/gea/gamma.py +0 -0
  150. {gea_program-0.22.0 → gea_program-0.23.0}/gea/gauge_aging.py +0 -0
  151. {gea_program-0.22.0 → gea_program-0.23.0}/gea/gauge_specs.py +0 -0
  152. {gea_program-0.22.0 → gea_program-0.23.0}/gea/geodesy.py +0 -0
  153. {gea_program-0.22.0 → gea_program-0.23.0}/gea/gravity_reference.py +0 -0
  154. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/TESTER_GUIDE.md +0 -0
  155. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/alarms.md +0 -0
  156. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/audit-update.md +0 -0
  157. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/backup.md +0 -0
  158. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/conformance.md +0 -0
  159. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/data-in.md +0 -0
  160. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/doctor.md +0 -0
  161. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/drift.md +0 -0
  162. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/etp.md +0 -0
  163. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/files.md +0 -0
  164. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/instruments.md +0 -0
  165. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/interval.md +0 -0
  166. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/month-end.md +0 -0
  167. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/notifications.md +0 -0
  168. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/osdu.md +0 -0
  169. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/patches.md +0 -0
  170. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/ppdm.md +0 -0
  171. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/quakeml.md +0 -0
  172. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/quality.md +0 -0
  173. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/seedlink.md +0 -0
  174. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/seismic.md +0 -0
  175. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/site.md +0 -0
  176. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/sites.md +0 -0
  177. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/soak.md +0 -0
  178. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/sra.md +0 -0
  179. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/start.md +0 -0
  180. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/structure.md +0 -0
  181. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/transients.md +0 -0
  182. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/upkeep.md +0 -0
  183. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/vibration.md +0 -0
  184. {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/well-tests.md +0 -0
  185. {gea_program-0.22.0 → gea_program-0.23.0}/gea/housekeeping.py +0 -0
  186. {gea_program-0.22.0 → gea_program-0.23.0}/gea/interval.py +0 -0
  187. {gea_program-0.22.0 → gea_program-0.23.0}/gea/inverse_engine.py +0 -0
  188. {gea_program-0.22.0 → gea_program-0.23.0}/gea/jobs.py +0 -0
  189. {gea_program-0.22.0 → gea_program-0.23.0}/gea/live_ports.py +0 -0
  190. {gea_program-0.22.0 → gea_program-0.23.0}/gea/loadtest.py +0 -0
  191. {gea_program-0.22.0 → gea_program-0.23.0}/gea/matplotlib_demo.py +0 -0
  192. {gea_program-0.22.0 → gea_program-0.23.0}/gea/modbus.py +0 -0
  193. {gea_program-0.22.0 → gea_program-0.23.0}/gea/model_card.py +0 -0
  194. {gea_program-0.22.0 → gea_program-0.23.0}/gea/month_end.py +0 -0
  195. {gea_program-0.22.0 → gea_program-0.23.0}/gea/mqtt_port.py +0 -0
  196. {gea_program-0.22.0 → gea_program-0.23.0}/gea/notify.py +0 -0
  197. {gea_program-0.22.0 → gea_program-0.23.0}/gea/opcua_port.py +0 -0
  198. {gea_program-0.22.0 → gea_program-0.23.0}/gea/operator_app.py +0 -0
  199. {gea_program-0.22.0 → gea_program-0.23.0}/gea/osdu.py +0 -0
  200. {gea_program-0.22.0 → gea_program-0.23.0}/gea/permits.py +0 -0
  201. {gea_program-0.22.0 → gea_program-0.23.0}/gea/ports.py +0 -0
  202. {gea_program-0.22.0 → gea_program-0.23.0}/gea/ppdm.py +0 -0
  203. {gea_program-0.22.0 → gea_program-0.23.0}/gea/profile_catalog.py +0 -0
  204. {gea_program-0.22.0 → gea_program-0.23.0}/gea/project.py +0 -0
  205. {gea_program-0.22.0 → gea_program-0.23.0}/gea/qt6_downhole_app.py +0 -0
  206. {gea_program-0.22.0 → gea_program-0.23.0}/gea/quakeml.py +0 -0
  207. {gea_program-0.22.0 → gea_program-0.23.0}/gea/reconciler.py +0 -0
  208. {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  209. {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  210. {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  211. {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  212. {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  213. {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  214. {gea_program-0.22.0 → gea_program-0.23.0}/gea/rock_inventory.py +0 -0
  215. {gea_program-0.22.0 → gea_program-0.23.0}/gea/sample_record.py +0 -0
  216. {gea_program-0.22.0 → gea_program-0.23.0}/gea/sample_well_profile.csv +0 -0
  217. {gea_program-0.22.0 → gea_program-0.23.0}/gea/sbom.py +0 -0
  218. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seedlink.py +0 -0
  219. {gea_program-0.22.0 → gea_program-0.23.0}/gea/segy.py +0 -0
  220. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic.py +0 -0
  221. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_array.py +0 -0
  222. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_assoc.py +0 -0
  223. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_detect.py +0 -0
  224. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_film.py +0 -0
  225. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_harmonic.py +0 -0
  226. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_qc.py +0 -0
  227. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_response.py +0 -0
  228. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_signature.py +0 -0
  229. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_track.py +0 -0
  230. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_uncertainty.py +0 -0
  231. {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_unlisted.py +0 -0
  232. {gea_program-0.22.0 → gea_program-0.23.0}/gea/sensor_swap.py +0 -0
  233. {gea_program-0.22.0 → gea_program-0.23.0}/gea/service_life.py +0 -0
  234. {gea_program-0.22.0 → gea_program-0.23.0}/gea/shapefile.py +0 -0
  235. {gea_program-0.22.0 → gea_program-0.23.0}/gea/shell.py +0 -0
  236. {gea_program-0.22.0 → gea_program-0.23.0}/gea/shut_in.py +0 -0
  237. {gea_program-0.22.0 → gea_program-0.23.0}/gea/sla_report.py +0 -0
  238. {gea_program-0.22.0 → gea_program-0.23.0}/gea/soak.py +0 -0
  239. {gea_program-0.22.0 → gea_program-0.23.0}/gea/sra.py +0 -0
  240. {gea_program-0.22.0 → gea_program-0.23.0}/gea/store_forward.py +0 -0
  241. {gea_program-0.22.0 → gea_program-0.23.0}/gea/strata_join.py +0 -0
  242. {gea_program-0.22.0 → gea_program-0.23.0}/gea/structure.py +0 -0
  243. {gea_program-0.22.0 → gea_program-0.23.0}/gea/supervisor.py +0 -0
  244. {gea_program-0.22.0 → gea_program-0.23.0}/gea/survey_cmd.py +0 -0
  245. {gea_program-0.22.0 → gea_program-0.23.0}/gea/survey_view.py +0 -0
  246. {gea_program-0.22.0 → gea_program-0.23.0}/gea/telemetry.py +0 -0
  247. {gea_program-0.22.0 → gea_program-0.23.0}/gea/tool_library.py +0 -0
  248. {gea_program-0.22.0 → gea_program-0.23.0}/gea/transient.py +0 -0
  249. {gea_program-0.22.0 → gea_program-0.23.0}/gea/vibration.py +0 -0
  250. {gea_program-0.22.0 → gea_program-0.23.0}/gea/well_assembler.py +0 -0
  251. {gea_program-0.22.0 → gea_program-0.23.0}/gea/well_test_validation.py +0 -0
  252. {gea_program-0.22.0 → gea_program-0.23.0}/gea/wits0.py +0 -0
  253. {gea_program-0.22.0 → gea_program-0.23.0}/gea/witsml.py +0 -0
  254. {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/dependency_links.txt +0 -0
  255. {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/entry_points.txt +0 -0
  256. {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/requires.txt +0 -0
  257. {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/top_level.txt +0 -0
  258. {gea_program-0.22.0 → gea_program-0.23.0}/setup.cfg +0 -0
  259. {gea_program-0.22.0 → gea_program-0.23.0}/tests/test_acceptance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gea-program
3
- Version: 0.22.0
3
+ Version: 0.23.0
4
4
  Summary: GEA-Program: downhole gauge monitoring - canonical measurement records, quality rules, drift evaluation with a re-fit change log, well-test validation, alarm management, model cards, store-and-forward, monthly SLA measurement, FAT/SAT protocols and a dashboard, generated from live objects in the client's reporting outline.
5
5
  Author: Daniel T. Murphy
6
6
  License: MPL-2.0
@@ -125,7 +125,7 @@ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --
125
125
  gea client-report --report accuracy --out client_report
126
126
  gea model-cards --out model_cards
127
127
  gea sbom --out sbom
128
- gea accept # the product gate (353 checks)
128
+ gea accept # the product gate (356 checks)
129
129
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
130
130
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
131
131
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -648,6 +648,21 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
648
648
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
649
649
  `rtserve.iris.washington.edu:18000`, network `TX`.
650
650
 
651
+ ### The field ports
652
+
653
+ The three links a gauge panel on a pad actually offers, each read-only in
654
+ the code (no write, control, time set or device-changing command exists),
655
+ each refusing to run without the site's map and its source, each with a
656
+ stand-in on the loopback to rehearse against, each one more protocol on the
657
+ Patch panel: **Modbus RTU** (`gea modbus-rtu`; the RS-485 drop through
658
+ pyserial, the `serial` extra, or RTU over TCP through a serial-to-Ethernet
659
+ converter; CRC-16 framed and checked by this program; functions 03 and 04),
660
+ **DNP3** (`gea dnp3`; IEEE 1815 data link, transport and application layers;
661
+ integrity and event polls; analog, binary and counter objects with their
662
+ flags and 48-bit event times; confirms and unsolicited responses handled),
663
+ **HART-IP** (`gea hartip`; the session, command 0, 3 and 9; HART unit codes
664
+ converted; device and variable status to quality). `gea help field-ports`.
665
+
651
666
  ### The interval against the mapped tops
652
667
 
653
668
  Where does the injection sit in the geology? The operator declares the
@@ -86,7 +86,7 @@ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --
86
86
  gea client-report --report accuracy --out client_report
87
87
  gea model-cards --out model_cards
88
88
  gea sbom --out sbom
89
- gea accept # the product gate (353 checks)
89
+ gea accept # the product gate (356 checks)
90
90
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
91
91
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
92
92
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -609,6 +609,21 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
609
609
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
610
610
  `rtserve.iris.washington.edu:18000`, network `TX`.
611
611
 
612
+ ### The field ports
613
+
614
+ The three links a gauge panel on a pad actually offers, each read-only in
615
+ the code (no write, control, time set or device-changing command exists),
616
+ each refusing to run without the site's map and its source, each with a
617
+ stand-in on the loopback to rehearse against, each one more protocol on the
618
+ Patch panel: **Modbus RTU** (`gea modbus-rtu`; the RS-485 drop through
619
+ pyserial, the `serial` extra, or RTU over TCP through a serial-to-Ethernet
620
+ converter; CRC-16 framed and checked by this program; functions 03 and 04),
621
+ **DNP3** (`gea dnp3`; IEEE 1815 data link, transport and application layers;
622
+ integrity and event polls; analog, binary and counter objects with their
623
+ flags and 48-bit event times; confirms and unsolicited responses handled),
624
+ **HART-IP** (`gea hartip`; the session, command 0, 3 and 9; HART unit codes
625
+ converted; device and variable status to quality). `gea help field-ports`.
626
+
612
627
  ### The interval against the mapped tops
613
628
 
614
629
  Where does the injection sit in the geology? The operator declares the
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.22.0"
126
+ __version__ = "0.23.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -278,6 +278,20 @@ def main(argv=None) -> int:
278
278
  p_lp.add_argument("--stream-csv", type=str, default=None, help="also write a historian-style CSV the other commands ingest")
279
279
  p_lp.add_argument("--selftest", action="store_true", help="etp: the codec round-trip and a session against the simulated store on the loopback")
280
280
 
281
+ for name, helptxt in (("modbus-rtu", "Modbus RTU port, read-only: the serial line (pyserial) or RTU over TCP; functions 03/04 only; the stand-in slave with --selftest"),
282
+ ("dnp3", "DNP3 (IEEE 1815) master, read-only: integrity and event polls over TCP; analog inputs, binaries, counters with flags and event times; the stand-in outstation with --selftest"),
283
+ ("hartip", "HART-IP client, read-only: session, command 0, 3 and 9; the variables with their unit codes and status; the stand-in device with --selftest")):
284
+ p_fp = sub.add_parser(name, help=helptxt)
285
+ p_fp.add_argument("--config", type=str, default=None, help="site map JSON (client-owned): the transport, the mapping and its source")
286
+ p_fp.add_argument("--write-example-config", type=str, default=None, help="write the example map (the stand-in) here and exit")
287
+ p_fp.add_argument("--seconds", type=float, default=None, help="run for this long (polls = seconds / interval_s); default the map's polls")
288
+ p_fp.add_argument("--polls", type=int, default=None, help="poll this many times instead")
289
+ p_fp.add_argument("--record", type=str, default=None, help="write one JSON line per poll (the values and faults) here")
290
+ p_fp.add_argument("--replay", type=str, default=None, help="not offered by these ports (a poll is a request; there is nothing to replay)")
291
+ p_fp.add_argument("--out", type=str, default=None, help="write the records CSV here")
292
+ p_fp.add_argument("--stream-csv", type=str, default=None, help="also write a historian-style CSV the other commands ingest")
293
+ p_fp.add_argument("--selftest", action="store_true", help="the codec vectors and a session against the stand-in on the loopback")
294
+
281
295
  p_ws = sub.add_parser("workspace", help="the client site folder: init, add wells (file | catalogue | live), list, migrate a --out folder, refresh the dashboard, audit log")
282
296
  p_ws.add_argument("--path", type=str, required=True, help="the workspace folder")
283
297
  p_ws.add_argument("--action", type=str, default="list",
@@ -378,7 +392,7 @@ def main(argv=None) -> int:
378
392
  p_ws.add_argument("--entry", type=str, default=None, help="add-catalog: catalogue entry")
379
393
  p_ws.add_argument("--well", type=str, default=None, help="add-catalog: well tag inside the entry; remove: well id")
380
394
  p_ws.add_argument("--station-md", type=float, default=None, help="station MD in ft (catalogue wells need it)")
381
- p_ws.add_argument("--port", type=str, default=None, choices=["opcua", "mqtt", "modbus_g6"], help="add-live: the port")
395
+ p_ws.add_argument("--port", type=str, default=None, choices=["opcua", "mqtt", "modbus_g6", "wits0", "witsml", "etp", "modbus_rtu", "dnp3", "hartip"], help="add-live: the port")
382
396
  p_ws.add_argument("--from", dest="from_dir", type=str, default=None, help="migrate: a folder written by `gea dashboard --out`")
383
397
  p_ws.add_argument("--actor", type=str, default="cli", help="who is acting (recorded in the audit log)")
384
398
  p_ws.add_argument("--month", type=str, default=None, help="refresh: also measure the SLA for YYYY-MM")
@@ -2268,6 +2282,70 @@ def main(argv=None) -> int:
2268
2282
  stop.set()
2269
2283
  print("etp-sim: stopped")
2270
2284
  return 0
2285
+ elif a.cmd in ("modbus-rtu", "dnp3", "hartip"):
2286
+ import json as _json
2287
+ from .live_ports import records_to_stream, summarize, write_records_csv
2288
+ mod = __import__({"modbus-rtu": "gea.modbus_rtu", "dnp3": "gea.dnp3", "hartip": "gea.hartip"}[a.cmd], fromlist=["x"])
2289
+ if a.selftest:
2290
+ r = mod.selftest()
2291
+ print(f"{a.cmd} selftest: {'OK' if r['ok'] else 'FAILED'}")
2292
+ for k, v in r.items():
2293
+ if k != "ok":
2294
+ print(f" {'ok ' if v else 'BAD'} {k}")
2295
+ return 0 if r["ok"] else 1
2296
+ if a.write_example_config:
2297
+ with open(a.write_example_config, "w", encoding="utf-8") as f:
2298
+ _json.dump(mod.EXAMPLE_CONFIG, f, indent=1)
2299
+ print("config:", a.write_example_config); return 0
2300
+ if a.replay:
2301
+ raise SystemExit(f"{a.cmd}: a poll is a request to the device; there is no recording to replay - run against the device or the stand-in")
2302
+ if not a.config:
2303
+ raise SystemExit(f"{a.cmd} needs --config (or --write-example-config to start one)")
2304
+ cfg = mod.load_config(a.config)
2305
+ polls = a.polls
2306
+ if polls is None and a.seconds is not None:
2307
+ polls = max(1, int(round(a.seconds / max(float(cfg.get("interval_s", 1.0)), 0.05))))
2308
+ from .patches import close_field_port, open_field_port
2309
+ proto = {"modbus-rtu": "modbus_rtu", "dnp3": "dnp3", "hartip": "hartip"}[a.cmd]
2310
+ try:
2311
+ tap = open_field_port(proto, mod, cfg)
2312
+ except (NotImplementedError, ConnectionError, ValueError, OSError) as e:
2313
+ raise SystemExit(f"{a.cmd}: {e}")
2314
+ rec_f = open(a.record, "w", encoding="utf-8") if a.record else None
2315
+ n = int(cfg.get("polls", 3) if polls is None else polls)
2316
+ dt = float(cfg.get("interval_s", 1.0))
2317
+ try:
2318
+ for k in range(n):
2319
+ before = len(tap.records)
2320
+ r = tap.poll_once(integrity=(k == 0)) if a.cmd == "dnp3" else tap.poll_once()
2321
+ if rec_f:
2322
+ rec_f.write(_json.dumps({"poll": k + 1, "utc": tap.records[-1].ingest_timestamp_utc if tap.records else None,
2323
+ "values": {x.tag_id: x.value for x in tap.records[before:]}, "quality": {x.tag_id: x.quality_flag for x in tap.records[before:]},
2324
+ "detail": (r if isinstance(r, dict) and a.cmd != "modbus-rtu" else None)}, default=str) + "\n")
2325
+ if dt > 0 and k < n - 1:
2326
+ import time as _time
2327
+ _time.sleep(dt)
2328
+ finally:
2329
+ close_field_port(proto, tap)
2330
+ if rec_f:
2331
+ rec_f.close()
2332
+ recs = tap.records
2333
+ print(f"{a.cmd}: {_json.dumps(tap.summary(), default=str)}")
2334
+ print(_json.dumps(summarize(recs), indent=1))
2335
+ if a.out:
2336
+ print(" records:", write_records_csv(recs, a.out))
2337
+ if a.stream_csv and recs:
2338
+ st = records_to_stream(recs, name=proto)
2339
+ import csv as _csv
2340
+ from datetime import datetime as _dt, timedelta as _td
2341
+ t0 = _dt.fromisoformat(st.meta["start_time"])
2342
+ with open(a.stream_csv, "w", newline="", encoding="utf-8") as f:
2343
+ w = _csv.writer(f)
2344
+ w.writerow(["timestamp"] + list(st.channels))
2345
+ for i, t in enumerate(st.index):
2346
+ w.writerow([(t0 + _td(seconds=float(t))).isoformat()] + ["" if st.channels[c].values[i] != st.channels[c].values[i] else round(float(st.channels[c].values[i]), 4) for c in st.channels])
2347
+ print(" stream:", a.stream_csv)
2348
+ return 0
2271
2349
  elif a.cmd in ("opcua", "mqtt", "wits0", "witsml", "etp"):
2272
2350
  import json as _json
2273
2351
  from .live_ports import records_to_stream, summarize, write_records_csv
@@ -6595,6 +6595,185 @@ def section_bp_interval(tmp: str) -> None:
6595
6595
  + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
6596
6596
 
6597
6597
 
6598
+ def section_bq_field_ports(tmp: str) -> None:
6599
+ """Section BQ - the field ports: Modbus RTU framed and parsed as specified over a serial-shaped transport and over TCP,
6600
+ DNP3 link / transport / application layers against a stand-in outstation, HART-IP sessions and commands 0, 3 and 9
6601
+ against a stand-in device; each through the patch supervisor and the CLI; the help."""
6602
+ import http.cookiejar
6603
+ import struct
6604
+ import subprocess as _sp
6605
+ import urllib.request
6606
+ import urllib.error
6607
+ from . import modbus_rtu as MR
6608
+ from . import dnp3 as DN
6609
+ from . import hartip as HT
6610
+ from . import helplib as H
6611
+ from .patches import PROTOCOLS, PatchSupervisor, validate_port_config
6612
+ from .telemetry import TelemetryRecorder, TelemetryConfig
6613
+ from .downhole_engine import DownholeEngine, SimulatorConfig
6614
+ from .workspace import Workspace, WorkspaceError
6615
+ from .service import Service, Users
6616
+ pkg = Path(__file__).parent
6617
+ d = Path(tmp, "bq"); d.mkdir()
6618
+
6619
+ # -- BQ1: Modbus RTU --------------------------------------------------------------------------------------------
6620
+ st = MR.selftest()
6621
+ sim = MR.SimSlave(7); sim.set(200, 1234.5, 'float32', 'little'); sim.set(202, -3, 'int16')
6622
+ h, port = sim.serve_tcp()
6623
+ try:
6624
+ p = MR.RtuPort({'transport': 'tcp', 'host': '127.0.0.1', 'port': port, 'unit_id': 7, 'table': 'input', 'word_order': 'little', 'map_source': 'gate',
6625
+ 'mapping': [{'address': 200, 'tag_id': 'A', 'type': 'float32', 'unit': 'psi'}, {'address': 202, 'tag_id': 'B', 'type': 'int16', 'unit': ''},
6626
+ {'address': 300, 'tag_id': 'C', 'type': 'uint16', 'unit': ''}]})
6627
+ p.run(polls=2, interval_s=0.05)
6628
+ vals = {r.tag_id: (r.value, r.quality_flag, r.rule_fired) for r in p.records[-3:]}
6629
+ finally:
6630
+ h.close()
6631
+ no_map = None
6632
+ try:
6633
+ MR.RtuPort({'transport': 'sim', 'mapping': [{'address': 1, 'tag_id': 'x'}]})
6634
+ except ValueError as e:
6635
+ no_map = str(e)
6636
+ req = MR.read_request(7, 'input', 200, 2)
6637
+ c1 = {
6638
+ "selftest": st["ok"],
6639
+ "request_as_specified": req[:6] == bytes([7, 4, 0, 200, 0, 2]) and MR.crc16(req[:6]) == struct.unpack('<H', req[6:])[0],
6640
+ "rtu_over_tcp": abs(vals['A'][0] - 1234.5) < 1e-3 and vals['A'][1] == 'GOOD' and vals['B'][0] == -3.0 and vals['C'][1] == 'GAP' and 'ILLEGAL DATA ADDRESS' in vals['C'][2]
6641
+ and p.summary()['polls'] == 2 and len(p.faults) == 2,
6642
+ "map_source_required": no_map and 'source' in no_map,
6643
+ "serial_needs_extra": (lambda: (_ for _ in ()).throw(SystemExit)) # placeholder replaced below
6644
+ }
6645
+ try:
6646
+ MR.SerialTransport('/dev/nonexistent-gea-port', 9600)
6647
+ c1["serial_needs_extra"] = True # pyserial present: it raised on the device, below
6648
+ except NotImplementedError as e:
6649
+ c1["serial_needs_extra"] = 'gea-program[serial]' in str(e)
6650
+ except Exception: # noqa: BLE001 - pyserial installed and no such port: the dependency path is fine
6651
+ c1["serial_needs_extra"] = True
6652
+ failed1 = [k for k, v in c1.items() if not v]
6653
+ ok(not failed1,
6654
+ "BQ1 Modbus RTU: the CRC against the specification's vector and every register type both word orders round-tripped; a read request framed "
6655
+ "as specified (unit, function, address, count, CRC low byte first); RTU over TCP against the stand-in slave - an input register in little word "
6656
+ "order, a signed register, and a register the slave does not have answered ILLEGAL DATA ADDRESS and recorded as GAP with the fault; an "
6657
+ "exception, a corrupt CRC and a silent slave each named; a map without its source refused; the serial transport names the extra it needs"
6658
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
6659
+
6660
+ # -- BQ2: DNP3 and HART-IP ---------------------------------------------------------------------------------------
6661
+ sd = DN.selftest()
6662
+ sh = HT.selftest()
6663
+ sim_o = DN.SimOutstation(outstation=4, master=3, analogs=[10.0, 20.0], flags=[0x01, 0x21], binaries=[0], counters=[9], events=False)
6664
+ h, port = sim_o.serve()
6665
+ try:
6666
+ pd_ = DN.Dnp3Port({'transport': 'tcp', 'host': '127.0.0.1', 'port': port, 'master': 3, 'outstation': 4, 'point_source': 'gate',
6667
+ 'mapping': [{'address': 'ai:0', 'tag_id': 'P', 'unit': 'psi'}, {'address': 'ai:1', 'tag_id': 'T', 'unit': 'degF'}, {'address': 'ai:9', 'tag_id': 'X', 'unit': ''}]})
6668
+ pd_.run(polls=1)
6669
+ q = {r.tag_id: (r.value, r.quality_flag, r.rule_fired) for r in pd_.records}
6670
+ sd_sum = pd_.summary()
6671
+ finally:
6672
+ h.close()
6673
+ wrong = DN.Dnp3Port({'transport': 'tcp', 'host': '127.0.0.1', 'port': 9, 'point_source': 'gate', 'mapping': [{'address': 'ai:0', 'tag_id': 'P'}]}, sock=None)
6674
+ bad_addr = None
6675
+ try:
6676
+ DN.Dnp3Port({'point_source': 'gate', 'mapping': [{'address': 'analog 3', 'tag_id': 'P'}]})
6677
+ except ValueError as e:
6678
+ bad_addr = str(e)
6679
+ sim_h = HT.SimDevice(pv=100.0, fixed_variable=5); h, port = sim_h.serve()
6680
+ try:
6681
+ ph = HT.HartIpPort({'transport': 'tcp', 'host': '127.0.0.1', 'port': port, 'device_source': 'gate',
6682
+ 'mapping': [{'address': 'pv', 'tag_id': 'P', 'unit': 'kPa'}, {'address': 'dv:5', 'tag_id': 'Q', 'unit': 'bbl/min'}, {'address': 'dv:77', 'tag_id': 'Z', 'unit': ''}]})
6683
+ ph.run(polls=1)
6684
+ hq = {r.tag_id: (r.value, r.quality_flag, r.rule_fired, r.unit) for r in ph.records}
6685
+ finally:
6686
+ h.close()
6687
+ c2 = {
6688
+ "dnp3_selftest": sd["ok"], "hartip_selftest": sh["ok"],
6689
+ "dnp3_flags": q['P'] == (10.0, 'GOOD', '') and q['T'][1] == 'BAD' and 'OVER_RANGE' in q['T'][2] and q['T'][0] == 20.0 and 'X' not in q
6690
+ and sd_sum['unmapped'] == {'bi:0': 1, 'ctr:0': 1} and sd_sum['outstation'] == 4 and sim_o.requests == [(1, 2, 3, 0)],
6691
+ "dnp3_refusals": bad_addr and 'ai:<index>' in bad_addr and wrong.master.outstation == 10,
6692
+ "hartip_units_and_status": hq['P'][1] == 'GOOD' and abs(hq['P'][0] - 100.0 / 0.14503773773) < 0.01 and hq['P'][3] == 'kPa'
6693
+ and hq['Q'][1] == 'BAD' and 'MANUAL/FIXED' in hq['Q'][2] and abs(hq['Q'][0] - 7.25) < 1e-3
6694
+ and hq['Z'][1] == 'GAP' and 'not in the answer' in hq['Z'][2] and sim_h.commands[:3] == [0, 3, 9],
6695
+ }
6696
+ failed2 = [k for k, v in c2.items() if not v]
6697
+ ok(not failed2,
6698
+ "BQ2 DNP3: the CRC against the specification's frame, a link round trip, every variation this master reads parsed with flags and the 48-bit "
6699
+ "event time, an unknown object named; an integrity poll against the stand-in with its confirm and its unsolicited null response, the event's "
6700
+ "time as the source time, a corrupt frame named; a point with OVER_RANGE carried as BAD with the flag, unmapped points counted, a bad point "
6701
+ "address refused. HART-IP: the PDU round trip and check byte, command 3 parsed, a session with command 0 taking the long address, a PV the "
6702
+ "device sends in psi recorded in kPa, a MANUAL/FIXED variable BAD with the status, a variable the device does not have GAP, DEVICE_MALFUNCTION "
6703
+ "and a refused command 9 named"
6704
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
6705
+
6706
+ # -- BQ3: the supervisor, the service, the CLI, the help ------------------------------------------------------------
6707
+ hist = str(d / "hist.csv")
6708
+ TelemetryRecorder(engine=DownholeEngine(SimulatorConfig()), config=TelemetryConfig(duration_hours=0.5, seed=3)).run().export_csv(hist)
6709
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
6710
+ w = ws.add_well_file(hist, display="Well F", actor="tester")
6711
+ sup = PatchSupervisor(ws)
6712
+ cfg_rtu = dict(MR.EXAMPLE_CONFIG, interval_s=0.2)
6713
+ cfg_dnp = dict(DN.EXAMPLE_CONFIG, interval_s=0.2)
6714
+ cfg_hart = dict(HT.EXAMPLE_CONFIG, interval_s=0.2)
6715
+ for name, proto, cfg in (("rtu", "modbus_rtu", cfg_rtu), ("rtu-dnp", "dnp3", cfg_dnp), ("hart", "hartip", cfg_hart)):
6716
+ sup.store.add(name, proto, w["id"], cfg, "tester", priority=1, stale_after_s=5)
6717
+ bad_cfg = None
6718
+ try:
6719
+ validate_port_config("dnp3", {"mapping": [{"address": "ai:0", "tag_id": "P"}]})
6720
+ except ValueError as e:
6721
+ bad_cfg = str(e)
6722
+ for name in ("rtu", "rtu-dnp", "hart"):
6723
+ sup.start(name, "tester")
6724
+ time.sleep(3.0)
6725
+ states = {x["name"]: x for x in sup.states()}
6726
+ for name in ("rtu", "rtu-dnp", "hart"):
6727
+ sup.stop(name, "tester")
6728
+ time.sleep(0.5)
6729
+ stopped = {x["name"]: x["status"] for x in sup.states()}
6730
+ users = Users(str(Path(wsp, "users.json"))); users.add("op", "operator-pass-1", "operator")
6731
+ svc = Service(wsp, port=0, scheduler=False).start()
6732
+ base_url = svc.url.rstrip("/")
6733
+ opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
6734
+
6735
+ def call(method, path, body=None):
6736
+ req = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
6737
+ req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
6738
+ try:
6739
+ with opener.open(req, timeout=120) as rr_:
6740
+ return rr_.status, json.loads(rr_.read())
6741
+ except urllib.error.HTTPError as e:
6742
+ return e.code, json.loads(e.read())
6743
+ try:
6744
+ call("POST", "/api/login", {"name": "op", "password": "operator-pass-1"})
6745
+ st_p, pl = call("GET", "/api/patches")
6746
+ st_x, ex = call("GET", "/api/config/defaults/hartip")
6747
+ finally:
6748
+ svc.stop()
6749
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
6750
+ help_md = (pkg / "help" / "field-ports.md").read_text(encoding="utf-8")
6751
+ cfg_path = str(d / "rtu.json")
6752
+ r_ex = _sp.run([sys.executable, "-m", "gea", "modbus-rtu", "--write-example-config", cfg_path], capture_output=True, text=True)
6753
+ r_run = _sp.run([sys.executable, "-m", "gea", "modbus-rtu", "--config", cfg_path, "--polls", "2", "--out", str(d / "rtu.csv"), "--stream-csv", str(d / "rtu_stream.csv"), "--record", str(d / "rtu.jsonl")],
6754
+ capture_output=True, text=True)
6755
+ r_self = _sp.run([sys.executable, "-m", "gea", "dnp3", "--selftest"], capture_output=True, text=True)
6756
+ r_replay = _sp.run([sys.executable, "-m", "gea", "hartip", "--config", cfg_path, "--replay", "x"], capture_output=True, text=True)
6757
+ c3 = {
6758
+ "protocols": all(x in PROTOCOLS for x in ("modbus_rtu", "dnp3", "hartip")) and bad_cfg and "source" in bad_cfg,
6759
+ "supervised": all(states[n]["status"] == "CONNECTED" and states[n]["samples_total"] >= 6 for n in ("rtu", "rtu-dnp", "hart"))
6760
+ and abs(states["rtu"]["tags"]["DHP_psi"]["value"] - 3185.5) < 1e-3 and abs(states["rtu-dnp"]["tags"]["INJ_bbl"]["value"] - 123456.0) < 1e-6
6761
+ and abs(states["hart"]["tags"]["DHT_degF"]["value"] - 212.3) < 1e-3 and all(stopped[n] == "STOPPED" for n in ("rtu", "rtu-dnp", "hart")),
6762
+ "service": st_p == 200 and set(("modbus_rtu", "dnp3", "hartip")) <= set(pl["protocols"]) and st_x == 200 and ex["content"]["mapping"][0]["address"] == "pv",
6763
+ "page": 'value="modbus_rtu"' in page_src and 'value="dnp3"' in page_src and 'value="hartip"' in page_src,
6764
+ "cli": r_ex.returncode == 0 and r_run.returncode == 0 and '"polls": 2' in r_run.stdout and os.path.isfile(d / "rtu_stream.csv") and open(d / "rtu.jsonl").read().count("\n") == 2
6765
+ and r_self.returncode == 0 and "dnp3 selftest: OK" in r_self.stdout and r_replay.returncode != 0 and "no recording to replay" in (r_replay.stderr + r_replay.stdout),
6766
+ "help": any(t["topic"] == "field-ports" for t in H.topics()) and not H.check("field-ports") and "there is no code path for a Modbus write" in help_md.split("will not call a measurement")[1],
6767
+ }
6768
+ failed3 = [k for k, v in c3.items() if not v]
6769
+ ok(not failed3,
6770
+ "BQ3 the three ports on the Patch panel: a config without its source refused on commit; each supervised patch CONNECTED against its stand-in "
6771
+ "with the values on the patch row, stopped on request; the service lists the protocols and serves the example maps; the page offers them for "
6772
+ "a live well; `gea modbus-rtu` writes the example, polls it with the records, stream and per-poll recording, `--selftest` runs, `--replay` is "
6773
+ "refused with the reason; the help page"
6774
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
6775
+
6776
+
6598
6777
  def main() -> int:
6599
6778
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
6600
6779
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -6666,6 +6845,7 @@ def main() -> int:
6666
6845
  section_bn_soak(tmp)
6667
6846
  section_bo_structure(tmp)
6668
6847
  section_bp_interval(tmp)
6848
+ section_bq_field_ports(tmp)
6669
6849
  if _FAILS:
6670
6850
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
6671
6851
  for f in _FAILS: