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.
- {gea_program-0.22.0 → gea_program-0.23.0}/PKG-INFO +17 -2
- {gea_program-0.22.0 → gea_program-0.23.0}/README.md +16 -1
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/__init__.py +1 -1
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/__main__.py +79 -1
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/acceptance_tests.py +180 -0
- gea_program-0.23.0/gea/dnp3.py +614 -0
- gea_program-0.23.0/gea/hartip.py +513 -0
- gea_program-0.23.0/gea/help/field-ports.md +11 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/helplib.py +1 -0
- gea_program-0.23.0/gea/modbus_rtu.py +439 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/patches.py +66 -3
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/service.py +6 -2
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/web/app.html +1 -1
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/workspace.py +1 -1
- {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/PKG-INFO +17 -2
- {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/SOURCES.txt +4 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/pyproject.toml +1 -1
- {gea_program-0.22.0 → gea_program-0.23.0}/LICENSE +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/backup.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/bench.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/blind_harness.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/certificates.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/cli.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/client_reports.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/config_versioning.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/conformance.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/correlation.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/dashboard.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/deviation.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/doctor.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/earth_model.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/elevation.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/etp.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/etp12_schemas.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/example_register_map.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/fat_sat.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/files.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/follower.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/forward_model.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/gamma.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/geodesy.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/alarms.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/backup.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/conformance.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/data-in.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/doctor.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/drift.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/etp.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/files.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/instruments.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/interval.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/month-end.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/notifications.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/osdu.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/patches.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/ppdm.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/quakeml.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/quality.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/seedlink.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/seismic.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/site.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/sites.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/soak.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/sra.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/start.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/structure.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/transients.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/upkeep.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/vibration.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/housekeeping.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/interval.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/jobs.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/live_ports.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/loadtest.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/modbus.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/model_card.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/month_end.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/notify.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/opcua_port.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/operator_app.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/osdu.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/permits.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/ports.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/ppdm.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/project.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/quakeml.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/reconciler.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/sample_record.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/sbom.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seedlink.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/segy.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_array.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_assoc.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_film.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_response.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_track.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/service_life.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/shapefile.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/shell.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/shut_in.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/sla_report.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/soak.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/sra.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/store_forward.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/strata_join.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/structure.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/supervisor.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/survey_view.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/telemetry.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/tool_library.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/transient.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/vibration.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/well_assembler.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/wits0.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea/witsml.py +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.22.0 → gea_program-0.23.0}/setup.cfg +0 -0
- {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.
|
|
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 (
|
|
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 (
|
|
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
|
|
@@ -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:
|