pycsamt.iot#
IoT field telemetry, edge processing, synchronisation, power budgeting, security, provenance, and simulation helpers for AMT, MT, CSAMT, and CSEM deployments.
The pycsamt.iot package is the public API for IoT-enabled field
acquisition. It is intentionally separate from the EDI/impedance science
API: IoT objects describe what happened in the field, what packets were
sent, what edge decisions were made, how clocks and power behaved, and how
the acquisition can be audited. Processed transfer functions still flow
through the normal pycsamt.emtools, pycsamt.site, and
pycsamt.z layers.
Implemented Capabilities#
The package implements concrete capabilities that can be reported in a methods section:
device and station metadata, including station coordinates, line position, electric dipole geometry, channels, and recorder IDs;
canonical telemetry packets and payload schemas for data, QC, health, power, synchronisation, event, and acquisition messages;
dry-run and real telemetry clients for file, HTTP(S), MQTT, serial, and WebSocket transports, with a generic recorder fallback for protocols such as LoRa, and a store-and-forward wrapper that buffers packets (with an optional on-disk spool) across an intermittent uplink;
generic edge QC: decimation, finite coverage, RMS/statistics, robust spike fraction, per-channel accept/reject decisions, and QC telemetry packet export;
AMT/CSAMT edge QC: powerline harmonic detection, channel SNR, saturation, contact-resistance proxy checks, frequency coverage, live spectra, impedance stability, and sensor dropout checks;
CSAMT/CSEM controlled-source edge QC: skin-depth near/transition/far field-zone classification, transmitter frequency-comb detection, source current/voltage stability, and CSEM magnitude/phase-versus-offset (MVO/PVO) analysis, plus a
transmitterdevice role, aSourcePayloadtransmitter telemetry schema, and a transmitter timing-lock extension on the sync payload;a galvanic static-shift indicator that separates a frequency-independent resistivity split (static shift) from anisotropy;
a data-model bridge that turns edge impedance estimates into a
pycsamt.z.z.Z, writes a preliminaryEDIFile, promotes a session into EDI-backed sites for the processing pipeline, routes those sites throughpycsamt.emtoolsQC so field and post-processing QC agree, and seeds a re-occupation-readyFieldSessionorDeploymentConfigfrom an existing EDI survey;method-aware QC: per-method acquisition profiles (frequency band, required channels, controlled-source and powerline-sensitivity flags) that drive monitoring thresholds and gate powerline detection;
telemetry stream monitoring for AMT, MT, CSAMT, TEM, and TDEM, including packet success, edge acceptance, latency, packet gaps, clock offset, battery voltage, required channels, and frequency-band checks;
power-budget estimation with duty cycle, battery reserve, regulator and charge efficiency, solar harvesting, telemetry load, edge-processing load, auxiliary load, status classification, and power packet export;
clock synchronisation audit: offset, drift, jitter, GPS lock, quality grade, batch status tables, and GPS-dropout detection;
acquisition provenance: station audit records, QC decision logs, raw-file hashes, manifests, and reproducibility bundles, with optional HMAC manifest signing and a tamper-evident QC-decision hash chain;
transport security configuration with TLS options, credential schemes, and secret redaction; and
reproducible synthetic field-network simulation for documentation, tests, and demonstrations without hardware.
acquisition visualisation with a compact field dashboard for station health, edge-QC acceptance, power/synchronisation status, and telemetry packet timing, plus a dedicated edge-QC summary for channel coverage, spike fraction, decisions, rejection reasons, and a power-budget summary for daily load, harvest, runtime, and energy-management state, and a synchronisation-quality summary for offset, drift, jitter, GPS lock, and quality grades.
Minimal Example#
import numpy as np
from pycsamt.iot import (
DeviceConfig,
EdgeProcessingConfig,
EdgeProcessor,
FieldSession,
MonitoringConfig,
)
device = DeviceConfig(
"node-1",
station="S01",
protocol="mqtt",
sample_rate_hz=256.0,
channels=["ex", "hy"],
)
edge = EdgeProcessor(
EdgeProcessingConfig(
finite_threshold=0.95,
channel_names=["ex", "hy"],
spike_threshold=6.0,
)
)
result = edge.process(np.random.randn(1024, 2))
packet = result.to_packet(device, timestamp=1_700_000_000.0,
survey_id="survey-a")
session = FieldSession(
"survey-a",
devices=[device],
monitoring_config=MonitoringConfig(
method="amt",
required_channels=["ex", "hy"],
min_edge_acceptance_rate=0.85,
),
)
session.add_packet(packet)
status = session.assess()
Packet and Table API#
Most functions return plain Python objects or pandas-like tables wrapped by the pyCSAMT API view layer when requested. Common reporting helpers include:
IoT helpers for pyCSAMT field telemetry and edge workflows.
This subpackage supports IoT-enabled AMT/CSAMT field acquisition:
a stateful acquisition hub (
FieldSession),canonical telemetry schemas (
schemas),stream monitoring, power budgeting, and clock-sync audit (
monitoring,power,sync),real telemetry transports (
protocols),acquisition provenance (
provenance),a field-network simulator (
sim), andtransport security configuration (
security).
- pycsamt.iot.build_edifile(z, *, station, lat=None, lon=None, elevation=None, method=None, acqby='pycsamt.iot', metadata=None)#
Assemble an in-memory
EDIFilefrom an impedance tensor.The container carries the minimum valid SEG-EDI structure — a
>HEADblock (station id and geometry), a>=MTSECTmeasurement section, and the impedance transfer function — so it round-trips throughpycsamt.seg.edi.EDIFile.write().- Parameters:
z (pycsamt.z.z.Z) – Impedance tensor for one station.
station (str) – Station / DATAID label.
lat (float, optional) – Station location written to the EDI head.
lon (float, optional) – Station location written to the EDI head.
elevation (float, optional) – Station location written to the EDI head.
method (str, optional) – Acquisition method recorded as an EDI info note.
acqby (str) –
ACQBYvalue; defaults to a pyCSAMT-IoT provenance tag.metadata (mapping, optional) – Extra head attributes (e.g.
county,prospect).
- Return type:
- pycsamt.iot.deployment_from_edis(sources, *, survey_id, protocol='mqtt', role='sensor_node', capabilities=None, device_suffix='-node')#
Seed a
DeploymentConfigfrom an EDI survey.A lighter-weight counterpart to
field_session_from_edis()that returns just the deployment inventory (one device per station), useful for planning device provisioning from a prior survey’s station list.
- pycsamt.iot.edi_survey_table(sources, *, api=None)#
Return an EDI-survey summary as a pyCSAMT table.
- pycsamt.iot.emtools_qc(source, impedance=None, freq=None, *, method=None, flags=False, api=None, **kwargs)#
Route IoT-acquired impedance through
pycsamt.emtoolsQC.Running the same coherence/skew/SNR quality control that downstream processing uses keeps field-side and post-processing QC consistent, rather than having two independent notions of a “good” station.
- Parameters:
source (FieldSession, site.Sites, or EDI source) – A
FieldSession(with impedance), an already-builtSitescollection, or any EDI source accepted byread_edi_survey()(directory, glob, file,EDIFile, …).impedance (mapping, optional) – Per-station impedance, required when source is a session (see
field_session_to_edifiles()for the accepted forms).freq (array-like, optional) – Shared frequency vector for impedance arrays without their own.
method (str, optional) – Acquisition method recorded on the built EDIs.
flags (bool) – When
Truereturnpycsamt.emtools.qc.qc_flags()(per-station pass/flag verdicts); otherwisepycsamt.emtools.qc.build_qc_table()(the full QC metrics table).api (bool, optional) – Forwarded to the emtools helper’s table wrapper.
**kwargs – Forwarded to the emtools helper (thresholds such as
min_frac_ok,min_snr_med,max_skew_med).
- Return type:
A pyCSAMT QC table (or flags table).
Notes
Requires the optional geospatial stack (
pyproj), since emtools QC operates on EDI-backed sites.
- pycsamt.iot.field_session_from_edis(sources, *, survey_id, protocol='mqtt', role='sensor_node', operator=None, method=None, device_suffix='-node')#
Seed a
FieldSessionfrom an EDI survey.Every station in sources becomes a registered station (with its recorded geometry and channels) and a sensor node, ready to be re-occupied in a follow-up IoT-enabled campaign.
- Parameters:
sources (str, path, EDIFile, or iterable thereof) – The EDI survey to import (see
read_edi_survey()).survey_id (str) – Identifier for the new session.
protocol (str) – Telemetry protocol assigned to each seeded device.
role (str) – Device role for each seeded node.
operator (str, optional) – Operator recorded on the session.
method (str, optional) – Acquisition method recorded on the session.
device_suffix (str) – Suffix appended to a station id to form its device id.
- Return type:
- pycsamt.iot.field_session_to_edifiles(session, impedance, freq=None, *, savepath=None, method=None, write=False, acqby='pycsamt.iot')#
Build per-station EDIs from a session plus impedance estimates.
Station geometry (lat/lon/elevation) is taken from the session’s registered stations, so the resulting EDIs carry the field-recorded location without any manual bookkeeping.
- Parameters:
session (pycsamt.iot.session.FieldSession) – Source of station geometry and the observed method.
impedance (mapping) –
{station_id: value}where value is aZ, a(impedance_array, freq)pair, or an impedance array paired with the shared freq argument.freq (array-like, optional) – Shared frequency vector used for array values that do not carry their own frequencies.
savepath (str, optional) – Directory for written files (used only when
write=True).method (str, optional) – Overrides the session method recorded in each EDI.
write (bool) – When
Truethe EDIs are written and the returned mapping holds file paths; otherwise it holds in-memoryEDIFileobjects.acqby (str) –
ACQBYprovenance tag.
- Returns:
{station_id: EDIFile}or{station_id: path}.- Return type:
- pycsamt.iot.impedance_to_z(impedance, freq, *, impedance_err=None, station=None, method=None, component='offdiag', aggregate='mean')#
Build a
Zfrom edge impedance estimates.This is the natural continuation of the edge diagnostics: the field node already forms per-window impedance estimates (see
assess_impedance_stability()); this turns them into the impedance-tensor container the processing and inversion flow consumes.- Parameters:
impedance (array-like of complex) –
Accepted shapes, with
n = len(freq):(n,)— one scalar impedance per frequency, placed according to component (an off-diagonal antisymmetric tensor by default);(n, 2, 2)— a full impedance tensor per frequency;(n_windows, n)— per-window scalar estimates, aggregated across windows (error taken from the window spread);(n_windows, n, 2, 2)— per-window tensors, aggregated;(2, 2)— a single tensor (only whenn == 1).
freq (array-like of float) – Strictly positive frequency vector in Hz.
impedance_err (array-like, optional) – Explicit absolute errors matching the aggregated impedance shape. Overrides any error derived from window spread.
method (str, optional) – Acquisition method (
"amt","mt","csamt") recorded in the object metadata.component ({'offdiag', 'xy', 'yx'}) – Placement for scalar inputs.
aggregate ({'mean', 'median'}) – Reduction across a window axis when one is present.
- Return type:
- pycsamt.iot.read_edi_survey(sources)#
Read an EDI survey into per-station summary records.
- pycsamt.iot.z_to_edi(z, *, station, savepath=None, filename=None, lat=None, lon=None, elevation=None, method=None, datatype=None, acqby='pycsamt.iot', metadata=None)#
Write an impedance tensor to a
.edifile and return its path.See
build_edifile()for the assembled structure.datatypeis forwarded topycsamt.seg.edi.EDIFile.write()and is auto-detected (MT) when left asNone.
- pycsamt.iot.z_to_site(z, *, station, lat=None, lon=None, elevation=None, method=None)#
Return an EDI-backed
Sitefor z.Unlike
FieldSession.to_sites(), which returns acquisition descriptors, this yields a realSitebuilt on anEDIFileand therefore requires the optional geospatial stack (pyproj).
- class pycsamt.iot.DeploymentConfig(survey_id, devices=<factory>, capabilities=<factory>, notes='', metadata=<factory>)#
Bases:
PyCSAMTObject,MetadataMixinSurvey-level IoT deployment metadata.
- Parameters:
survey_id (str)
devices (list[DeviceConfig])
capabilities (list[IoTCapability])
notes (str)
- devices: list[DeviceConfig]#
- capabilities: list[IoTCapability]#
- validate()#
Validate and normalise deployment fields.
- Return type:
None
- add_device(device)#
Append a device and revalidate uniqueness.
- Parameters:
device (DeviceConfig | Mapping[str, Any])
- Return type:
None
- has_capability(capability)#
Return whether the deployment declares capability.
- Parameters:
capability (IoTCapability | str)
- Return type:
- class pycsamt.iot.DeviceConfig(device_id, station=None, protocol='mqtt', sample_rate_hz=None, channels=<factory>, role=DeviceRole.SENSOR_NODE, enabled=True, metadata=<factory>)#
Bases:
PyCSAMTObject,MetadataMixinConfiguration for one field IoT node or recorder gateway.
- Parameters:
- role: DeviceRole | str = 'sensor_node'#
- validate()#
Validate and normalise device configuration.
- Return type:
None
- topic(kind, *, survey_id=None)#
Return a canonical telemetry topic for this device.
- Parameters:
kind (PacketKind | str)
survey_id (str | None)
- Return type:
- classmethod from_mapping(data)#
Build a device config from a mapping.
- Parameters:
- Return type:
- class pycsamt.iot.DeviceRole(*values)#
-
Role of an IoT device in a field deployment.
- SENSOR_NODE = 'sensor_node'#
- RECORDER = 'recorder'#
- GATEWAY = 'gateway'#
- BASE_STATION = 'base_station'#
- REMOTE_REFERENCE = 'remote_reference'#
- TRANSMITTER = 'transmitter'#
- class pycsamt.iot.IoTCapability(*values)#
-
IoT capability flags used in deployment reports.
- TELEMETRY = 'telemetry'#
- EDGE_PROCESSING = 'edge_processing'#
- SYNCHRONIZATION = 'synchronization'#
- ENERGY_MANAGEMENT = 'energy_management'#
- HEALTH_MONITORING = 'health_monitoring'#
- class pycsamt.iot.PacketKind(*values)#
-
Telemetry packet categories used by pyCSAMT IoT workflows.
- DATA = 'data'#
- QC = 'qc'#
- HEALTH = 'health'#
- SYNC = 'sync'#
- POWER = 'power'#
- EVENT = 'event'#
- SOURCE = 'source'#
- class pycsamt.iot.TelemetryPacket(device_id, timestamp, topic, payload, qos=0, kind=PacketKind.DATA, retained=False)#
Bases:
PyCSAMTObjectA timestamped message emitted by an IoT-enabled field node.
- Parameters:
- kind: PacketKind | str = 'data'#
- validate()#
Validate and normalise packet fields.
- Return type:
None
- classmethod from_device(device, *, timestamp, payload, kind=PacketKind.DATA, survey_id=None, qos=0, retained=False)#
Create a packet using a device’s canonical topic.
- Parameters:
device (DeviceConfig)
timestamp (float)
kind (PacketKind | str)
survey_id (str | None)
qos (int)
retained (bool)
- Return type:
- pycsamt.iot.deployment_report(deployment, *, api=None)#
Return one row per device describing declared IoT capabilities.
- Parameters:
deployment (DeploymentConfig)
api (bool | None)
- Return type:
- class pycsamt.iot.StationConfig(station_id, lat=None, lon=None, elevation=None, profile=None, position_m=None, channels=<factory>, dipole_length_m=None, ex_azimuth_deg=None, ey_azimuth_deg=None, device_ids=<factory>, operator=None, notes='', metadata=<factory>)#
Bases:
PyCSAMTObject,MetadataMixinGeospatial and acquisition metadata for one field station.
- Parameters:
station_id (str) – Stable station identifier (e.g.
"S01"). Used as the join key across telemetry, QC, and provenance records.lat (float, optional) – Geographic coordinates in decimal degrees. Latitude is validated to
[-90, 90]; longitude accepts both[-180, 180]and[0, 360]conventions.lon (float, optional) – Geographic coordinates in decimal degrees. Latitude is validated to
[-90, 90]; longitude accepts both[-180, 180]and[0, 360]conventions.elevation (float, optional) – Ground elevation in metres.
profile (str, optional) – Survey line/profile label the station belongs to (e.g.
"L1").position_m (float, optional) – Chainage (distance along the profile) in metres. Enables ordering of stations into a 2D section.
channels (list of str, optional) – Acquisition channels recorded at the station (e.g.
["ex", "ey", "hx", "hy"]).dipole_length_m (float, optional) – Electric dipole length in metres, used for E-field scaling and contact-resistance checks.
ex_azimuth_deg (float, optional) – Orientation of the Ex/Ey electric dipoles, degrees clockwise from geographic north.
ey_azimuth_deg (float, optional) – Orientation of the Ex/Ey electric dipoles, degrees clockwise from geographic north.
device_ids (list of str, optional) – Identifiers of the IoT nodes occupying this station.
operator (str, optional) – Field operator responsible for the occupation.
notes (str) – Free-form field notes.
metadata (dict) – Free-form structured metadata.
- validate()#
Validate and normalise station fields.
- Return type:
None
- property coords: tuple[float, float, float] | None#
Return
(lat, lon, elevation)if a location is set.
- attach_device(device_id)#
Associate an IoT node with this station (idempotent).
- Parameters:
device_id (str)
- Return type:
- classmethod from_mapping(data)#
Build a station config from a mapping, ignoring unknown keys.
- Parameters:
- Return type:
- pycsamt.iot.station_table(stations, *, api=None)#
Return one row per station describing its acquisition metadata.
- Parameters:
stations (StationConfig | Iterable[StationConfig])
api (bool | None)
- Return type:
- class pycsamt.iot.FieldSession(survey_id, *, devices=None, stations=None, monitoring_config=None, sync_config=None, operator=None, method=None, metadata=None)#
Bases:
PyCSAMTObject,MetadataMixinStateful container for one IoT-enabled field acquisition session.
- Parameters:
survey_id (str)
devices (Iterable[DeviceConfig | Mapping[str, Any]] | None)
stations (Iterable[StationConfig | Mapping[str, Any]] | None)
monitoring_config (MonitoringConfig | None)
sync_config (SyncConfig | None)
operator (str | None)
method (str | None)
- validate()#
Validate the session (required by
PyCSAMTObject).- Return type:
None
- add_device(device)#
Register a device, auto-creating its station when named.
- Parameters:
device (DeviceConfig | Mapping[str, Any])
- Return type:
- add_station(station)#
Register (or merge into) a station.
- Parameters:
station (StationConfig | Mapping[str, Any])
- Return type:
- add_packet(packet)#
Append one telemetry packet, registering unknown devices.
- Parameters:
packet (TelemetryPacket | Mapping[str, Any])
- Return type:
- add_packets(packets)#
Append many telemetry packets.
- Parameters:
packets (Iterable[TelemetryPacket | Mapping[str, Any]])
- Return type:
None
- assess(*, now=None)#
Assess telemetry quality with the session monitoring config.
- Parameters:
now (float | None)
- Return type:
- station_table(*, api=None)#
Return the registered stations as a pyCSAMT table.
- to_sites()#
Return acquisition-level station descriptors.
These are
StationConfigobjects enriched from telemetry (channels and method observed in packets). They are acquisition descriptors, not EDI-backedpycsamt.site.base.Siteobjects, which require processed impedance data.Once impedance is available,
to_edifiles()andto_sites_collection()promote these descriptors into EDI-backed sites ready for the processing pipeline.- Return type:
- to_pipeline_input()#
Return a serialisable hand-off for the processing pipeline.
Contains, per station, the location, channels, observed method, accepted frequency band, and QC acceptance — everything the downstream pyCSAMT processing flow needs to know from acquisition.
- to_edifiles(impedance, freq=None, *, method=None, savepath=None, write=False, acqby='pycsamt.iot')#
Promote per-station impedance into
EDIFileobjects.A thin convenience over
pycsamt.iot.bridge.field_session_to_edifiles(): each EDI is enriched with the geometry this session recorded for the station. See that function for the accepted impedance mapping forms and the meaning of write/savepath.
- to_sites_collection(impedance, freq=None, *, method=None)#
Return an EDI-backed
pycsamt.site.base.Sitescollection.This is the processing hand-off: given per-station impedance, it builds one EDI per station and wraps them into a
Sitescollection that can be fed straight topycsamt.pipeline.Pipeline.run(). Requires the optional geospatial stack (pyproj).
- to_manifest()#
Build a reproducible
AcquisitionManifest.- Return type:
- export_manifest(path, *, indent=2)#
Write the acquisition manifest to path and return its path.
- to_json(*, indent=2)#
Return the session as a JSON string.
- classmethod from_json(text)#
Reconstruct a session from a JSON string.
- Parameters:
text (str)
- Return type:
- write_json(path, *, indent=2)#
Write the session JSON to path and return its path.
- class pycsamt.iot.AcquisitionPayload(kind=PacketKind.DATA, station=None, extra=<factory>, method=None, channels=<factory>, sample_rate_hz=None, frequency_hz=None, frequency_band_hz=None, n_samples=None, gain=None, duration_s=None)#
Bases:
TelemetryPayloadMetadata describing one raw acquisition record/window.
- Parameters:
- kind: PacketKind = 'data'#
Canonical packet kind the schema describes. Overridden per subclass.
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- classmethod from_mapping(payload)#
- Parameters:
- Return type:
- class pycsamt.iot.EventPayload(kind=PacketKind.EVENT, station=None, extra=<factory>, event=None, severity=EventSeverity.INFO, message=None, code=None)#
Bases:
TelemetryPayloadDiscrete field event (state change, alarm, operator note).
- Parameters:
kind (PacketKind)
station (str | None)
event (str | None)
severity (EventSeverity | str)
message (str | None)
code (str | None)
- kind: PacketKind = 'event'#
Canonical packet kind the schema describes. Overridden per subclass.
- severity: EventSeverity | str = 'info'#
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- classmethod from_mapping(payload)#
- Parameters:
- Return type:
- class pycsamt.iot.EventSeverity(*values)#
-
Severity levels for event telemetry.
- INFO = 'info'#
- WARNING = 'warning'#
- ERROR = 'error'#
- CRITICAL = 'critical'#
- class pycsamt.iot.HealthPayload(kind=PacketKind.HEALTH, station=None, extra=<factory>, battery_v=None, temperature_c=None, uptime_s=None, free_storage_mb=None, rssi_dbm=None, firmware=None)#
Bases:
TelemetryPayloadDevice-health telemetry (battery, temperature, link quality).
- Parameters:
- kind: PacketKind = 'health'#
Canonical packet kind the schema describes. Overridden per subclass.
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- classmethod from_mapping(payload)#
- Parameters:
- Return type:
- class pycsamt.iot.PowerPayload(kind=PacketKind.POWER, station=None, extra=<factory>, battery_v=None, state=None, runtime_days=None, net_wh_per_day=None, solar_w=None, load_w=None)#
Bases:
TelemetryPayloadEnergy-budget telemetry for a field node.
- Parameters:
- kind: PacketKind = 'power'#
Canonical packet kind the schema describes. Overridden per subclass.
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- classmethod from_mapping(payload)#
- Parameters:
- Return type:
- class pycsamt.iot.QCPayload(kind=PacketKind.QC, station=None, extra=<factory>, accepted=None, decision=None, snr_db=None, finite_coverage=None, spike_fraction=None, rms=None, method=None, channels=<factory>, frequency_band_hz=None, reasons=<factory>)#
Bases:
TelemetryPayloadEdge quality-control telemetry for one acquisition window.
- Parameters:
- kind: PacketKind = 'qc'#
Canonical packet kind the schema describes. Overridden per subclass.
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- class pycsamt.iot.SourcePayload(kind=PacketKind.SOURCE, station=None, extra=<factory>, source_id=None, tx_current_a=None, tx_voltage_v=None, tx_frequency_hz=None, tx_power_w=None, dipole_length_m=None, duty_cycle=None, on=None, offset_m=None, azimuth_deg=None)#
Bases:
TelemetryPayloadTransmitter telemetry for a controlled-source (CSAMT/TDEM) survey.
Reported by a transmitter node so the receiver-side QC and provenance know the state of the source that produced each sounding: the injected current and voltage, the frequency currently being transmitted, the grounded-dipole geometry, and the keyed on/off state.
- Parameters:
- kind: PacketKind = 'source'#
Canonical packet kind the schema describes. Overridden per subclass.
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- classmethod from_mapping(payload)#
- Parameters:
- Return type:
- class pycsamt.iot.SyncPayload(kind=PacketKind.SYNC, station=None, extra=<factory>, offset_ms=None, drift_ppm=None, jitter_ms=None, gps_lock=None, n_reference_points=None, reference=None, tx_locked=None, tx_sync_offset_ms=None, tx_id=None)#
Bases:
TelemetryPayloadClock-synchronisation telemetry for a field node.
- Parameters:
- kind: PacketKind = 'sync'#
Canonical packet kind the schema describes. Overridden per subclass.
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- classmethod from_mapping(payload)#
- Parameters:
- Return type:
- class pycsamt.iot.TelemetryPayload(kind=PacketKind.DATA, station=None, extra=<factory>)#
Bases:
PyCSAMTObjectBase class for canonical telemetry payloads.
- Parameters:
kind (PacketKind)
station (str | None)
- kind: PacketKind = 'data'#
Canonical packet kind the schema describes. Overridden per subclass.
- pycsamt.iot.parse_payload(kind, payload)#
Parse payload into the schema registered for kind.
- Parameters:
kind (PacketKind | str)
- Return type:
- pycsamt.iot.schema_for(kind)#
Return the payload schema registered for kind.
- Parameters:
kind (PacketKind | str)
- Return type:
- pycsamt.iot.validate_payload(kind, payload, *, drop_none=True)#
Return a canonicalised payload dictionary for kind.
Aliased keys are folded into canonical names and values are range checked. Unknown keys are preserved. Raises
ValueErrorwhen a field fails validation.
- class pycsamt.iot.EdgeChannelSummary(channel, n_sample, finite_coverage, rms, mean, std, min, max, spike_fraction, accepted, reasons=<factory>)#
Bases:
PyCSAMTObjectQuality-control summary for one edge data channel.
- Parameters:
- validate()#
Validate and normalise the channel summary.
- Return type:
None
- class pycsamt.iot.EdgeDecision(*values)#
-
Acceptance state assigned by edge-side quality control.
ACCEPTandREJECTare the hard decisions.WARNINGmarks a window that is usable but marginal,REPEATrequests re-occupation, andUNKNOWNis used when QC could not be evaluated.- ACCEPT = 'accept'#
- REJECT = 'reject'#
- WARNING = 'warning'#
- REPEAT = 'repeat'#
- UNKNOWN = 'unknown'#
- class pycsamt.iot.EdgeProcessingConfig(decimation=1, finite_threshold=0.9, sample_axis=0, channel_names=None, compute_rms=True, compute_coverage=True, compute_spikes=True, spike_threshold=6.0, max_spike_fraction=0.05, warn_finite_threshold=None, warn_spike_fraction=None, retain_payload_samples=False, metadata=<factory>)#
Bases:
PyCSAMTObjectConfiguration for lightweight field-side processing.
- Parameters:
- validate()#
Validate and normalise edge-processing options.
- Return type:
None
- class pycsamt.iot.EdgeProcessingResult(data, metrics, accepted, reasons=<factory>, channels=<factory>, decision_override=None)#
Bases:
PyCSAMTObjectSummary returned by
EdgeProcessor.- Parameters:
- channels: list[EdgeChannelSummary]#
- decision_override: EdgeDecision | str | None = None#
- validate()#
Validate and normalise the processing result.
- Return type:
None
- property decision: EdgeDecision#
Return the compact QC decision.
Falls back to a plain accept/reject derived from
acceptedunless an explicitdecision_override(e.g.WARNING) was assigned by the processor.
- payload(*, include_data=None)#
Return a serialisable QC payload for telemetry.
- to_packet(device, *, timestamp, survey_id=None, qos=0, retained=False, include_data=None)#
Encode this edge result as a QC telemetry packet.
- Parameters:
- Return type:
- class pycsamt.iot.EdgeProcessor(config=None)#
Bases:
PyCSAMTObjectSmall edge-processing block for telemetry payload reduction.
- Parameters:
config (EdgeProcessingConfig | None)
- process(data, *, channel_names=None)#
Decimate numeric data and compute simple quality metrics.
- Parameters:
- Return type:
- pycsamt.iot.edge_summary_table(result, *, api=None)#
Return one row per channel from edge-processing results.
- Parameters:
result (EdgeProcessingResult | Iterable[EdgeProcessingResult])
api (bool | None)
- Return type:
- class pycsamt.iot.FrequencyCoverage(sample_rate_hz, nyquist_hz, f_low_hz, f_high_hz, n_decades, coverage_fraction=nan, missing_bands=<factory>)#
Bases:
PyCSAMTObjectResult of
estimate_frequency_coverage().- Parameters:
- class pycsamt.iot.HarmonicPeak(order, frequency_hz, power_ratio, flagged)#
Bases:
PyCSAMTObjectContamination measured at one powerline harmonic.
- class pycsamt.iot.ImpedanceStability(n_windows, cv_magnitude, phase_std_deg, stable)#
Bases:
PyCSAMTObjectResult of
assess_impedance_stability().
- class pycsamt.iot.PowerlineHarmonics(mains_hz, peaks=<factory>, total_ratio=0.0, contaminated=False)#
Bases:
PyCSAMTObjectResult of
detect_powerline_harmonics().- Parameters:
mains_hz (float)
peaks (list[HarmonicPeak])
total_ratio (float)
contaminated (bool)
- peaks: list[HarmonicPeak]#
- property dominant: HarmonicPeak | None#
Return the strongest harmonic, if any were measured.
- class pycsamt.iot.StaticShift(shift_factor, split_decades, consistency_std, phase_diff_deg, static_shift)#
Bases:
PyCSAMTObjectResult of
estimate_static_shift().- Parameters:
- pycsamt.iot.amt_edge_report(data, sample_rate, *, method=None, mains_hz=50.0, signal_band_hz=None)#
Run the core AMT edge diagnostics on one channel and collate them.
When method is given (
"amt","mt","csamt", …), the diagnostics become method-aware: powerline-harmonic detection is only run for powerline-sensitive methods (it is skipped for, e.g., TDEM), and frequency coverage is scored against the method’s target bands. Passing no method preserves the original behaviour.
- pycsamt.iot.amt_edge_table(reports, *, api=None)#
Flatten one or more
amt_edge_report()results into a table.Accepts a
{channel: report}mapping (or(channel, report)pairs) and returns one row per channel with the headline metrics.
- pycsamt.iot.assess_impedance_stability(z_windows, *, max_cv=0.15, max_phase_std_deg=10.0)#
Assess the stability of per-window impedance estimates.
- Parameters:
z_windows (array-like of complex) – Impedance estimates, shape
(n_windows,)or(n_windows, n_freq). Real inputs are treated as magnitudes with zero phase.max_cv (float) – Maximum coefficient of variation of
|Z|for a stable result.max_phase_std_deg (float) – Maximum phase standard deviation (degrees) for a stable result.
- Return type:
- pycsamt.iot.check_channel_saturation(data, *, limit=None, max_clip_fraction=0.01, tol=1e-09)#
Detect ADC clipping / saturation in a channel.
When
limitis provided, samples withabs(x) >= limitcount as saturated. Otherwise, samples equal to the observed min/max (withintol) are treated as clipped, which catches rail-to-rail saturation without a known full-scale value.
- pycsamt.iot.check_contact_resistance(ex, ey=None, *, sample_rate=None, noise_rms_threshold=None)#
Proxy assessment of electrode contact quality.
True contact resistance requires a current injection measurement. On passive AMT electric channels, poor contact manifests as elevated low-frequency noise, large DC offset, and drift. This routine reports those proxies per channel (
exand optionaley) and flags a channel when its high-pass noise RMS exceedsnoise_rms_threshold(when provided) or when Ex/Ey noise is strongly imbalanced.
- pycsamt.iot.compute_live_spectra(data, sample_rate, *, nperseg=None)#
Return
{"frequency_hz": ..., "psd": ...}for live display.
- pycsamt.iot.detect_powerline_harmonics(data, sample_rate, *, mains_hz=50.0, n_harmonics=5, bandwidth_hz=1.0, threshold_ratio=0.05)#
Detect mains-frequency harmonics in a time series.
- Parameters:
data (array-like) – Single-channel time series.
sample_rate (float) – Sampling frequency in Hz.
mains_hz (float) – Powerline fundamental (50 or 60 Hz typically).
n_harmonics (int) – Number of harmonics (including the fundamental) to test.
bandwidth_hz (float) – Half-width of the integration band around each harmonic.
threshold_ratio (float) – Per-harmonic band-power fraction above which a harmonic is flagged as contaminating.
- Return type:
- pycsamt.iot.detect_sensor_dropout(data, *, min_flat_run=8, flat_tol=1e-12)#
Detect NaN gaps and stuck-value (flatline) runs in a channel.
Returns counts for NaN samples and the longest run of (near-)constant consecutive samples, which typically indicates a disconnected or stuck sensor.
- pycsamt.iot.estimate_channel_snr(data, sample_rate=None, *, signal_band_hz=None)#
Estimate channel SNR in decibels.
Two estimators are provided:
If
sample_rateandsignal_band_hzare given, SNR is the ratio of in-band to out-of-band spectral power.Otherwise a time-domain estimate is used: the signal power is the variance of the series and the noise power is derived from the variance of first differences (a white-noise proxy).
- pycsamt.iot.estimate_frequency_coverage(timeseries, sample_rate, *, target_bands=None, snr_floor_db=6.0)#
Estimate the resolvable frequency band of a recording.
The PSD noise floor is taken as its median. Frequencies whose power exceeds the floor by
snr_floor_dbare considered resolved; the lowest and highest such frequencies define the covered band. Whentarget_bandsare supplied, the fraction that falls inside the covered band is reported along with any missing bands.
- pycsamt.iot.estimate_static_shift(res_xy, res_yx, *, phase_xy=None, phase_yx=None, min_split_decades=0.15, max_log_std=0.15, max_phase_diff_deg=10.0)#
Flag a static shift between the two apparent-resistivity modes.
Static shift is a galvanic distortion that multiplies apparent resistivity by a frequency-independent factor while leaving phase unchanged. It therefore shows up as the
xyandyxresistivity curves running parallel on a log scale (a near-constant split) even though their phases coincide – unlike true anisotropy, which splits the phases too.- Parameters:
res_xy (array-like) – Apparent resistivity (\(\Omega\cdot m\)) for the two off-diagonal modes, one value per frequency.
res_yx (array-like) – Apparent resistivity (\(\Omega\cdot m\)) for the two off-diagonal modes, one value per frequency.
phase_xy (array-like, optional) – Corresponding phases in degrees. When given, agreeing phases strengthen a static-shift call (and disagreeing phases veto it).
phase_yx (array-like, optional) – Corresponding phases in degrees. When given, agreeing phases strengthen a static-shift call (and disagreeing phases veto it).
min_split_decades (float) – Minimum
|log10(shift_factor)|for a split to matter.max_log_std (float) – Maximum standard deviation of the per-frequency log split for it to count as frequency-independent.
max_phase_diff_deg (float) – Maximum mean phase difference (when phases are supplied) for the distortion to read as purely galvanic.
- Return type:
- class pycsamt.iot.FieldZone(*values)#
-
Wave-propagation regime of a CSAMT measurement at one frequency.
- NEAR = 'near'#
- TRANSITION = 'transition'#
- FAR = 'far'#
- class pycsamt.iot.FieldZoneCoverage(offset_m, freq_hz=<factory>, skin_depth_m=<factory>, offset_ratio=<factory>, zones=<factory>, n_near=0, n_transition=0, n_far=0)#
Bases:
PyCSAMTObjectResult of
classify_field_zones().- Parameters:
- class pycsamt.iot.SourceLine(frequency_hz, snr_db, power_ratio, detected)#
Bases:
PyCSAMTObjectDetection result for one expected transmitter frequency.
- class pycsamt.iot.SourceStability(current_mean_a, current_cv, current_drift_a, on_fraction, voltage_mean_v=None, stable=False, flags=<factory>)#
Bases:
PyCSAMTObjectResult of
assess_source_stability().- Parameters:
- class pycsamt.iot.TransmitterComb(lines=<factory>)#
Bases:
PyCSAMTObjectResult of
detect_transmitter_frequencies().- Parameters:
lines (list[SourceLine])
- lines: list[SourceLine]#
- pycsamt.iot.assess_source_stability(tx_current, tx_voltage=None, *, max_cv=0.05, on_threshold_frac=0.5)#
Assess transmitter current (and optional voltage) steadiness.
The transmitter current sets the signal level of every sounding, so its steadiness bounds data quality. This reports the coefficient of variation and linear drift of the on-state current, together with the on-fraction of an on/off-keyed source.
- Parameters:
tx_current (array-like) – Transmitter current samples (A).
tx_voltage (array-like, optional) – Transmitter voltage samples (V).
max_cv (float) – Maximum on-state current coefficient of variation for a stable source.
on_threshold_frac (float) – Fraction of the peak current above which the source counts as “on”; on-state statistics use only those samples.
- Return type:
- pycsamt.iot.classify_field_zones(freq, resistivity, offset_m, *, near_ratio=1.0, far_ratio=3.0)#
Classify each frequency as near, transition, or far field.
The controlling quantity is the transmitter-receiver offset expressed in skin depths, \(r / \delta\). A larger ratio means the receiver is electrically farther from the source and the plane-wave assumption is safer.
- Parameters:
freq (array-like of float) – Frequencies in Hz.
resistivity (float or array-like) – (Apparent) resistivity in \(\Omega\cdot m\). A scalar is used as a half-space value for every frequency; an array must match freq.
offset_m (float) – Transmitter-receiver separation \(r\) in metres.
near_ratio (float) –
r / deltaat or below which a frequency is near field.far_ratio (float) –
r / deltaat or above which a frequency is far field. Values in between are the transition zone.
- Return type:
- pycsamt.iot.csamt_edge_report(data, sample_rate, *, tx_frequencies, offset_m, resistivity, tx_current=None, tx_voltage=None, near_ratio=1.0, far_ratio=3.0)#
Run the core CSAMT edge diagnostics on one channel and collate them.
- pycsamt.iot.csamt_edge_table(reports, *, api=None)#
Flatten one or more
csamt_edge_report()results into a table.
- pycsamt.iot.detect_transmitter_frequencies(data, sample_rate, tx_frequencies, *, bandwidth_hz=1.0, snr_threshold_db=6.0)#
Check that recorded energy is present at each transmitted frequency.
For every expected transmitter frequency the in-band power is compared with a local out-of-band noise estimate to form a per-line SNR. A line counts as detected when its SNR meets
snr_threshold_db; lines above Nyquist are reported as undetected.- Parameters:
data (array-like) – Single-channel time series.
sample_rate (float) – Sampling frequency in Hz.
tx_frequencies (iterable of float) – Expected transmitter frequencies in Hz.
bandwidth_hz (float) – Half-width of the signal band around each line.
snr_threshold_db (float) – Minimum in-band/out-of-band SNR for a line to count as detected.
- Return type:
- pycsamt.iot.skin_depth_m(resistivity, freq)#
Electromagnetic skin depth in metres.
\(\delta = 503.29\,\sqrt{\rho / f}\) with \(\rho\) in \(\Omega\cdot m\) and \(f\) in Hz. Scalars and arrays broadcast together; non-positive or non-finite inputs yield
nan.
- class pycsamt.iot.OffsetResponse(frequency_hz, offsets_m=<factory>, amplitudes=<factory>, phases_deg=None, above_noise=<factory>, max_detectable_offset_m=None, monotonic_decay=False, dynamic_range_db=nan)#
Bases:
PyCSAMTObjectResult of
field_vs_offset()– one MVO/PVO curve.- Parameters:
- pycsamt.iot.csem_edge_report(offsets_m, amplitudes, *, phases_deg=None, noise_floor=None, frequency_hz=None)#
Run the CSEM offset-domain diagnostics for one frequency.
- pycsamt.iot.csem_edge_table(reports, *, api=None)#
Flatten one or more
csem_edge_report()results into a table.Accepts a
{label: report}mapping (or(label, report)pairs); the label is typically a frequency or receiver-line identifier.
- pycsamt.iot.field_vs_offset(offsets_m, amplitudes, *, phases_deg=None, noise_floor=None, frequency_hz=None, monotonic_tol=0.05)#
Build a magnitude/phase-versus-offset curve and QC it.
- Parameters:
offsets_m (array-like of float) – Source-receiver offsets in metres (need not be sorted).
amplitudes (array-like of float) – Field amplitude at each offset (e.g. normalised E-field). Must be non-negative and the same length as offsets_m.
phases_deg (array-like of float, optional) – Phase at each offset in degrees, carried through for PVO.
noise_floor (float, optional) – Amplitude below which a reading is not detectable. When given, the largest offset above the floor is reported as the detectability limit and only detectable points drive the decay/dynamic-range QC.
frequency_hz (float, optional) – Frequency this curve was measured at, recorded for reference.
monotonic_tol (float) – Fractional tolerance when checking that amplitude never increases with offset (
a[i+1] <= a[i] * (1 + tol)).
- Return type:
- class pycsamt.iot.MethodProfile(method, frequency_band_hz, required_channels, default_sample_rate_hz=None, controlled_source=False, powerline_sensitive=False, description='')#
Bases:
objectCanonical acquisition characteristics for one EM method.
- Parameters:
- pycsamt.iot.method_profile(method)#
Return the
MethodProfilefor method.
- pycsamt.iot.target_bands_for_method(method)#
Return per-decade sub-bands spanning a method’s frequency band.
These feed coverage checks such as
estimate_frequency_coverage(), which report the fraction of target bands a recording actually resolves. A method without a defined band (e.g. time-domain) returns an empty list.
- class pycsamt.iot.EMMethod(*values)#
-
Electromagnetic survey methods recognised by IoT monitoring.
- AMT = 'amt'#
- MT = 'mt'#
- CSAMT = 'csamt'#
- CSEM = 'csem'#
- TDEM = 'tdem'#
- TEM = 'tem'#
- UNKNOWN = 'unknown'#
- class pycsamt.iot.MonitoringConfig(method=EMMethod.UNKNOWN, expected_interval_s=None, max_latency_s=30.0, max_gap_s=None, min_packet_success_rate=0.95, min_edge_acceptance_rate=0.85, min_battery_v=None, max_clock_offset_ms=None, frequency_band_hz=None, required_channels=<factory>, metadata=<factory>)#
Bases:
PyCSAMTObject,MetadataMixinThresholds used to monitor AMT/MT/CSAMT telemetry streams.
- Parameters:
- classmethod for_method(method, **overrides)#
Build a config seeded with a method’s canonical defaults.
The expected frequency band and required channels are taken from the method’s
MethodProfile, so the method-mismatch, missing-channel, and out-of-band checks inTelemetryMonitorbecome method-aware without any manual threshold tuning. Any keyword overrides win over the defaults.Example
>>> cfg = MonitoringConfig.for_method("csamt", min_battery_v=11.5) >>> cfg.required_channels ['ex', 'ey', 'hx', 'hy']
- Parameters:
- Return type:
- validate()#
Validate and normalise monitoring thresholds.
- Return type:
None
- class pycsamt.iot.MonitoringLevel(*values)#
-
Overall status level for a monitored telemetry stream.
- OK = 'ok'#
- WARNING = 'warning'#
- CRITICAL = 'critical'#
- NO_DATA = 'no_data'#
- class pycsamt.iot.MonitoringStatus(level, n_packet, packet_success_rate, edge_acceptance_rate, mean_latency_s, max_gap_s, battery_min_v, clock_offset_max_ms, methods=<factory>, stations=<factory>, channels=<factory>, frequency_min_hz=nan, frequency_max_hz=nan, issues=<factory>)#
Bases:
PyCSAMTObjectStatus returned by
TelemetryMonitor.- Parameters:
- level: MonitoringLevel | str#
- validate()#
Validate and normalise status fields.
- Return type:
None
- class pycsamt.iot.TelemetryMonitor(config=None)#
Bases:
PyCSAMTObjectMonitor field telemetry from AMT, MT, CSAMT, and related surveys.
- Parameters:
config (MonitoringConfig | None)
- assess(packets, *, now=None)#
Assess a telemetry packet stream against configured thresholds.
- Parameters:
- Return type:
- pycsamt.iot.assess_telemetry(packets, *, config=None, now=None)#
Convenience wrapper around
TelemetryMonitor.- Parameters:
packets (Iterable[TelemetryPacket | Mapping[str, Any]])
config (MonitoringConfig | None)
now (float | None)
- Return type:
- pycsamt.iot.monitoring_status_table(statuses, *, api=None)#
Return monitoring statuses as a pyCSAMT table.
- Parameters:
statuses (MonitoringStatus | Iterable[MonitoringStatus])
api (bool | None)
- Return type:
- pycsamt.iot.packet_table(packets, *, api=None)#
Return telemetry packets as a pyCSAMT table.
- pycsamt.iot.telemetry_summary(packets, *, api=None)#
Summarise telemetry packet counts by device and topic.
- class pycsamt.iot.DevicePowerProfile(name, active_power_w, sleep_power_w=0.05, telemetry_power_w=0.0, edge_power_w=0.0, metadata=<factory>)#
Bases:
PyCSAMTObjectNamed power profile for a field node or recorder.
- Parameters:
- validate()#
Validate and normalise profile fields.
- Return type:
None
- apply(*, battery_wh, duty_cycle=1.0, solar_wh_per_day=0.0, **kwargs)#
Build an
EnergyConfigfrom this profile.- Parameters:
- Return type:
- class pycsamt.iot.EnergyConfig(battery_wh, active_power_w, sleep_power_w=0.05, duty_cycle=1.0, solar_wh_per_day=0.0, reserve_fraction=0.0, regulator_efficiency=1.0, charge_efficiency=1.0, telemetry_power_w=0.0, telemetry_seconds_per_day=0.0, edge_power_w=0.0, edge_duty_cycle=0.0, auxiliary_wh_per_day=0.0, min_runtime_days=None, device_id=None, metadata=<factory>)#
Bases:
PyCSAMTObject,MetadataMixinPower-budget inputs for a field IoT device.
- Parameters:
battery_wh (float)
active_power_w (float)
sleep_power_w (float)
duty_cycle (float)
solar_wh_per_day (float)
reserve_fraction (float)
regulator_efficiency (float)
charge_efficiency (float)
telemetry_power_w (float)
telemetry_seconds_per_day (float)
edge_power_w (float)
edge_duty_cycle (float)
auxiliary_wh_per_day (float)
min_runtime_days (float | None)
device_id (str | None)
- validate()#
Validate and normalise power-budget inputs.
- Return type:
None
- class pycsamt.iot.EnergyEstimate(average_power_w, runtime_hours, runtime_days, net_wh_per_day, load_wh_per_day=0.0, harvest_wh_per_day=0.0, usable_battery_wh=0.0, telemetry_wh_per_day=0.0, edge_wh_per_day=0.0, auxiliary_wh_per_day=0.0, reserve_wh=0.0, energy_margin_wh_per_day=0.0, autonomy_days_no_harvest=0.0, state=PowerState.OK, issues=<factory>)#
Bases:
PyCSAMTObjectEstimated runtime and power draw.
- Parameters:
average_power_w (float)
runtime_hours (float)
runtime_days (float)
net_wh_per_day (float)
load_wh_per_day (float)
harvest_wh_per_day (float)
usable_battery_wh (float)
telemetry_wh_per_day (float)
edge_wh_per_day (float)
auxiliary_wh_per_day (float)
reserve_wh (float)
energy_margin_wh_per_day (float)
autonomy_days_no_harvest (float)
state (PowerState | str)
- state: PowerState | str = 'ok'#
- validate()#
Validate and normalise estimate fields.
- Return type:
None
- class pycsamt.iot.PowerState(*values)#
-
Energy status for an IoT field device.
- SUSTAINING = 'sustaining'#
- OK = 'ok'#
- WARNING = 'warning'#
- CRITICAL = 'critical'#
- pycsamt.iot.estimate_deployment_energy(configs, *, api=None)#
Estimate and tabulate energy budgets for multiple devices.
- Parameters:
configs (Iterable[EnergyConfig])
api (bool | None)
- Return type:
- pycsamt.iot.estimate_energy_budget(config)#
Estimate runtime from battery, duty cycle, and optional solar input.
- Parameters:
config (EnergyConfig)
- Return type:
- pycsamt.iot.power_summary_table(estimates, *, device_ids=None, api=None)#
Return energy estimates as a pyCSAMT table.
- Parameters:
estimates (EnergyEstimate | Iterable[EnergyEstimate])
api (bool | None)
- Return type:
- pycsamt.iot.plot_edge_qc_summary(edge, *, figsize=(12.0, 7.5), title='Edge QC summary', output_path=None, close=False)#
Plot edge quality-control decisions and channel metrics.
- Parameters:
edge (EdgeProcessingResult, TelemetryPacket, FieldSession, mapping, or iterable) – Edge-processing result(s), QC telemetry packet(s), a field session, or serialised mappings. Session inputs are filtered to QC packets.
figsize (tuple) – Matplotlib figure size in inches.
title (str) – Figure title.
output_path (str, optional) – If given, save the figure to this path.
close (bool) – Close the figure before returning it.
- Returns:
The QC summary figure. The normalised rows are attached as
fig.pycsamt_iot_edge_qc.- Return type:
- pycsamt.iot.plot_field_dashboard(session, *, now=None, figsize=(13.0, 8.0), station_axis='auto', title=None, output_path=None, close=False)#
Plot a compact IoT acquisition dashboard.
- Parameters:
session (FieldSession or mapping) – Field session, or a mapping produced by
pycsamt.iot.FieldSession.to_dict().now (float, optional) – Reference timestamp used for live latency/status calculations.
figsize (tuple) – Matplotlib figure size in inches.
station_axis ({"auto", "profile", "map"}) – Station layout.
"profile"uses profile chainage/index;"map"uses longitude/latitude when all stations have coordinates;"auto"chooses map only when coordinates exist.title (str, optional) – Figure title. Defaults to the survey id.
output_path (str, optional) – If given, save the figure to this path.
close (bool) – Close the figure before returning it. Useful for batch report generation after saving.
- Returns:
The dashboard figure. The computed data are also attached as
fig.pycsamt_iot_dashboardfor reproducible report workflows.- Return type:
- pycsamt.iot.plot_power_budget(power, *, figsize=(12.0, 7.5), title='IoT power budget', output_path=None, close=False)#
Plot IoT energy budget, runtime, and power-state summaries.
- Parameters:
power (EnergyConfig, EnergyEstimate, TelemetryPacket, FieldSession, mapping, or iterable) – Power budget input(s).
EnergyConfigobjects are estimated before plotting. Session inputs are filtered toPacketKind.POWERpackets.figsize (tuple) – Matplotlib figure size in inches.
title (str) – Figure title.
output_path (str, optional) – If given, save the figure to this path.
close (bool) – Close the figure before returning it.
- Returns:
The power-budget figure. Normalised rows are attached as
fig.pycsamt_iot_power_budget.- Return type:
- pycsamt.iot.plot_sync_quality(sync, *, figsize=(12.0, 7.5), title='Clock synchronisation quality', tolerance_ms=1.0, max_drift_ppm=None, max_jitter_ms=None, output_path=None, close=False)#
Plot clock offset, drift, jitter, GPS lock, and quality grades.
- Parameters:
sync (SyncStatus, TelemetryPacket, FieldSession, mapping, or iterable) – Synchronisation status input(s). Session inputs are filtered to
PacketKind.SYNCpackets.figsize (tuple) – Matplotlib figure size in inches.
title (str) – Figure title.
tolerance_ms (float, optional) – Optional visual threshold lines.
max_drift_ppm (float, optional) – Optional visual threshold lines.
max_jitter_ms (float, optional) – Optional visual threshold lines.
output_path (str, optional) – If given, save the figure to this path.
close (bool) – Close the figure before returning it.
- Returns:
The synchronisation figure. Normalised rows are attached as
fig.pycsamt_iot_sync_quality.- Return type:
- class pycsamt.iot.BaseTelemetryClient(endpoint=None, *, protocol=None, dry_run=False, **options)#
Bases:
objectCommon behaviour for telemetry transports.
- Parameters:
endpoint (str, optional) – Transport-specific destination (broker URL, file path, URL, serial port, …).
protocol (IoTProtocol) – The protocol this client implements.
dry_run (bool) – When
True, packets are recorded tosentand no real transport is opened.**options – Transport-specific options (credentials, timeouts, TLS, …).
- protocol: IoTProtocol = 'file'#
- connect()#
Open the transport (no-op in dry-run mode).
- Return type:
None
- disconnect()#
Close the transport (no-op in dry-run mode).
- Return type:
None
- receive(*, timeout=None)#
Receive one payload, or
Nonein dry-run/unsupported mode.
- subscribe(topic)#
Register interest in topic (transport permitting).
- Parameters:
topic (str)
- Return type:
None
- class pycsamt.iot.FileTelemetryClient(endpoint=None, *, dry_run=False, append=True, **options)#
Bases:
BaseTelemetryClientAppend/replay telemetry packets to a JSON-lines file.
- protocol: IoTProtocol = 'file'#
- class pycsamt.iot.HTTPTelemetryClient(endpoint=None, *, dry_run=False, timeout=10.0, token=None, headers=None, method='POST', **options)#
Bases:
BaseTelemetryClientPOST telemetry packets to an HTTP(S) endpoint.
- Parameters:
- protocol: IoTProtocol = 'http'#
- class pycsamt.iot.IoTProtocol(*values)#
-
Supported telemetry protocol identifiers.
- MQTT = 'mqtt'#
- HTTP = 'http'#
- LORA = 'lora'#
- SERIAL = 'serial'#
- FILE = 'file'#
- WEBSOCKET = 'websocket'#
- class pycsamt.iot.MQTTTelemetryClient(endpoint=None, *, dry_run=False, host=None, port=1883, username=None, password=None, tls=False, keepalive=60, client_id=None, **options)#
Bases:
BaseTelemetryClientPublish/subscribe telemetry over MQTT.
- Parameters:
- protocol: IoTProtocol = 'mqtt'#
- class pycsamt.iot.SerialTelemetryClient(endpoint=None, *, dry_run=False, baudrate=115200, timeout=1.0, **options)#
Bases:
BaseTelemetryClientSend/receive telemetry over a serial port.
- protocol: IoTProtocol = 'serial'#
- class pycsamt.iot.StoreAndForwardClient(client, *, spool_path=None, max_queue=None, base_backoff_s=1.0, max_backoff_s=300.0)#
Bases:
objectWrap a telemetry client with a persistent offline send buffer.
- Parameters:
client (BaseTelemetryClient) – The underlying transport used for real sends.
spool_path (str, optional) – Path to a JSON-lines spool file. When given, the queue is persisted on every change and reloaded on construction, so a node keeps its backlog across restarts.
max_queue (int, optional) – Maximum number of buffered packets. When the buffer is full the oldest packet is dropped (the newest data is the most valuable for a live survey).
Nonemeans unbounded.base_backoff_s (float) – Base delay for the exponential backoff hint (see
next_retry_delay_s).max_backoff_s (float) – Ceiling for the backoff hint.
- property next_retry_delay_s: float#
Exponential-backoff hint for when to next call
flush().Returns
0.0when the queue is empty. After consecutive flush failures the delay grows asbase * 2**(failures-1), capped atmax_backoff_s– a caller’s scheduling loop can honour this without this class ever sleeping itself.
- send(packet)#
Send packet, or queue it when the transport is unavailable.
A direct send is attempted only when the buffer is empty, so a backlog is never overtaken by fresh packets. On transport failure the packet is queued and a
queuedacknowledgement is returned rather than raising.- Parameters:
packet (Any)
- Return type:
- class pycsamt.iot.TelemetryAck(ok, protocol, packet_id, detail='')#
Bases:
objectAcknowledgement returned by a telemetry client.
- class pycsamt.iot.TelemetryClient(endpoint=None, *, protocol=IoTProtocol.FILE, dry_run=True, **options)#
Bases:
BaseTelemetryClientGeneric recorder used for dry-run simulation and unmapped protocols.
Retains the historical dry-run behaviour: with
dry_run=True(default) packets are recorded tosent. Withdry_run=Falseit has no real transport and raisesNotImplementedErroron send, since a concrete client should be used instead.- Parameters:
endpoint (str | None)
protocol (IoTProtocol)
dry_run (bool)
options (Any)
- exception pycsamt.iot.TelemetryError#
Bases:
RuntimeErrorRaised when a telemetry transport operation fails.
- class pycsamt.iot.WebSocketTelemetryClient(endpoint=None, *, dry_run=False, timeout=10.0, **options)#
Bases:
BaseTelemetryClientSend/receive telemetry over a WebSocket connection.
- protocol: IoTProtocol = 'websocket'#
- pycsamt.iot.build_telemetry_client(protocol, *, endpoint=None, dry_run=True, **options)#
Construct a telemetry client for protocol.
- Parameters:
protocol (str or IoTProtocol) – Transport identifier (
"mqtt","http","file","serial","websocket", or"lora").endpoint (str, optional) – Transport destination (broker URL, file path, HTTP URL, port).
dry_run (bool) – When
True(default), the returned client records packets without opening a real transport — safe for tests and demos.**options – Transport-specific options forwarded to the client.
- Returns:
A concrete client when the protocol has a real transport, else a generic
TelemetryClientrecorder (e.g. forlora).- Return type:
- class pycsamt.iot.ClockSynchronizer(config=None)#
Bases:
objectEvaluate device clock status against a reference.
- Parameters:
config (SyncConfig | None)
- class pycsamt.iot.SyncConfig(tolerance_ms=1.0, reference='gps', max_drift_ppm=None, max_jitter_ms=None, min_reference_points=2)#
Bases:
PyCSAMTObjectClock-synchronisation tolerances.
- Parameters:
- validate()#
Validate and normalise sync tolerances.
- Return type:
None
- class pycsamt.iot.SyncQuality(*values)#
-
Overall synchronisation grade for a device.
- EXCELLENT = 'excellent'#
- GOOD = 'good'#
- FAIR = 'fair'#
- POOR = 'poor'#
- UNKNOWN = 'unknown'#
- class pycsamt.iot.SyncStatus(device_id, offset_ms, within_tolerance, reference='gps', drift_ppm=nan, jitter_ms=nan, gps_lock=None, n_reference_points=0, quality=SyncQuality.UNKNOWN)#
Bases:
PyCSAMTObjectClock-synchronisation status for one device.
- Parameters:
- quality: SyncQuality | str = 'unknown'#
- validate()#
Validate and normalise sync-status fields.
- Return type:
None
- pycsamt.iot.assess_sync_quality(*, offset_ms, drift_ppm=nan, jitter_ms=nan, gps_lock=None, tolerance_ms=1.0)#
Grade synchronisation from offset, drift, jitter, and GPS lock.
- pycsamt.iot.batch_assess_sync(references, *, config=None, api=None)#
Assess many devices at once and return a status table.
- Parameters:
references (mapping) –
{device_id: spec}where spec is either a(local, reference)pair or a mapping withlocal/reference(and optionalgps_lock) keys.config (SyncConfig, optional) – Shared tolerances.
api (bool, optional) – Frame-wrapping override passed to
maybe_wrap_frame().
- Return type:
- pycsamt.iot.detect_gps_dropout(gps_lock, timestamps=None, *, min_lock_fraction=0.9)#
Summarise GPS-lock dropouts across a sample sequence.
- Parameters:
gps_lock (iterable) – Per-sample lock flags (bool/0-1). Non-finite entries are treated as unlocked.
timestamps (iterable, optional) – Matching sample timestamps (seconds), used to report the longest dropout duration.
min_lock_fraction (float) – Threshold below which
okisFalse.
- Returns:
Keys:
n_samples,n_locked,lock_fraction,n_dropout_events,longest_dropout_samples,longest_dropout_s, andok.- Return type:
- pycsamt.iot.estimate_clock_drift_ppm(local_timestamps, reference_timestamps)#
Estimate clock drift in parts-per-million.
Fits the local-reference offset (in seconds) against reference time and returns the slope scaled to ppm. Requires at least two distinct reference points; otherwise returns
nan.
- pycsamt.iot.estimate_clock_jitter_ms(local_timestamps, reference_timestamps)#
Estimate timing jitter as the std of drift-corrected offsets (ms).
- pycsamt.iot.estimate_clock_offset_ms(local_timestamps, reference_timestamps)#
Estimate median local-reference clock offset in milliseconds.
- pycsamt.iot.sync_status_table(statuses, *, api=None)#
Return one row per device from sync-status objects.
- Parameters:
statuses (SyncStatus | Iterable[SyncStatus])
api (bool | None)
- Return type:
- class pycsamt.iot.AcquisitionManifest(survey_id, created_utc=<factory>, tool='pycsamt.iot', tool_version=<factory>, method=None, operator=None, records=<factory>, devices=<factory>, files=<factory>, qc_decisions=<factory>, processing_steps=<factory>, environment=<factory>, metadata=<factory>)#
Bases:
PyCSAMTObjectReproducible manifest describing one IoT acquisition session.
- Parameters:
- records: list[ProvenanceRecord]#
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- add_record(record)#
- Parameters:
record (ProvenanceRecord | Mapping[str, Any])
- Return type:
None
- chained_qc_decisions()#
Return the QC decisions as a tamper-evident hash chain.
- sign(key, *, algo='sha256')#
Return an HMAC-signed envelope around the manifest.
The result wraps the manifest payload under
manifestalongside asignatureandsignature_algo, so it can be written out and later checked withverify_manifest()by a holder of key.
- class pycsamt.iot.ProvenanceRecord(station_id, instrument_serial=None, firmware=None, operator=None, lat=None, lon=None, elevation=None, ex_azimuth_deg=None, ey_azimuth_deg=None, occupation_start=None, occupation_end=None, sample_rate_hz=None, gps_quality=None, battery_status=None, accepted_band_hz=None, field_notes='', qc_decisions=<factory>, rejected_windows=<factory>, processing_steps=<factory>, raw_files=<factory>)#
Bases:
PyCSAMTObjectPer-station occupation provenance for one field session.
- Parameters:
station_id (str)
instrument_serial (str | None)
firmware (str | None)
operator (str | None)
lat (float | None)
lon (float | None)
elevation (float | None)
ex_azimuth_deg (float | None)
ey_azimuth_deg (float | None)
occupation_start (float | None)
occupation_end (float | None)
sample_rate_hz (float | None)
gps_quality (str | None)
battery_status (str | None)
field_notes (str)
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- property occupation_seconds: float | None#
Occupation duration in seconds, if both bounds are known.
- add_raw_file(path, *, algo='sha256')#
Hash path and attach the integrity record.
- pycsamt.iot.build_acquisition_manifest(survey_id, *, records=None, devices=None, qc_decisions=None, processing_steps=None, method=None, operator=None, metadata=None)#
Assemble an
AcquisitionManifestfrom session components.- Parameters:
- Return type:
- pycsamt.iot.export_acquisition_manifest(survey_id, path, *, indent=2, **kwargs)#
Build an acquisition manifest and write it to path.
Thin convenience over
build_acquisition_manifest()+AcquisitionManifest.write();kwargsare forwarded to the builder (records,devices,qc_decisions,method, …). Returns the written path.
- pycsamt.iot.export_reproducibility_bundle(manifest, out_dir, *, include_raw=False, zip_bundle=False)#
Write a manifest and per-station audits into out_dir.
- Parameters:
manifest (AcquisitionManifest) – The session manifest to export.
out_dir (str) – Destination directory (created if needed).
include_raw (bool) – When
True, copy referenced raw files intoout_dir/rawif they exist on disk.zip_bundle (bool) – When
True, also produce<out_dir>.zipof the bundle.
- Returns:
Paths written:
manifest,audits, optionalrawandzip.- Return type:
- pycsamt.iot.export_station_audit(record, path, *, indent=2)#
Write a single-station provenance audit to path (JSON).
- pycsamt.iot.hash_bytes(data, *, algo='sha256')#
Return the hex digest of data using algo.
- pycsamt.iot.hash_chain(entries, *, algo='sha256', genesis='')#
Return a tamper-evident hash chain over entries.
Each returned entry is a copy of the input with
seq,prev_hash, andentry_hashadded, whereentry_hashfolds in the previous entry’s hash. Altering, inserting, or reordering any entry breaks the chain from that point on (detected byverify_hash_chain()). This suits a running log such as per-window QC decisions.
- pycsamt.iot.hash_mapping(mapping, *, algo='sha256')#
Return a stable hash of mapping (key-sorted JSON, UTF-8).
- pycsamt.iot.hash_raw_file(path, *, algo='sha256', chunk_size=1048576)#
Hash a raw acquisition file and return its integrity record.
Returns a mapping with
path,bytes,algo,digest, andmodified_utc. RaisesFileNotFoundErrorwhen the file is missing.
- pycsamt.iot.log_qc_decision(station, decision, *, channel=None, reasons=None, operator=None, timestamp=None, window=None)#
Return a normalised QC-decision record for the audit trail.
- pycsamt.iot.sign_mapping(mapping, key, *, algo='sha256')#
Return an HMAC signature over the canonical JSON of mapping.
Unlike
hash_mapping(), which anyone can recompute, an HMAC signature also proves the manifest was produced by a party holding the shared key – useful for asserting a field audit trail has not been tampered with in transit.
- pycsamt.iot.verify_hash_chain(chained, *, algo='sha256', genesis='')#
Return whether a
hash_chain()sequence is intact and in order.
- pycsamt.iot.verify_manifest(signed, key)#
Verify a signed manifest produced by
AcquisitionManifest.sign().Checks the HMAC signature over the wrapped
manifestpayload and, when present, that the payload’s owncontent_hashstill matches.
- pycsamt.iot.verify_signature(mapping, signature, key, *, algo='sha256')#
Return whether signature is a valid HMAC of mapping under key.
Uses a constant-time comparison so verification does not leak timing information.
- class pycsamt.iot.AuthScheme(*values)#
-
Supported authentication schemes.
- NONE = 'none'#
- BEARER = 'bearer'#
- BASIC = 'basic'#
- API_KEY = 'api_key'#
- class pycsamt.iot.Credential(scheme=AuthScheme.NONE, token=None, username=None, password=None, api_key=None, api_key_header='X-API-Key')#
Bases:
PyCSAMTObjectAuthentication credential with secret redaction.
Secrets (
token,password,api_key) are never shown byreproras_dict(); usereveal()explicitly when a real transport needs the raw values.- Parameters:
- scheme: AuthScheme | str = 'none'#
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- class pycsamt.iot.SecurityConfig(tls=<factory>, credential=<factory>, require_tls=False, allowed_protocols=None)#
Bases:
PyCSAMTObjectCombined TLS + credential policy for telemetry transports.
- Parameters:
tls (TLSConfig)
credential (Credential)
require_tls (bool)
- credential: Credential#
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- allows(protocol)#
Return whether protocol is permitted by policy.
- client_options()#
Return options for
build_telemetry_client().Includes TLS flags, credential headers, and (for MQTT) username / password so a transport can authenticate. Raw secrets are included because this feeds the live client, not a log.
- class pycsamt.iot.TLSConfig(enabled=False, ca_cert=None, certfile=None, keyfile=None, verify=True, min_version=None)#
Bases:
PyCSAMTObjectTransport-layer security material for a telemetry client.
- Parameters:
- validate()#
Validate object state.
Subclasses can override this hook. The base implementation intentionally accepts all states.
- Return type:
None
- pycsamt.iot.redact_secret(value)#
Return a redaction placeholder when value is a non-empty secret.
- pycsamt.iot.simulate_amt_channel(n_samples, sample_rate, *, snr_db=20.0, mains_hz=50.0, powerline_amplitude=0.0, dropout_rate=0.0, seed=None)#
Simulate one AMT channel: band-limited signal + noise + artefacts.
- pycsamt.iot.simulate_amt_station(station_id, *, channels=('ex', 'ey', 'hx', 'hy'), sample_rate=128.0, n_samples=1024, mains_hz=50.0, snr_db=20.0, powerline_amplitude=0.2, dropout_rate=0.0, survey_id=None, profile=None, position_m=None, lat=None, lon=None, timestamp=None, seed=None)#
Simulate a full AMT station: config, channel data, and packets.
- Returns:
Keys:
station(StationConfig),device(DeviceConfig),data({channel: ndarray}), andpackets(list ofTelemetryPacket: one health, one QC).- Return type:
- Parameters:
- pycsamt.iot.simulate_battery_decay(n_samples, *, initial_v=13.2, final_v=10.5, noise_v=0.05, seed=None)#
Simulate a monotonic-ish battery discharge curve with noise.
- pycsamt.iot.simulate_gps_drift(n_samples, *, sample_interval_s=1.0, drift_ppm=5.0, jitter_ms=0.2, offset_ms=0.0, dropout_rate=0.0, start_time=1700000000.0, seed=None)#
Simulate paired reference/local clocks with drift, jitter, dropout.
- Returns:
Keys
referenceandlocal(POSIX-second arrays) andgps_lock(bool array). Feedlocal/referencestraight intoestimate_clock_drift_ppm()etc.- Return type:
- Parameters:
- pycsamt.iot.simulate_iot_network(n_stations=10, *, profiles=('L1',), channels=('ex', 'ey', 'hx', 'hy'), sample_rate=128.0, n_samples=512, mains_hz=50.0, snr_db=20.0, dropout_rate=0.05, survey_id='SIM', station_spacing_m=50.0, seed=None, detail=False)#
Simulate a network of AMT stations across one or more profiles.
- Parameters:
n_stations (int) – Total number of stations to generate.
profiles (sequence of str) – Profile/line labels; stations are round-robin assigned to them.
detail (bool) – When
Truereturn a dict withstationsandpackets. WhenFalse(default) return just the flat list of packets, as in the documented example.sample_rate (float)
n_samples (int)
mains_hz (float)
snr_db (float)
dropout_rate (float)
survey_id (str)
station_spacing_m (float)
seed (int | None)
- Return type:
list of TelemetryPacket, or dict when
detail=True.
- pycsamt.iot.simulate_packet_loss(packets, dropout_rate=0.05, *, seed=None)#
Return packets with a fraction randomly dropped.
- Parameters:
packets (Iterable[TelemetryPacket])
dropout_rate (float)
seed (int | None)
- Return type:
- pycsamt.iot.simulate_powerline_noise(n_samples, sample_rate, *, mains_hz=50.0, amplitude=1.0, n_harmonics=3, seed=None)#
Return a powerline-noise time series (fundamental + harmonics).
Modules#
Field-station configuration for IoT-enabled AMT/CSAMT acquisition. |
|
Formal telemetry payload schemas. |
|
Field acquisition session — the hub of the IoT subpackage. |
|
Telemetry transports for IoT-enabled field acquisition. |
|
Telemetry transport base classes and the dry-run recorder. |
|
File-backed telemetry transport (newline-delimited JSON). |
|
HTTP(S) telemetry transport using the standard library. |
|
MQTT telemetry transport (optional dependency: |
|
Serial (UART) telemetry transport (optional dependency: |
|
Store-and-forward buffering for intermittent field links. |
|
WebSocket telemetry transport (optional dependency: |
|
AMT/CSAMT-specific edge quality-control metrics. |
|
CSAMT / controlled-source specific edge quality-control metrics. |
|
CSEM controlled-source electromagnetic edge quality control. |
|
Per-method acquisition profiles for method-aware IoT QC. |
|
Bridge between IoT field telemetry and the pyCSAMT data model. |
|
Clock synchronisation audit for IoT field acquisition. |
|
Visualisation helpers for IoT-enabled field acquisition. |
|
Acquisition provenance and reproducibility for IoT field sessions. |
|
Security configuration for IoT telemetry transports. |
|
Synthetic IoT/AMT field-network simulator. |