Source code for dragonradio.radio.config

# Copyright 2018-2020 Drexel University
# Author: Geoffrey Mainland <mainland@drexel.edu>

"""Radio configuration"""
import argparse
import configparser
import io
import logging
import os
from pprint import pformat
import platform
import re

import libconf

import dragonradio
import dragonradio.liquid

logger = logging.getLogger('config')

[docs]def getNodeIdFromHostname(): """Determine node ID from hostname""" m = re.search(r'([0-9]{1,3})$', platform.node()) if not m: logger.warning('Cannot determine node id from hostname') return None return int(m.group(1))
[docs]class ExtendAction(argparse.Action): """Add a list of values to an argument's value""" # pylint: disable=too-few-public-methods def __init__(self, option_strings, *args, **kwargs): super().__init__(option_strings=option_strings, nargs=0, *args, **kwargs) def __call__(self, parser, namespace, values, option_string=None): items = getattr(namespace, self.dest) or [] items.extend(self.const) setattr(namespace, self.dest, items)
[docs]class LogLevelAction(argparse.Action): """Set log level along with verbose and debug flags""" # pylint: disable=too-few-public-methods def __init__(self, option_strings, *args, **kwargs): super().__init__(option_strings=option_strings, nargs=0, *args, **kwargs) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.const) if self.const <= logging.INFO: namespace.verbose = True else: namespace.verbose = False if self.const <= logging.DEBUG: namespace.debug = True else: namespace.debug = False
[docs]class LoadConfigAction(argparse.Action): """Load configuration parameters from a file in libconf format.""" # pylint: disable=too-few-public-methods def __init__(self, option_strings, *args, **kwargs): super().__init__(option_strings=option_strings, *args, **kwargs) def __call__(self, parser, namespace, values, option_string=None): namespace.loadConfig(values)
[docs]def parser(): """Return the default configuration parser.""" return Config().parser()
[docs]class Config: """Radio configuration""" # pylint: disable=too-many-instance-attributes def __init__(self): # pylint: disable=too-many-statements # Set some default values self.loglevel = logging.WARNING self.verbose_packet_trace = False self.team = 0 self.node_id = getNodeIdFromHostname() self.num_nodes = None self.interactive = False # Log parameters self.log_directory = None self.log_sources = [] self.log_interfaces = [] self.log_invalid_headers = False self.log_snapshots = False self.log_protobuf = False self.log_scoring = False self.compress_interface_logs = False # This is the actual path to the log directory self.logdir_ = None # USRP settings self.addr = '' self.rx_antenna = 'RX2' self.tx_antenna = 'TX/RX' self.rx_subdev = None self.tx_subdev = None self.rx_max_samps_factor = 8 self.tx_max_samps_factor = 8 self.clock_source = None """Clock source for the USRP device""" self.time_source = None """Time source for the USRP device""" # Frequency and bandwidth # Default frequency in the Colosseum is 1GHz self.frequency = 1e9 """Radio frequency""" self.bandwidth = 5e6 """Radio bandwidth to use""" self.max_bandwidth = 50e6 """Max bandwidth radio can handle""" self.rx_bandwidth = None """If set, always receive at this bandwidth. Otherwise, calculate receive bandwidth based on RX oversample factor and radio bandwidth.""" self.tx_bandwidth = None """If set, always transmit at this bandwidth. Otherwise, calculate transmit bandwidth based on TX oversample factor and channel bandwidth.""" self.rx_oversample_factor = 1.0 """Oversample factor on RX""" self.tx_oversample_factor = 1.0 """Oversample factor on TX""" self.channel_bandwidth = 1e6 """Default channel bandwidth for FDMA""" # TX/RX gain parameters self.tx_gain = 25 self.rx_gain = 25 self.soft_tx_gain = -8 self.auto_soft_tx_gain = None self.auto_soft_tx_gain_clip_frac = 1.0 # PHY parameters self.phy = 'ofdm' self.num_modulation_threads = 1 self.num_demodulation_threads = 10 self.max_channels = 10 self.tx_upsample = True # Channelizer parameters self.channelizer = 'freqdomain' self.channelizer_enforce_ordering = False # Synthesizer parameters self.synthesizer = 'freqdomain' # General liquid modulation options self.check = 'crc32' self.fec0 = 'rs8' self.fec1 = 'none' self.ms = 'qpsk' # Header MCS self.header_check = 'crc32' self.header_fec0 = 'none' self.header_fec1 = 'v29p78' self.header_ms = 'bpsk' # Soft decoding options self.soft_header = True self.soft_payload = True # OFDM parameters self.M = 48 self.cp_len = 6 self.taper_len = 4 self.subcarriers = None # MAC parameters self.mac = 'tdma-fdma' """Mac""" self.slot_size = .035 """Total slot duration, including guard interval (seconds)""" self.guard_size = .01 """Size of slot guard interval (seconds)""" self.demod_overlap_size = .005 """Size of demodulation overlap if using the overlapping demodulator (seconds)""" self.slot_send_lead_time = 5e-3 """Lead time needed for slot transmission (seconds)""" self.aloha_prob = .1 """Probability of transmission in a given slot for ALOHA""" self.superslots = False """True if slots should be combined into superslots""" self.mac_accurate_tx_timestamps = False """True if MAC should provide more accurate TX timestamps at a potential performance cost""" self.mac_timed_tx_delay = 500e-6 """Delay for timed TX""" self.neighbor_discovery_period = 12 """Neighbor discovery period at radio startup (sec)""" # ARQ options self.arq = False """Should ARQ be enabled?""" self.arq_window = 1024 """ARQ window size""" self.arq_enforce_ordering = False """Should ARQ enforce packet ordering?""" self.arq_max_retransmissions = None """Maximum number of times a packet is allowed to be retransmitted""" self.arq_ack_delay = 100e-3 """Maximum delay before an explicit ACK is sent (sec)""" self.arq_ack_delay_estimation_window = 1 """Time window over which to estimate ACK delay (sec)""" self.arq_retransmission_delay = 500e-3 """Default duration of retransmission timer (sec)""" self.arq_min_retransmission_delay = 200e-3 """Minimum duration of retransmission timer (sec)""" self.arq_retransmission_delay_slop = 1.1 """Safety factor for retransmission timer estimator""" self.arq_sack_delay = 50e-3 """Maximum time to wait for a regular packet to have a SACK attached (sec)""" self.arq_max_sacks = None """Maximum number of SACKs in a packet""" self.arq_explicit_nak_win = 10 """Maximum number of NAKs to send during NAK window""" self.arq_explicit_nak_win_duration = 0.1 """Duration of NAK window (sec)""" self.arq_selective_ack = True """Send selective ACKs?""" self.arq_selective_ack_feedback_delay = 0.300 """Maximum time to wait before counting a selective NAK as a TX failure""" self.arq_mcu = 100 """Maximum number of extra bytes beyond MTU to be used for control information""" self.arq_move_along = True """Move the send window along even when it's full""" self.arq_decrease_retrans_mcsidx = False """Decrease MCS index for retransmitted packets with a deadline""" self.arq_broadcast_gain_db = 0.0 """Gain to be applied to broadcast packets (dB)""" self.arq_ack_gain_db = 0.0 """Gain to be applied to ACK packets (dB)""" # AMC options self.amc = False self.amc_table = None self.amc_short_per_window = 100e-3 self.amc_long_per_window = 400e-3 self.amc_short_stats_window = 100e-3 self.amc_long_stats_window = 400e-3 self.amc_mcs_fast_adjustment_period = 1.0 self.amc_mcsidx_broadcast = None self.amc_mcsidx_ack = None self.amc_mcsidx_min = None self.amc_mcsidx_max = None self.amc_mcsidx_init = 0 self.amc_mcsidx_up_per_threshold = 0.04 self.amc_mcsidx_down_per_threshold = 0.10 self.amc_mcsidx_alpha = 0.5 self.amc_mcsidx_prob_floor = 0.1 # Snapshot options self.snapshot_frequency = None """How often to take a snapshot (sec)""" self.snapshot_duration = 0.5 """Duration of each snapshot (sec)""" self.snapshot_finalize_wait = 200e-3 """How long to wait for demodulation to finish after stopping a snapshot (sec)""" # Network options self.mtu = 1500 """"Maximum Transmission Unit""" self.internal_net = '10.10.10.0/24' """IP network for internal radio network""" self.external_net = '192.168.0.0/16' """IP network for external network""" self.tap_iface = 'tap0' """Tap interface to use""" self.tap_ipaddr = '10.10.10.%d' """printf-style string specifying node IP address""" self.tap_macaddr = 'c6:ff:ff:ff:ff:%02x' """printf-style string specifying node MAC address""" self.queue = 'fifo' """Network queue to use""" self.packet_compression = False """Enable packet compression?""" # Queue options self.transmission_delay = 0 """Estimated packet transmission delay (seconds)""" self.mandate_bonus_phase = True """Flag indicating whether or not to have a bonus phase""" # Tail drop queue options self.tail_drop_max_size = 5*1500 "Tail drop queue maximum size (bytes)" # RED queue options self.red_gentle = True "Gentle RED" self.red_min_thresh = 5*1500 "RED minimum queue threshold" self.red_max_thresh = 3*self.red_min_thresh "RED maximum queue threshold" self.red_max_p = 0.1 "RED maximum packet drop probability" self.red_w_q = 0.002 "RED queue weight (for EWMA)" # Neighbor discover options # discovery_hello_interval is how often we send HELLO packets during # discovery, and standard_hello_interval is how often we send HELLO # packets during the rest of the run self.discovery_hello_interval = 1.0 self.standard_hello_interval = 60.0 # Clock synchronization self.clock_sync_period = None """Period at which clock is synchronized""" self.clock_noskew = False """Assume no clock skew relative to master""" # Measurement options self.measurement_period = 1.0 # Scoring options self.max_performance_age = 8.0 """Performance reports may be from a measurement period no older than this many seconds""" self.stats_ignore_window = self.measurement_period + 0.5 """Ignore flow statistics during this (most recent) time window""" # Internal agent options self.status_update_period = 5 # Collaboration server options self.force_gateway = False self.collab_iface = None self.collab_server_ip = None self.collab_server_port = 5556 self.collab_client_port = 5557 self.collab_peer_port = 5558 # Collaboration agent message periods self.location_update_period = 15 self.spectrum_usage_update_period = 5 self.detailed_performance_update_period = 5 # Spectrum usage tuning parameters self.spec_future_period = 10.0 """How far into the future to predict spectrum usage""" self.spec_chan_trim_lo = 0.05 """Trim this fraction of the bandwidth from the low edge of channel when predicting""" self.spec_chan_trim_hi = 0.05 """Trim this fraction of the bandwidth from the high edge of channel when predicting""" # Traffic options self.traffic_iface = 'tr0' """Name of Colosseum traffic interface""" def __str__(self): return pformat(self.__dict__) @property def logdir(self): """Log directory""" if self.logdir_: return self.logdir_ if self.log_directory is None: return None logdir = os.path.join(self.log_directory, 'node-{:03d}'.format(self.node_id)) if not os.path.exists(logdir): os.makedirs(logdir) self.logdir_ = os.path.abspath(logdir) return self.logdir_ @property def log_level(self): """Log level""" return logging.getLevelName(self.loglevel) @log_level.setter def log_level(self, level): self.loglevel = getattr(logging, level)
[docs] def mergeConfig(self, config): """Merge a configuration into this configuration""" for key in config: setattr(self, key, config[key])
[docs] def loadConfig(self, path): """Load configuration parameters from a radio.conf file in libconf format.""" try: with io.open(path) as f: self.mergeConfig(libconf.load(f)) logger.info("Loaded radio config '%s'", path) except: logger.exception("Cannot load radio config '%s'", path) raise
[docs] def loadColosseumIni(self, path): """Load configuration parameters from a colosseum_config.ini file.""" try: with open(path, 'r') as f: logger.debug("Read colosseum.ini '%s':\n%s", path, f.read()) except: logger.exception("Cannot open colosseum_config.ini '%s'", path) raise try: config = configparser.ConfigParser() config.read(path) if 'COLLABORATION' in config: for key in config['COLLABORATION']: setattr(self, key, config['COLLABORATION'][key]) if 'RF' in config: for key in config['RF']: setattr(self, key, float(config['RF'][key])) logger.info("Loaded colosseum_config.ini '%s'", path) except: logger.exception("Cannot load colosseum_config.ini '%s'", path) raise
[docs] def parser(self): """Create an argument parser and populate it with arguments.""" parser = argparse.ArgumentParser(description='Run dragonradio.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) self.addArguments(parser) return parser
[docs] def addArguments(self, parser): """ Populate an ArgumentParser with arguments corresponding to configuration parameters. """ # pylint: disable=too-many-statements # pylint: disable=too-many-locals def enumHelp(cls): return ', '.join(sorted(cls.__members__.keys())) # Node ID parser.add_argument('--team', action='store', type=int, dest='team', metavar='ID', help='set team ID') parser.add_argument('-i', action='store', type=int, dest='node_id', metavar='ID', help='set node ID') parser.add_argument('-n', action='store', type=int, dest='num_nodes', metavar='N', help='set number of nodes in network') parser.add_argument('--interactive', action='store_const', const=True, dest='interactive', help='enter interactive shell after radio is started') # Load configuration file parser.add_argument('--config', action=LoadConfigAction, default=argparse.SUPPRESS, metavar='FILE', help='load configuration options from a file') # Log parameters log = parser.add_argument_group('Logging') log.add_argument('-d', '--debug', action=LogLevelAction, const=logging.DEBUG, default=argparse.SUPPRESS, dest='loglevel', help='print debugging information') log.add_argument('-v', '--verbose', action=LogLevelAction, const=logging.INFO, default=argparse.SUPPRESS, dest='loglevel', help='be verbose') log.add_argument('--verbose-packet-trace', action='store_const', const=True, default=argparse.SUPPRESS, dest='verbose_packet_trace', help='show trace of packets written to network') log.add_argument('-l', action='store', dest='log_directory', metavar='PATH', help='specify directory for log files') log.add_argument('--log-iq', action=ExtendAction, const=['log_slots', 'log_recv_symbols', 'log_sent_iq'], default=argparse.SUPPRESS, dest='log_sources', help='log IQ data') log.add_argument('--log-iface', action='append', dest='log_interfaces', metavar='IFACE', help='log packets received on interface') log.add_argument('--log-invalid-headers', action='store_const', const=True, dest='log_invalid_headers', help='log packets with invalid headers') log.add_argument('--log-snapshots', action='store_const', const=True, dest='log_snapshots', help='log snapshots') log.add_argument('--log-protobuf', action='store_const', const=True, dest='log_protobuf', help='log protobuf') log.add_argument('--compress-iface-logs', action='store_const', const=True, dest='compress_interface_logs', help='compress interface logs') # USRP settings usrp = parser.add_argument_group('USRP') usrp.add_argument('--addr', action='store', dest='addr', help='specify device address') usrp.add_argument('--rx-subdev', action='store', type=str, dest='rx_subdev', metavar='DEVICE', help='specify RX subdevice') usrp.add_argument('--tx-subdev', action='store', type=str, dest='tx_subdev', metavar='DEVICE', help='specify TX subdevice') usrp.add_argument('--rx-antenna', action='store', dest='rx_antenna', metavar='ANTENNA', help='set RX antenna') usrp.add_argument('--tx-antenna', action='store', dest='tx_antenna', metavar='ANTENNA', help='set TX antenna') usrp.add_argument('--rx-max-samps-factor', action='store', type=int, dest='rx_max_samps_factor', metavar='X', help='set multiplicative factor for rx_max_samps') usrp.add_argument('--tx-max-samps-factor', action='store', type=int, dest='tx_max_samps_factor', metavar='X', help='set multiplicative factor for tx_max_samps') usrp.add_argument('--clock-source', action='store', dest='clock_source', metavar='CLOCK', help='set clock source') usrp.add_argument('--time-source', action='store', dest='time_source', metavar='CLOCK', help='set time source') # Frequency and bandwidth freqbw = parser.add_argument_group('Frequency and bandwidth') freqbw.add_argument('-f', '--frequency', action='store', type=float, dest='frequency', metavar='HZ', help='set center frequency (Hz)') freqbw.add_argument('-b', '--bandwidth', action='store', type=float, dest='bandwidth', metavar='HZ', help='set bandwidth (Hz)') freqbw.add_argument('--max-bandwidth', action='store', type=float, dest='max_bandwidth', metavar='HZ', help='set maximum bandwidth (Hz)') freqbw.add_argument('--rx-bandwidth', action='store', type=float, dest='rx_bandwidth', metavar='HZ', help='set receive bandwidth (Hz)') freqbw.add_argument('--tx-bandwidth', action='store', type=float, dest='tx_bandwidth', metavar='HZ', help='set transmit bandwidth (Hz)') freqbw.add_argument('--rx-oversample', action='store', type=float, dest='rx_oversample_factor', metavar='X', help='set RX oversample factor') freqbw.add_argument('--tx-oversample', action='store', type=float, dest='tx_oversample_factor', metavar='X', help='set TX oversample factor') freqbw.add_argument('--channel-bandwidth', action='store', type=float, dest='channel_bandwidth', metavar='HZ', help='set channel bandwidth (Hz)') # Gain-related options gain = parser.add_argument_group('Gain') gain.add_argument('-G', '--tx-gain', action='store', type=float, dest='tx_gain', metavar='DB', help='set UHD TX gain (dB)') gain.add_argument('-R', '--rx-gain', action='store', type=float, dest='rx_gain', metavar='DB', help='set UHD RX gain (dB)') gain.add_argument('-g', '--soft-tx-gain', action='store', type=float, dest='soft_tx_gain', metavar='DB', help='set soft TX gain (dB)') gain.add_argument('--auto-soft-tx-gain', action='store', type=int, dest='auto_soft_tx_gain', metavar='COUNT', help='use COUNT packets to calculate soft TX gain to attain 0dBFS') gain.add_argument('--auto-soft-tx-gain-clip-frac', action='store', type=float, dest='auto_soft_tx_gain_clip_frac', metavar='FRACTION', help='clip fraction for automatic soft TX gain') # PHY parameters phy = parser.add_argument_group('PHY') phy.add_argument('--phy', action='store', choices=['flexframe', 'newflexframe', 'ofdm'], dest='phy', help='set PHY') phy.add_argument('--max-channels', action='store', type=int, dest='max_channels', help='set maximum number of channels') phy.add_argument('--tx-upsample', action='store_const', const=True, dest='tx_upsample', help='use software upsampler on TX') phy.add_argument('--no-tx-upsample', action='store_const', const=False, dest='tx_upsample', help='use USRP\'s hardware upsampler on TX') # Channelizer parameters phy.add_argument('--channelizer', action='store', choices=['freqdomain', 'timedomain', 'overlap'], dest='channelizer', help='set channelization algorithm') phy.add_argument('--channelizer-enforce-ordering', action='store_const', const=True, dest='channelizer_enforce_ordering', help='enforce packet order when demodulating in channelizer') # Synthesizer parameters phy.add_argument('--synthesizer', action='store', choices=['multichannel', 'freqdomain', 'timedomain'], dest='synthesizer', help='set synthesizer algorithm') # General liquid modulation options liquid = parser.add_argument_group('liquid-dsp') liquid.add_argument('-r', '--check', action='store', type=dragonradio.liquid.CRCScheme, dest='check', help='set data validity check: ' + \ enumHelp(dragonradio.liquid.CRCScheme)) liquid.add_argument('-c', '--fec0', action='store', type=dragonradio.liquid.FECScheme, dest='fec0', metavar='FEC', help='set inner FEC: ' + \ enumHelp(dragonradio.liquid.FECScheme)) liquid.add_argument('-k', '--fec1', action='store', type=dragonradio.liquid.FECScheme, dest='fec1', metavar='FEC', help='set outer FEC: ' + \ enumHelp(dragonradio.liquid.FECScheme)) liquid.add_argument('-m', '--mod', action='store', type=dragonradio.liquid.ModulationScheme, dest='ms', metavar='MODULATION', help='set modulation scheme: ' + \ enumHelp(dragonradio.liquid.ModulationScheme)) # Soft decoding options liquid.add_argument('--soft-header', action='store_const', const=True, dest='soft_header', help='use soft decoding for header') liquid.add_argument('--soft-payload', action='store_const', const=True, dest='soft_payload', help='use soft decoding for payload') # OFDM-specific options ofdm = parser.add_argument_group('OFDM') ofdm.add_argument('-M', action='store', type=int, dest='M', metavar='N', help='set number of OFDM subcarriers') ofdm.add_argument('-C', '--cp', action='store', type=int, dest='cp_len', metavar='N', help='set OFDM cyclic prefix length') ofdm.add_argument('-T', '--taper', action='store', type=int, dest='taper_len', metavar='N', help='set OFDM taper length') ofdm.add_argument('--subcarriers', action='store', type=str, dest='subcarriers', help='set OFDM subcarrier allocation (.=null, P=pilot, +=data)') # MAC parameters mac = parser.add_argument_group('MAC') mac.add_argument('--mac', action='store', choices=['aloha', 'tdma', 'tdma-fdma', 'fdma'], dest='mac', help='set MAC') mac.add_argument('--aloha', action='store_const', const='aloha', dest='mac', help='use slotted ALOHA MAC') mac.add_argument('--tdma', action='store_const', const='tdma', dest='mac', help='use pure TDMA MAC') mac.add_argument('--fdma', action='store_const', const='fdma', dest='mac', help='use FDMA MAC') mac.add_argument('--tdma-fdma', action='store_const', const='tdma-fdma', dest='mac', help='use TDMA/FDMA MAC') mac.add_argument('--slot-size', action='store', type=float, dest='slot_size', metavar='SEC', help='set MAC slot size (sec)') mac.add_argument('--guard-size', action='store', type=float, dest='guard_size', metavar='SEC', help='set MAC guard interval (sec)') mac.add_argument('--demod-overlap-size', action='store', type=float, dest='demod_overlap_size', metavar='SEC', help='set demodulation overlap interval (sec)') mac.add_argument('--superslots', action='store_const', const=True, dest='superslots', help='use TDMA superslots') mac.add_argument('--accurate-mac-tx-timestamps', action='store_const', const=True, dest='mac_accurate_tx_timestamps', help='provide more accurate TX timestamps at a potential performance cost') mac.add_argument('--mac-timed-tx-delay', action='store', type=float, dest='mac_timed_tx_delay', metavar='SEC', help='delay for timed TX (sec)') # ARQ options arq = parser.add_argument_group('ARQ') arq.add_argument('--arq', action='store_const', const=True, dest='arq', default=argparse.SUPPRESS, help='enable ARQ') arq.add_argument('--no-arq', action='store_const', const=False, dest='arq', default=argparse.SUPPRESS, help='disable ARQ') arq.add_argument('--arq-window', action='store', type=int, dest='arq_window', metavar='NPACKETS', help='set ARQ window size') arq.add_argument('--arq-enforce-ordering', action='store_const', const=True, dest='arq_enforce_ordering', help='enforce packet order when performing ARQ') arq.add_argument('--max-retransmissions', action='store', type=int, dest='arq_max_retransmissions', metavar='COUNT', help='set maximum number of retransmission attempts') arq.add_argument('--explicit-nak-window', action='store', type=int, dest='arq_explicit_nak_win', metavar='NPACKETS', help='set explicit NAK window size') arq.add_argument('--explicit-nak-window-duration', action='store', type=float, dest='arq_explicit_nak_win_duration', metavar='SEC', help='set explicit NAK window duration (sec)') arq.add_argument('--selective-ack', action='store_const', const=True, dest='arq_selective_ack', help='send selective ACK\'s') arq.add_argument('--no-selective-ack', action='store_const', const=False, dest='arq_selective_ack', help='do not send selective ACK\'s') # AMC options amc = parser.add_argument_group('AMC') amc.add_argument('--amc', action='store_const', const=True, dest='amc', default=argparse.SUPPRESS, help='enable AMC') amc.add_argument('--no-amc', action='store_const', const=False, dest='amc', default=argparse.SUPPRESS, help='disable AMC') amc.add_argument('--short-per-window', action='store', type=float, dest='amc_short_per_window', metavar='SEC', help='time window used to calculate short-term PER') amc.add_argument('--long-per-window', action='store', type=float, dest='amc_long_per_window', metavar='SEC', help='time window used to calculate long-term PER') amc.add_argument('--short-stats-window', action='store', type=float, dest='amc_short_stats_window', metavar='SEC', help=('time window used to calculate short-term statistics, ' 'e.g., EVM and RSSI')) amc.add_argument('--long-stats-window', action='store', type=float, dest='amc_long_stats_window', metavar='SEC', help=('time window used to calculate long-term statistics, ' 'e.g., EVM and RSSI')) amc.add_argument('--mcsidx-up-per-threshold', action='store', type=float, dest='amc_mcsidx_up_per_threshold', metavar='FRACTION', help='set PER threshold for increasing modulation level') amc.add_argument('--mcsidx-down-per-threshold', action='store', type=float, dest='amc_mcsidx_down_per_threshold', metavar='FRACTION', help='set PER threshold for decreasing modulation level') amc.add_argument('--mcsidx-alpha', action='store', type=float, dest='amc_mcsidx_alpha', metavar='ALPHA', help='set decay factor for learning MCS transition probabilities') amc.add_argument('--mcsidx-prob-floor', action='store', type=float, dest='amc_mcsidx_prob_floor', metavar='FRACTION', help='set minimum MCS transition probability') # Snapshot options snapshot = parser.add_argument_group('Snapshots') snapshot.add_argument('--snapshot-frequency', action='store', type=float, dest='snapshot_frequency', metavar='SEC', help='set snapshot frequency (sec)') snapshot.add_argument('--snapshot-duration', action='store', type=float, dest='snapshot_duration', metavar='SEC', help='set snapshot duration (sec)') # Network options net = parser.add_argument_group('Network') net.add_argument('--mtu', action='store', type=int, dest='mtu', metavar='BYTES', help='set Maximum Transmission Unit (bytes)') net.add_argument('--tap', action='store', type=str, dest='tap_iface', metavar='IFACE', help='name of tap interface') net.add_argument('--queue', action='store', choices=['fifo', 'lifo', 'mandate', 'red'], dest='queue', help='set network queuing algorithm') net.add_argument('--fifo', action='store_const', const='fifo', dest='queue', help='use FIFO network queue algorithm') net.add_argument('--lifo', action='store_const', const='lifo', dest='queue', help='use LIFO network queue algorithm') net.add_argument('--taildrop', action='store_const', const='taildrop', dest='queue', help='use tail drop network queue algorithm') net.add_argument('--red', action='store_const', const='red', dest='queue', help='use RED network queue algorithm') net.add_argument('--packet-compression', action='store_const', const=True, dest='packet_compression', help='enable network packet compress') # Collaboration server options collab = parser.add_argument_group('Collaboration') collab.add_argument('--force-gateway', action='store_const', const=True, dest='force_gateway', help='force this node to act as a gateway') collab.add_argument('--collab-server-ip', action='store', type=str, dest='collab_server_ip', metavar='IP', help='set collaboration server IP address') # Set defaults defaults = {} for act in parser._actions: # pylint: disable=protected-access if not isinstance(act, LogLevelAction) and not isinstance(act, ExtendAction): dest = act.dest if hasattr(self, dest) and parser.get_default(dest) != argparse.SUPPRESS: defaults[dest] = getattr(self, dest) parser.set_defaults(**defaults)