ELARA API V4.2026.4.1

ELARA Python API

The LuxWave ELARA API is a Python client library for communicating with the ELARA Server, a high-performance RF channel emulator. Control RF inputs/outputs, configure channel impairments (delay, Doppler, noise), stream scenarios, and retrieve monitoring data over the connection.

Python 3.10+ V4.2026.4.1

Installation

The ELARA API needs 64-bit Python 3.10 or later, on Windows (x64) or Linux (x86_64).

If you have the earlier package

Earlier versions of the API were installed as elara_python. If you have it, remove it first, before you install luxwave. If you do not have it, go on to Install with pip.

terminal pip uninstall elara_python

Scripts written for the earlier package keep running; new scripts import from luxwave, as shown under Import below.

Installed for all users? If the earlier elara_python was installed for all users (from an administrator prompt on Windows, or with sudo on Linux), it has to be removed the same way: run pip uninstall elara_python from an administrator prompt or with sudo (or run the install script once that way, see Install offline), then install normally.

Installed luxwave before you removed elara_python? Removing elara_python then also removes part of luxwave, and import luxwave fails. Install luxwave again over itself:

terminal pip install --force-reinstall luxwave

Offline, run the install script again instead (see Install offline).

Install with pip

With internet access, install the API from a terminal:

terminal pip install luxwave

This installs the API, and nothing else, into the Python that runs your scripts or, when a virtual environment is active, into that environment.

Install offline

Without internet access, take the wheel files and the install script from the Toolkit's download card for the Python API. Put them in one folder and, from that folder, run install_python.bat on Windows, or bash install_python.sh on Linux:

cmd install_python.bat
bash bash install_python.sh

The script installs the API into the Python that runs your scripts or, when a virtual environment is active, into that environment. It takes the wheel that fits that Python and stops with a message, before installing or removing anything, if none does. If that Python has the earlier package elara_python, the script removes it first. It ends by printing the installed API version.

Import

python from luxwave import ( ELARA, RFInput, RFOutput, Channel, ChannelConfig, ChannelConfigInput, ChannelConfigOutput, ChannelStart, ChannelStop, Monitoring, SystemSettings, ScenarioMode, MonitoringType, )

Check your API version

The installed API version is available as the standard package attribute:

python import luxwave print(luxwave.__version__) # e.g. '4.2026.4.1'

Network

The API signs in over HTTPS (port 443) to the LuxWave login service, auth.luxwave.lu, and connects to the ELARA Server through the board's own ports. A proxy or firewall between this computer and either must let them through. If the login service cannot be reached, the constructor raises a ConnectionError that says so.

Ensure the ELARA Server process is reachable on the target host before connecting.

Quickstart

Use Case

A common use case for the ELARA Python API is automated channel emulation testing: a test script connects to the ELARA hardware, configures the RF front-end to match the frequency plan of the device under test (DUT), then uploads a time-varying sequence of impairments (multipath delay, Doppler shift, and additive noise) so the DUT experiences a controlled, repeatable radio environment without any actual over-the-air propagation.

The script below models this workflow end-to-end. It configures one input and two output ports, generates 1 000 impairment steps where delay ramps from 10 ms upward and Doppler increases linearly, uploads those steps to channel 0, fires the scenario, captures monitoring data to verify the hardware is running correctly, then stops everything cleanly. This pattern can be extended for multi-channel, long-duration, or parametric sweep tests simply by adjusting the impairment generation loop.

python from luxwave import ( ELARA, RFInput, RFOutput, Channel, ChannelConfig, ChannelConfigInput, ChannelConfigOutput, ChannelStart, ChannelStop, ScenarioMode, ) # ── Step 1: Connect and authenticate ────────────────────────────────────────── # ELARA() verifies your LuxWave account credentials, connects to the ELARA Server, # and loads the current hardware state (inputs, outputs, channels, system settings) # into the local api object. api = ELARA('10.186.217.157', 'dev', 'dev') # ── Step 2: Configure RF ports ──────────────────────────────────────────────── # Tell the hardware which carrier frequencies to use on each port. # update_inputs() / update_outputs() configure the RF ports on the ELARA Server. # Frequencies outside the hardware range are silently clamped to the nearest supported value. api.update_inputs(RFInput(index=0, frequency_mhz=2400.0, alias="Antenna-A")) api.update_outputs([ RFOutput(index=0, frequency_mhz=710.0, alias="Output-0"), RFOutput(index=1, frequency_mhz=1100.0, alias="Output-1"), ]) # ── Step 3: Build the impairment timeline ───────────────────────────────────── # Each ChannelConfig represents one time step in the scenario. Here we create # 1 000 steps where: # • delay_ms ramps from 10 ms to 1009 ms (1 ms per step) # • doppler_hz grows from 0 Hz to 4995 Hz (5 Hz per step) # • noise_dbm is held constant at −30 dBm # • input 0 is routed through with 10 dB attenuation # • output 0 receives the processed signal # ChannelConfigInput / ChannelConfigOutput reference the live RFInput / RFOutput # objects already stored in api.inputs and api.outputs. configs = [ ChannelConfig( step_number=i, delay_enable=True, delay_ms=10.0 + i, doppler_enable=True, doppler_hz=5.0 * i, noise_enable=True, noise_dbm=-30.0, inputs=[ChannelConfigInput(input=api.inputs[0], attenuation_db=10.0)], outputs=[ChannelConfigOutput(output=api.outputs[0])], ) for i in range(1000) ] # ── Step 4: Configure the channel and upload impairments ────────────────────── # set_channel() sets the human-readable name for the channel. # set_channel_impairments() replaces any existing impairment steps on channel 0 # with the new list. api.set_channel(Channel(index=0, name="Channel-A")) api.set_channel_impairments(0, configs) # ── Step 5: Start the scenario ──────────────────────────────────────────────── # ChannelStart specifies which channel to activate, the step index to begin # from (0 = first step), and the desired playback mode. # PLAY_ONCE plays all uploaded steps once from start_index to the last # step, then resets the hardware to step 0 and stops (transitions to IDLE). api.start_scenario(ChannelStart(channel_index=0, start_index=0, scenario_mode=ScenarioMode.PLAY_ONCE)) # ── Step 6: Read monitoring data ────────────────────────────────────────────── # fetch_monitoring() captures a live I/Q snapshot at the DAC output together # with the delay, Doppler, and noise values and the active step index at the # moment of capture. mon = api.fetch_monitoring(0) print(f"Captured {len(mon.i_samples)} I/Q pairs") print(f"Active step: {mon.config_steps[0]} | Delay: {mon.delays[0]} ms | Doppler: {mon.dopplers[0]} Hz") # ── Step 7: Stop and disconnect ─────────────────────────────────────────────── # stop_scenario() immediately halts scenario playback on the specified channel. # close() releases the connection. api.stop_scenario(ChannelStop(channel_index=0)) api.close()

How the code works, step by step

Step 1: Connect & authenticate

ELARA(host, username, password) is the only constructor. It first validates your LuxWave account credentials. If they are correct, it connects to the ELARA Server. The constructor finishes by loading the current hardware state into api.inputs, api.outputs, api.channels, and api.system_settings from the live hardware state, so your script always starts with an up-to-date view of the device without a separate query.

Step 2: Configure RF ports

The ELARA hardware has physical RF input and output ports identified by zero-based indices. update_inputs() and update_outputs() each accept either a single object or a list, so both a single port and multiple ports can be updated in one call. The server stores the new carrier frequency and alias, and the method also updates the corresponding entry inside api.inputs / api.outputs so subsequent ChannelConfigInput / ChannelConfigOutput references stay consistent. Frequency values are clamped to the hardware limits (api.system_settings.limit_input_frequency_minimum_hz / …maximum_hz) before transmission.

Step 3: Build the impairment timeline

A scenario is a time-ordered list of ChannelConfig objects. Each object describes the channel impairments to apply for one hardware tick: delay, Doppler shift, additive noise, input routing, and output routing. Building this list in Python gives you full numeric control: you can use linspace ramps, pre-recorded traces, or lookup tables from a propagation model. In the example, a simple linear ramp is constructed with a list comprehension. The interval between steps is 10ms. For example, a scenario of 1000 steps will run for 10 seconds before looping back to the start.

ChannelConfigInput links a source RFInput port to the channel and adds an attenuation value (dB). ChannelConfigOutput similarly links a destination RFOutput and can optionally carry geographic coordinates (lat, lon, alt) for geo-tagged emulation scenarios.

Step 4: Upload the impairment list

set_channel() sets the human-readable display name for the channel.

set_channel_impairments() replaces any previously loaded impairment steps for that channel with the new list. For larger scenarios the upload may take a few seconds. An optional progress_callback(sent, total) argument lets you display upload progress in long-running scripts.

Step 5: Start the scenario

start_scenario() accepts a ChannelStart (or a list for synchronised multi-channel starts). start_index selects the first step to apply (useful for resuming mid-scenario). Pass ScenarioMode.PLAY_ONCE to play the scenario once from start_index to the last step; the hardware then resets to step 0 and the channel transitions to IDLE automatically.

Step 6: Read monitoring data

fetch_monitoring() captures a live I/Q snapshot at the DAC output. The returned Monitoring object contains parallel arrays: i_samples / q_samples for the complex baseband signal, delays / dopplers / noise_levels for the active impairment values at the capture instant, and config_steps for the step index that was running on each captured sample.

Step 7: Stop and disconnect

stop_scenario() halts scenario playback immediately. The impairment steps remain on the server and can be restarted without re-uploading. close() closes the connection. Always call close() at the end of your script to release the server-side connection slot.

Multi-client state synchronisation

When multiple clients connect to the same ELARA board simultaneously, a frequency change made by one client is not automatically broadcast to the others. Use fetch_input() or fetch_output() to poll the server for the latest port state at any time:

python # Poll current state after another client may have changed a port frequency: inp = api.fetch_input(0) outp = api.fetch_output(1) print(f"Input 0: {inp.frequency_mhz} MHz") print(f"Output 1: {outp.frequency_mhz} MHz") # api.inputs and api.outputs are now up to date.
One call to fetch_input() or fetch_output() refreshes all ports in the local cache. Subsequent reads of api.inputs and api.outputs reflect the latest server state without an additional network round trip.
Progress reporting during upload: for large scenarios pass a progress_callback to set_channel_impairments(): lambda sent, total: print(f"{sent/total*100:.0f}%"). The callback is invoked periodically as steps are uploaded.

ELARA(host, username, password, force_logout, board_name)

Creates a new ELARA client instance. On construction, the client authenticates the user against the LuxWave account system, connects to the ELARA Server, and synchronises local state (inputs, outputs, channels, system settings) from the server.

class ELARA(host: str, username: str, password: str, force_logout: bool = False, board_name: str | None = None)

Parameters

NameTypeDescription
hostrequired str IP address or hostname of the machine running the ELARA Server (e.g. "10.186.217.157" or "127.0.0.1").
usernamerequired str Your LuxWave account username.
passwordrequired str Password associated with the username.
force_logoutoptional bool When True, any existing active session for this user is forcibly evicted and the new session takes over immediately. Default: False. If an active session exists and force_logout is False, a PermissionError is raised.
board_nameoptional str Names the subscription the board takes on its first connection (see Board lock). The name must be 1 to 40 characters once leading and trailing whitespace is trimmed, with no control characters such as a tab or a line break and no lone surrogates (U+D800 to U+DFFF); otherwise a ValueError is raised before anything connects. Default: None. Without it, the subscription keeps a name your administrator gave it, or gets a suggested name such as Board 2, and then a warning gives its number and name. On later connections of the same board the name is not changed.

Instance Attributes (populated after construction)

AttributeTypeDescription
inputs list[RFInput] RF input ports reported by the server. Each element is an RFInput with fields index, name, alias, frequency_mhz. Refreshed on construction and by fetch_input().
outputs list[RFOutput] RF output ports reported by the server. Each element is an RFOutput with fields index, name, alias, frequency_mhz. Refreshed on construction and by fetch_output().
channels list[Channel] Channels available on the hardware. Each element is a Channel with fields index, name, cable_con_loss_db, scenario_mode. Refreshed on construction and by any fetch_* call.
system_settings SystemSettings Hardware capabilities and parameter limits from the server. See SystemSettings for the full field list (frequency limits, delay limits, calibration values, converter settings, etc.). Populated on construction.
Raises ValueError if credentials are invalid, or, before anything connects, if board_name is not a string of 1 to 40 characters without control characters or lone surrogates. Raises ConnectionError if this computer cannot reach the LuxWave login service, or the service answers that it is temporarily unavailable (see the network the API needs under Installation). Raises PermissionError if there is no active subscription, if another session is already active (force_logout=False), if the board is refused because none of your subscriptions of the package it runs is locked to it and none is free (see Board lock), if the board could not be recorded on a free subscription (try again), or if your account is locked to a different computer (see Node lock). A refused board's message lists your subscriptions of that package by number and name, for example "All 4 subscriptions for ZCU111_ExtClk are in use on other boards: S-135 Lab A, S-136 Lab B, S-137 Rack 2, S-138 Board 4. Ask your administrator to free one."
Node lock. Your LuxWave account is tied to the computer you first use it on. The Toolkit and the API have separate locks, so you can run the Toolkit on one computer and your scripts on another. Signing in from a different computer raises a PermissionError with the message "This account is locked to a different computer." If that happens, ask your administrator to reset the node lock for your account.

The Python API and the MATLAB API share a single lock, so both must be run from the same computer.
Board lock. Each subscription covers one board. The first board you connect with a subscription is recorded against it. With several subscriptions of the same package you can use that many boards, each board keeping its own subscription.

Each subscription has a number, such as S-137, and a name. The name is set when a board first connects with the subscription: the board_name you pass; without it, the name the subscription already has (your administrator gave it one, or it kept its name when its board lock was reset); otherwise a suggested name such as Board 2, and a warning tells you the number and the name. After that first connection, only an administrator can rename a subscription.

When every subscription of that package is locked to another board, the constructor raises a PermissionError whose message lists them by number and name. If that happens, ask your administrator to reset a board lock or to add a subscription.

If a free subscription cannot be recorded for the board, for example because the connection to LuxWave dropped, the board is not let in: the constructor raises a PermissionError with the message "The LuxWave login service is temporarily unavailable. Please try again." Connecting again is safe; if it keeps happening, contact your administrator.
Subscription check. While you are connected, the API checks once a minute that your subscription still covers the board. When the subscription has ended, was removed or no longer covers this board, the connection to the board is closed (what the board is already playing keeps running), and every later call except close() and stop_monitoring() raises a PermissionError with the message "Your subscription for package 'ZCU111_ExtClk' has ended or no longer covers this board. Please contact your administrator." The API also prints this message when it happens, through Python's standard logging module (logger 'luxwave_api', shown on stderr unless your script configures logging), so a script that only receives monitoring updates sees why they stopped.

When the check cannot reach LuxWave for 10 minutes in a row, the same happens, with the message "Your subscription for package 'ZCU111_ExtClk' could not be checked for 10 minutes. Please check your connection to LuxWave; if this keeps happening, contact your administrator."

To continue once the subscription is renewed, call close() and connect again with a new ELARA(...), or connect again with force_logout=True.

Example

python api = ELARA('10.186.217.157', 'dev', 'dev') # Force-close any existing session for this user and take over: # api = ELARA('10.186.217.157', 'dev', 'dev', force_logout=True) print(api.inputs) # [RFInput(index=0, ...), ...] print(api.channels) # [Channel(index=0, ...), ...]

connect()

Opens the connection to the ELARA board. This is called automatically during construction and does not need to be called manually unless reconnecting after a close().

connect() -> None
Ensure the ELARA Server is reachable from the client host before connecting.

close()

Closes the connection to the ELARA Server. Always call this when you are done to release the connection resource.

close() -> None

Example

python api = ELARA('127.0.0.1', 'dev', 'dev') # ... do work ... api.close()

update_inputs(inputs)

Sets the carrier frequency (and optional alias) for one or more RF input ports. Values are clamped to the hardware limits defined in SystemSettings before being applied.

update_inputs(inputs: RFInput | list[RFInput]) -> None

Parameters

NameTypeDescription
inputsrequired RFInput | list[RFInput] A single RFInput or a list of them. Each object must have a valid index matching an existing input port. Only frequency_mhz and (optionally) alias are applied.
Frequency clamping: if frequency_mhz is outside the hardware range [limit_input_frequency_minimum_hz / 1e6, limit_input_frequency_maximum_hz / 1e6] it is silently clamped to the nearest boundary.

Example

python # Update a single input api.update_inputs(RFInput(index=0, frequency_mhz=2450.0, alias="GPS-Band")) # Update multiple inputs at once api.update_inputs([ RFInput(index=0, frequency_mhz=2400.0), RFInput(index=1, frequency_mhz=5800.0), ])

update_outputs(outputs)

Sets the carrier frequency (and optional alias) for one or more RF output ports. Values are clamped to the hardware limits defined in SystemSettings.

update_outputs(outputs: RFOutput | list[RFOutput]) -> None

Parameters

NameTypeDescription
outputsrequired RFOutput | list[RFOutput] A single RFOutput or a list. Each object's index must match an existing output port.

Example

python api.update_outputs([ RFOutput(index=0, frequency_mhz=710.0, alias="LTE-Band28"), RFOutput(index=1, frequency_mhz=1800.0, alias="LTE-Band3"), ])

set_channel(channels)

Sets the display name for one or more channels.

set_channel(channels: Channel | list[Channel]) -> None

Parameters

NameTypeDescription
channelsrequired Channel | list[Channel] One or more Channel objects. index must match an existing hardware channel. name is applied.

Example

python api.set_channel(Channel(index=0, name="Urban-Path"))

set_channel_impairments(channel_index, channel_impairments, progress_callback)

Uploads a list of ChannelConfig steps to a specific channel. The server's existing impairment list for that channel is cleared first, then the new list is uploaded.

All impairment values (delay_ms, doppler_hz, noise_dbm, attenuation_db) are clamped to hardware limits before being applied. The noise has its own rules, because ELARA's noise is always on: a step with noise_enable=False runs at the channel floor (see noise_floor_dbm()). If its noise_dbm is not a finite number, it is sent with the floor as its level. With noise_enable=True, a noise_dbm below limit_noise_minimum_dbm is raised to the minimum and one above limit_noise_maximum_dbm is lowered to the maximum. A level that is not a finite number (such as nan) gets the channel floor: the step is sent with noise_enable=False and the floor as its level. The floor is for switching the noise off, the minimum for a level you set, so a channel's floor may be below the minimum. The API reports these steps in its noise warnings. Reading the channel back with fetch_channel_impairments() returns the steps as sent; monitoring shows the delivered levels. On an older board that does not report noise floors, noise_enable=False switches the noise off, limit_noise_minimum_dbm takes the place of the floor, and noise_enable is sent as given.

set_channel_impairments( channel_index: int, channel_impairments: list[ChannelConfig] | ChannelConfig, progress_callback: Callable[[int, int], None] | None = None ) -> None

Parameters

NameTypeDefaultDescription
channel_indexrequired int Zero-based index of the target channel.
channel_impairmentsrequired list[ChannelConfig] | ChannelConfig The complete ordered list of impairment steps. step_number is automatically assigned by position in the list. On the fast upload, every step uses the first step's inputs and outputs, in any order.
progress_callbackoptional Callable[[int, int], None] None Called periodically as steps are uploaded. Receives (sent: int, total: int). Useful for showing upload progress.

Example

python def on_progress(sent, total): print(f"Uploaded {sent}/{total} steps ({sent/total*100:.1f}%)") configs = [ ChannelConfig( delay_enable=True, delay_ms=5.0 + i * 0.1, doppler_enable=True, doppler_hz=10.0, noise_enable=False, noise_dbm=-60.0, # False: each step runs at the channel floor inputs=[ChannelConfigInput(input=api.inputs[0], attenuation_db=5.0)], outputs=[ChannelConfigOutput(output=api.outputs[0])], ) for i in range(12000) ] api.set_channel_impairments(0, configs, progress_callback=on_progress)
After uploading, the scenario is automatically stopped on that channel (ready to be started fresh with start_scenario()).
For large scenario lists (tens of thousands of steps), the upload is performed reliably in the background. Use progress_callback to track progress in long-running uploads.
Inputs and outputs. On the fast upload, which the API uses whenever the board offers it, the steps of one call keep the inputs and the outputs of the first step. A step may list them in another order, and each value still goes to its own port. A list whose steps change them raises ValueError naming the channel and the step. Nothing is sent, and the steps are left as they were passed. For example, when the third step of channel 3 adds an input, the error reads:

CH3 row 2 (step_number 2) routes Rx_0, Rx_1 -> Tx_2, but the scenario routes Rx_0 -> Tx_2 (row 0, step_number 0); a channel's inputs and outputs stay the same within one scenario; nothing sent

On a board without the fast upload, the steps are sent as given.
Noise warnings. When the noise of some steps is not delivered as asked, set_channel_impairments() logs one line per kind, never one per step, before the upload starts. The lines go through Python's standard logging module, logger 'luxwave_api', and are printed on stderr unless your script configures logging. The four kinds read like this (the first only on a board that reports noise floors):

ELARA channel 3: 100 of 120 steps ask for noise off; ELARA's noise is always on, so they get the channel floor, -90.0 dBm
ELARA channel 3: 12 of 120 steps ask for less noise than the minimum (lowest -95.0 dBm); set to -90.0 dBm
ELARA channel 3: 5 of 120 steps ask for more noise than the maximum (highest -20.0 dBm); set to -30.0 dBm
ELARA channel 3: 3 of 120 steps ask for a noise level that is not a finite number; set to the channel floor, -90.0 dBm

To send them elsewhere, configure logging as usual, for example logging.basicConfig(filename="elara.log"). To silence them: logging.getLogger('luxwave_api').setLevel(logging.ERROR).

noise_floor_dbm(channel_index)

Returns a channel's noise floor in dBm. ELARA's noise is always on: a step with noise_enable=False runs at the channel floor, and so does a noise_dbm that is not a finite number. The floor may be below limit_noise_minimum_dbm: a level you set with noise_enable=True is held between the noise minimum and maximum, whatever the floor. Each channel has its own floor. It comes from the board settings received when the session was opened; nothing is sent to the board.

noise_floor_dbm(channel_index: int) -> float | None

Parameters

NameTypeDescription
channel_indexrequired int Zero-based index of the channel, as in set_channel_impairments().
Returns: float, the channel floor in dBm, or None when the board does not report noise floors. On such an older board, noise_enable=False switches the noise off.

Example

python floor = api.noise_floor_dbm(0) if floor is not None: print(f"Channel 0 noise floor: {floor:.1f} dBm")

fetch_channel_impairments(channel_index, progress_callback)

Reads back the complete impairment configuration currently stored on the server for a given channel. Returns an ordered list of ChannelConfig objects.

fetch_channel_impairments( channel_index: int, progress_callback: Callable[[int, int], None] | None = None ) -> list[ChannelConfig]

Parameters

NameTypeDefaultDescription
channel_indexrequired int Zero-based index of the channel to read from.
progress_callbackoptional Callable[[int, int], None] None Called periodically as steps are retrieved, with (received, total).
Returns: list[ChannelConfig], the ordered list of impairment steps stored on the server for the specified channel.

Example

python stored = api.fetch_channel_impairments(0) print(f"Channel 0 has {len(stored)} impairment steps") print(stored[0].delay_ms) # inspect first step

fetch_monitoring(index)

Requests a live I/Q snapshot at the DAC output from the ELARA hardware. Returns I/Q samples, delays, Dopplers, noise levels, and the associated configuration step indices.

fetch_monitoring( index: int ) -> Monitoring

Parameters

NameTypeDescription
indexrequired int Output port index to monitor (0-based).
Returns: Monitoring, an object containing i_samples, q_samples, delays, dopplers, noise_levels, and config_steps.

Example

python mon = api.fetch_monitoring(0) print(f"Captured {len(mon.i_samples)} I/Q pairs") print(f"Delay at step 0: {mon.delays[0]} ms")

start_monitoring(index, callback, interval_s, monitoring_type)

Starts a continuous monitoring stream instead of one-shot fetch_monitoring() polling. The board pushes a monitoring snapshot at a fixed rate; each snapshot is passed to your optional callback and cached for get_latest_monitoring(). Call stop_monitoring() to end the stream.

start_monitoring( index: int, callback: Callable[[Monitoring], None] = None, interval_s: float = 0.05, monitoring_type: MonitoringType = MonitoringType.DAC ) -> None

Parameters

NameTypeDescription
indexrequired int Output port index to monitor (0-based).
callback callable Called as callback(Monitoring) for each snapshot. Optional.
interval_s float Requested push interval in seconds (default 0.05).
Returns: None. Snapshots arrive via callback and get_latest_monitoring(), which returns the most recent Monitoring (or None until the first arrives).

Example

python api.start_monitoring(0, callback=lambda m: print(m.delays[0])) # ... do work while snapshots stream in ... latest = api.get_latest_monitoring() api.stop_monitoring()

fetch_input(index)

Fetches the latest server state for the specified RF input port and returns the matching RFInput. Also refreshes api.inputs, api.outputs, and api.channels.

fetch_input(index: int) -> RFInput

Parameters

NameTypeDescription
indexrequired int Zero-based index of the input port to fetch.
Returns: RFInput, the current server state for the port, including the latest frequency_mhz. Returns None if no port with that index exists.
After calling fetch_input(), api.inputs, api.outputs, and api.channels all reflect the latest server state.

Example

python # Check current frequency on input 0 after another client may have changed it inp = api.fetch_input(0) print(f"Input 0 frequency: {inp.frequency_mhz} MHz")

fetch_output(index)

Fetches the latest server state for the specified RF output port and returns the matching RFOutput. Also refreshes api.inputs, api.outputs, and api.channels.

fetch_output(index: int) -> RFOutput

Parameters

NameTypeDescription
indexrequired int Zero-based index of the output port to fetch.
Returns: RFOutput, the current server state for the port, including the latest frequency_mhz. Returns None if no port with that index exists.
After calling fetch_output(), api.inputs, api.outputs, and api.channels all reflect the latest server state.

Example

python # Check current frequency on output 1 after another client may have changed it outp = api.fetch_output(1) print(f"Output 1 frequency: {outp.frequency_mhz} MHz")

start_scenario(channel_starts)

Starts scenario playback on one or more channels. The scenario engine begins iterating over the uploaded impairment steps from the specified start_index.

start_scenario(channel_starts: ChannelStart | list[ChannelStart]) -> None

Parameters

NameTypeDescription
channel_startsrequired ChannelStart | list[ChannelStart] One or more ChannelStart objects specifying the channel index, starting step, and desired ScenarioMode.

Example

python # Start two channels simultaneously from step 0 api.start_scenario([ ChannelStart(channel_index=0, start_index=0, scenario_mode=ScenarioMode.PLAY_ONCE), ChannelStart(channel_index=1, start_index=0, scenario_mode=ScenarioMode.PLAY_ONCE), ])

stop_scenario(channel_stops)

Stops scenario playback on one or more channels immediately.

stop_scenario(channel_stops: ChannelStop | list[ChannelStop]) -> None

Parameters

NameTypeDescription
channel_stopsrequired ChannelStop | list[ChannelStop] One or more ChannelStop objects identifying the channels to stop.

Example

python api.stop_scenario(ChannelStop(channel_index=0))

RFInput

Represents an RF input port on the ELARA hardware.

@dataclass class RFInput
FieldTypeDefaultDescription
indexint0Zero-based hardware port index. Used to reference this input in other calls.
namestr""Hardware port name (read-only, populated from server).
aliasstr""User-defined label for the port.
frequency_mhzfloat0.0Carrier frequency in MHz.

RFOutput

Represents an RF output port on the ELARA hardware. Fields are identical to RFInput.

@dataclass class RFOutput
FieldTypeDefaultDescription
indexint0Zero-based hardware port index.
namestr""Hardware port name (from server).
aliasstr""User-defined label.
frequency_mhzfloat0.0Carrier frequency in MHz.

Channel

Represents a logical channel connecting one or more RF inputs to one or more outputs through the emulation pipeline.

@dataclass class Channel
FieldTypeDefaultDescription
indexint0Zero-based channel index.
namestr""Human-readable channel label.
scenario_modeScenarioModeIDLECurrent playback state (read-only, from server).
current_stepint0Index of the step currently being played (read-only).

ChannelConfig

Defines a single scenario step: a snapshot of all impairment parameters to apply to a channel at a given point in time.

@dataclass class ChannelConfig
FieldTypeDefaultDescription
step_numberint0Playback order index. Automatically assigned by position when using set_channel_impairments().
delay_enableboolFalseWhether delay impairment is active for this step.
delay_msfloat0.0Propagation delay in milliseconds. Clamped to hardware limits.
doppler_enableboolFalseWhether Doppler frequency shift is active.
doppler_hzfloat0.0Doppler frequency shift in Hz. Clamped to hardware limits.
noise_enableboolFalseTrue: the noise runs at noise_dbm. False: the noise runs at the channel floor (see noise_floor_dbm()); ELARA's noise is always on. On an older board that does not report noise floors, False switches the noise off.
noise_dbmfloat0.0Noise power level in dBm, used when noise_enable is True. A finite level is held between limit_noise_minimum_dbm and limit_noise_maximum_dbm: a lower one is raised to the minimum, a higher one lowered to the maximum. A level that is not a finite number, such as nan or an infinity, gets the channel floor, which may be below the minimum; noise off gives the channel floor too.
inputslist[ChannelConfigInput]NonePer-input attenuation and position metadata for this step.
outputslist[ChannelConfigOutput]NonePer-output position metadata for this step.

ChannelConfigInput

Per-input parameters for a single ChannelConfig step, including attenuation and spatial position.

@dataclass class ChannelConfigInput
FieldTypeDefaultDescription
inputRFInputNoneReference to the RF input port this entry applies to.
attenuation_dbfloat0.0Signal attenuation applied to this input (dB). Clamped to hardware limits.
latfloat0.0Geographic latitude of the emitter (decimal degrees).
lonfloat0.0Geographic longitude of the emitter (decimal degrees).
altfloat0.0Altitude of the emitter in metres.

ChannelConfigOutput

Per-output spatial metadata associated with a ChannelConfig step.

@dataclass class ChannelConfigOutput
FieldTypeDefaultDescription
outputRFOutputNoneReference to the RF output port this entry applies to.
latfloat0.0Geographic latitude of the receiver (decimal degrees).
lonfloat0.0Geographic longitude of the receiver (decimal degrees).
altfloat0.0Altitude of the receiver in metres.

ChannelStart

Command payload used with start_scenario().

@dataclass class ChannelStart
FieldTypeDefaultDescription
channel_indexint0Zero-based index of the channel to start.
start_indexint0Step index from which playback begins.
scenario_modeScenarioModeIDLEPlayback mode. Use ScenarioMode.PLAY_ONCE, ScenarioMode.PLAY_ONCE_HOLD, or ScenarioMode.PLAY_REVERT_LOOP to start playback.

ChannelStop

Command payload used with stop_scenario().

@dataclass class ChannelStop
FieldTypeDefaultDescription
channel_indexint0Zero-based index of the channel to stop.

Monitoring

Returned by fetch_monitoring(). Contains raw sample arrays captured at the selected tap point.

@dataclass class Monitoring
FieldTypeDescription
i_sampleslist[float]In-phase (I) component samples.
q_sampleslist[float]Quadrature (Q) component samples.
delayslist[float]Delay values applied at the time of capture (ms).
dopplerslist[float]Doppler shift values at the time of capture (Hz).
noise_levelslist[float]The delivered noise level of each channel at the time of capture (dBm). On a board that reports noise floors, a channel whose step has noise_enable=False reads its channel floor, give or take the board's noise level step: under 1 dB near -90 dBm, so the reading can be slightly above or below the floor.
config_stepslist[int]Step indices from the scenario active at each sample instant.
DNAlist[int]Device identifier reported by the hardware.

SystemSettings

Read-only hardware configuration and operational limits populated from the server during construction. All limit fields define the valid range for the corresponding parameter; values outside these ranges are automatically clamped by the API.

@dataclass class SystemSettings

General

FieldTypeDescription
package_namestrFirmware package name.
product_namestrHardware product name.
informationstrFreeform info string.
scenario_sample_time_sfloatTime per scenario step (seconds).

Limits

FieldTypeDescription
limit_delay_minimum_msfloatMinimum allowed delay (ms).
limit_delay_maximum_msfloatMaximum allowed delay (ms).
limit_doppler_minimum_hzfloatMinimum allowed Doppler shift (Hz).
limit_doppler_maximum_hzfloatMaximum allowed Doppler shift (Hz).
limit_noise_minimum_dbmfloatThe lowest noise level you can set with the noise on (dBm): a finite noise_dbm below it is raised to it. A channel floor (noise_floor_dbm()) may be below it.
limit_noise_maximum_dbmfloatMaximum noise power (dBm).
limit_attenuation_minimum_dbfloatMinimum input attenuation (dB).
limit_attenuation_maximum_dbfloatMaximum input attenuation (dB).
limit_input_frequency_minimum_hzfloatMinimum RF input frequency (Hz).
limit_input_frequency_maximum_hzfloatMaximum RF input frequency (Hz).
limit_output_frequency_minimum_hzfloatMinimum RF output frequency (Hz).
limit_output_frequency_maximum_hzfloatMaximum RF output frequency (Hz).

ScenarioMode

Describes the current execution state of a channel's scenario engine. Also used as a command parameter in ChannelStart to specify the desired playback mode.

class ScenarioMode(Enum)
ValueIntegerDescription
ScenarioMode.PLAY_ONCE1Plays all uploaded steps once in sequence from start_index to the last step, then resets the hardware to step 0 and stops (transitions to IDLE).
ScenarioMode.PLAY_ONCE_HOLD2Plays all uploaded steps once from start_index to the last step, then holds at the last step indefinitely (IDSS/IDOSS zeroed). Sending stop_scenario() resets to step 0. Sending start_scenario() again resumes from the new start_index in the requested mode.
ScenarioMode.PLAY_REVERT_LOOP3Ping-pong loop: plays forward from start_index to the last step, then reverses step-by-step back to start_index, and repeats indefinitely. Turnaround points are played once only (not doubled). IDSS/IDOSS are automatically sign-correct for both directions. Calling stop_scenario() immediately resets to step 0 and goes IDLE. If the window contains only one step, that step is held indefinitely.
ScenarioMode.IDLE0Channel is stopped. This is the state after stop_scenario() or after a scenario completes naturally. Do not pass to start_scenario().

Internal reset states (RESETTING, LOADING_STEP0) are managed privately by the server and are never exposed to the API.

Example

python from luxwave import ChannelStart, ScenarioMode # Play all steps once, then stop at step 0 api.start_scenario( ChannelStart(channel_index=0, start_index=0, scenario_mode=ScenarioMode.PLAY_ONCE) )