rfgen.scene.source_system¶
Use this API to describe a known set of RF-relevant devices and the directed relationships that are permitted between them before another component owns population, motion, event scheduling, propagation, or waveform generation. It gives a stable, JSON-compatible identity-and-topology record; it does not create devices or signals.
For the user-facing relationship between this environment description, stable device populations, count and event plans, emitters, channels, and scene composition, start with Concepts / Scenes.
Start here when a configuration or an external catalog already identifies the participants in one system and you need to preserve those identities without inventing a protocol or trajectory. The Scene API documents scene composition, while GeometryPose documents the typed static pose used by this record.
Build and validate a source-system record¶
The following constructs controller-to-aircraft links, serializes the frozen
version-1 graph, and validates the serialized value again. trajectory_ref is
an opaque, nonblank reference: this API records it but never resolves it.
from rfgen.core.types import GeometryPose
from rfgen.scene import SourceDevice, SourceLink, SourceSystem, namespace_hash
namespace = "example.source-system"
controller = SourceDevice(
device_id=namespace_hash(namespace, "controller-17"),
role="controller",
pose=GeometryPose(
position_m=(0.0, 0.0, 1.5),
orientation_rad=(0.0, 0.0, 0.0),
),
)
aircraft = SourceDevice(
device_id=namespace_hash(namespace, "aircraft-7"),
role="aircraft",
trajectory_ref="catalog:flight-7",
)
system = SourceSystem(
system_id=namespace_hash(namespace, "training-range-a"),
devices=(controller, aircraft),
links=(
SourceLink(
link_id=namespace_hash(namespace, "control-uplink"),
source_device=controller.device_id,
target_device=aircraft.device_id,
direction="uplink",
),
SourceLink(
link_id=namespace_hash(namespace, "telemetry-downlink"),
source_device=aircraft.device_id,
target_device=controller.device_id,
direction="downlink",
),
),
)
payload = system.model_dump(mode="json")
restored = SourceSystem.model_validate(payload)
assert restored == system
This is a programmatic call shape for an environment with rfgen installed.
model_dump(mode="json") converts tuples and the nested pose to JSON-compatible
lists. model_validate rejects a modified graph that violates the rules below;
it does not contact a device registry or fetch the trajectory reference.
Public API¶
namespace_hash¶
def namespace_hash(namespace: str, identifier: str) -> str: ...
Returns the canonical version-1 ID for identifier within namespace.
Both string components are normalized to Unicode NFC, encoded as UTF-8, joined
as normalized_namespace + "\\0" + normalized_identifier, and hashed with
SHA-256. The result is a lowercase, unprefixed 64-character hexadecimal digest.
For example, composed "caf\u00e9" and decomposed "cafe\u0301" components
produce the same ID.
Argument |
Type |
Meaning |
|---|---|---|
|
|
Logical collision boundary for an identifier. |
|
|
Stable source identifier inside that namespace. |
Raises TypeError if either argument is not a string, and ValueError if an
NFC-normalized component is empty or contains a NUL character. A digest is a
canonical record key, not proof of a real-world identity or origin.
namespace_hash is the provided way to generate IDs. The record models retain
only the digest string, however: they accept a canonical lowercase 64-hex
digest-format identifier and do not retain a namespace or identifier preimage,
recompute the digest, or verify that a supplied value came from
namespace_hash.
Vocabularies¶
DeviceRole = Literal["aircraft", "controller", "ground_station", "relay", "unknown"]
LinkDirection = Literal["uplink", "downlink", "peer"]
DeviceRole is the closed device-role vocabulary. LinkDirection describes
the directed link predicate; it is not a transmitter/receiver configuration or
a radio-protocol assertion.
SourceDevice¶
class SourceDevice(BaseModel):
device_id: str
role: DeviceRole
pose: GeometryPose | None = None
trajectory_ref: str | None = None
An immutable device identity with exactly one geometry reference.
Field |
Required |
Meaning |
|---|---|---|
|
yes |
Canonical lowercase 64-hex digest-format identifier. Use |
|
yes |
One value from |
|
conditional |
Typed static scene-frame pose. Supply this or |
|
conditional |
Opaque nonblank string owned by a trajectory-capable component. Supply this or |
Pydantic rejects unknown fields, raw or malformed IDs, unsupported roles, and zero or two geometry references. It validates the ID format only; it cannot recover or verify a namespace/identifier preimage. Instances are frozen after construction.
SourceLink¶
class SourceLink(BaseModel):
link_id: str
source_device: str
target_device: str
direction: LinkDirection
An immutable directed edge. All three IDs must be canonical lowercase 64-hex
digest-format identifiers. Membership, self-link, duplicate, and
role-direction checks happen when a SourceSystem validates the full graph;
the model does not verify their hash provenance.
Field |
Required |
Meaning |
|---|---|---|
|
yes |
Canonical ID for this edge. |
|
yes |
ID of the edge source. |
|
yes |
ID of the edge target. |
|
yes |
|
SourceSystem¶
class SourceSystem(BaseModel):
schema_version: Literal[1] = 1
system_id: str
devices: tuple[SourceDevice, ...]
links: tuple[SourceLink, ...]
An immutable schema-version-1 graph. model_dump(mode="json") returns a
JSON-compatible dictionary; model_validate accepts and validates the same
shape. schema_version is fixed at 1, and system_id must use the same
canonical lowercase 64-hex digest format as device and link IDs. Its format is
validated, but its namespace/identifier provenance is not.
Each device ID and each link ID must be unique. Every link must join two distinct listed devices. An invalid whole-graph condition raises ValidationError with one of these machine-readable contexts:
Condition |
|
|---|---|
Duplicate device ID |
|
Duplicate link ID, missing endpoint, self-link, or incompatible role/direction |
|
Directed-link rule¶
The role predicate is intentionally narrow and exhaustive for version 1:
Direction |
Accepted source → target roles |
All other role pairs |
|---|---|---|
|
|
rejected |
|
|
rejected |
|
any of the five roles → any different device role |
accepted |
The model validates identifier format and a declared topology only. It does not prove identifier provenance, radio reachability, spectrum authorization, antenna compatibility, protocol semantics, coordinate correctness, trajectory existence, or generated-signal behavior. For validation evidence and the tested operating boundary, see the source-system validation report.