Using the Python Interface

While the web interface, the XRA-31 Command-line Interface and the XRA-31 Python Interface provide the same options for interaction with the XRA-31, the latter introduces the power of Python to allow for more complex scenarios.

After the basics have been introduced, the tutorial will move on to a typical debugging scenario (Full example).

Storing and loading configurations

The same JSON files that can be stored and loaded using the command-line interface (Storing and loading configurations), can also be created or loaded from the Python interface.

Channel configuration

Assuming the XRA-31 can be accessed at http://xra31_hostname, the following script will store the active configuration to a.json, and load b.json for a new test:

import json
from excentis import xra31

# Connect to the XRA-31
with xra31.connect(address="xra31_hostname") as client:

    # Store the active configuration
    description = client.configuration.describe()
    json.dump(description, open("a.json", "w"), indent=4)

    # Load the desired configuration
    description = json.load(open("b.json", "r"))
    client.configuration.apply(description)

Capture configuration

The same can be done for capture configurations:

import json
from excentis import xra31

# Connect to the XRA-31
with xra31.connect(address="xra31_hostname") as client:

    # Store the active capture settings
    description = client.capture.describe()
    json.dump(description, open("a.json", "w"), indent=4)

    # Load the desired capture settings
    description = json.load(open("b.json", "r"))
    client.capture.apply(description)

Fine-grained configuration

For fine-grained channel configuration, a minimal example can be found in the Introduction. This also covers configuring, starting and stopping a capture, as well as downloading the result.

Full example

As a full example, this script configures the XRA-31 channels using a previously stored JSON file, starts an unlimited rolling file capture and waits for the moment a given cable modem goes offline.

We’re only interested in the final minute before, and the first minute after the cable modem goes offline, so using 3 files with a per-file duration limit of 1 minute will provide the necessary time window.

While we choose to stop the capture, it can also be kept alive and the procedure can then be repeated to observe multiple occurrences.

This script uses the OS-specific system ping to avoid permission issues.

  1#!/usr/bin/env python3
  2"""Capture the final minute before, and the first after a CM goes offline."""
  3
  4import argparse
  5import datetime
  6import json
  7import logging
  8import pathlib
  9import subprocess
 10import sys
 11import time
 12
 13from excentis import xra31
 14
 15# Arguments
 16parser = argparse.ArgumentParser(
 17    description="Capture while waiting for a modem to go offline")
 18parser.add_argument("address",
 19                    metavar="XRA-31",
 20                    help="the XRA-31 hostname or IP address")
 21parser.add_argument("cm",
 22                    metavar="cable-modem",
 23                    help="the IP address of the cable modem",
 24                    type=str)
 25parser.add_argument("--load",
 26                    metavar="configuration.json",
 27                    help="load a JSON configuration to the XRA-31",
 28                    type=argparse.FileType('r'))
 29
 30if __name__ == "__main__":
 31    args = parser.parse_args()
 32
 33    # Logging
 34    logger = logging.getLogger(pathlib.Path(__file__).name)
 35    logging.basicConfig(format="[%(name)s][%(levelname)s]: %(message)s")
 36    logger.setLevel(logging.INFO)
 37
 38    # Create a day-specific file for the output
 39    path = "cm-offline/cm-offline-{}".format(
 40        datetime.datetime.now().strftime("%Y-%m-%d"))
 41
 42    # Connect to the XRA-31, force full access mode
 43    with xra31.connect(address=args.address, full_access=True,
 44                       force=True) as client:
 45
 46        # Stop capturing if needed
 47        logger.info("Stop capturing")
 48        client.capture.stop()
 49
 50        # Optionally change its channels
 51        if args.load:
 52            logger.info("Load configuration")
 53            description = json.load(args.load)
 54            client.configuration.apply(description)
 55
 56        # Select all channels
 57        logger.info("Update channel selection")
 58        client.capture.channels = client.configuration.channels
 59
 60        # Select all packet types, OFDM streams and OFDM profiles
 61        logger.info("Update filtering")
 62        client.capture.filtering.packet_types = xra31.capture.PacketType
 63        client.capture.filtering.ofdm_streams = xra31.capture.OfdmStream
 64        client.capture.filtering.ofdm_profiles = xra31.capture.OfdmProfile
 65        # Remove NCP
 66        logger.info("Ignore NCP")
 67        client.capture.filtering.remove_ofdm_stream(
 68            xra31.capture.OfdmStream.NCP)
 69
 70        # Update the capture output settings
 71        logger.info("Update capture output settings")
 72        client.capture.output.path = path
 73        client.capture.output.size = None  # unlimited
 74        client.capture.output.duration = None  # unlimited
 75        client.capture.output.number_of_files = 3
 76        client.capture.output.file_size = None  # unlimited
 77        client.capture.output.file_duration = 60
 78
 79        # Start the capture
 80        logger.info("Start capturing")
 81        client.capture.start()
 82
 83        # Check if the CM is online by pinging with a 2s timeout
 84        ping_command = ["ping", "/n", "1", "/w", "2000", args.cm
 85                        ] if sys.platform == "win32" else [
 86                            "ping", "-c", "1", "-W", "2", args.cm
 87                        ]
 88        # Ping about every 10s
 89        # until non-zero exit code causes CalledProcessError (check=True)
 90        try:
 91            while True:
 92                logger.info("ping")
 93                subprocess.run(ping_command,
 94                               stdout=subprocess.DEVNULL,
 95                               stderr=subprocess.DEVNULL,
 96                               check=True)
 97                time.sleep(10)
 98        except subprocess.CalledProcessError:
 99            pass
100
101        logger.info("No ping response, continue capturing for 1 minute")
102        time.sleep(60)
103        logger.info("Stop capturing")
104        client.capture.stop()
105        logger.info("Download the captured files")
106        client.analysis.download(path,
107                                 rolling=True,
108                                 compress=True,
109                                 verbose=True)
110        logger.info("Remove the captured files from the XRA-31")
111        client.analysis.delete(path, rolling=True)