def _cfg_frequencies(sat, lnb, setup):
    """Print summary of frequencies

    Inform the downlink RF frequency, the LNB LO frequency and the L-band
    frequency to be configured in the receiver.

    Args:
        sat   : user's satellite info
        lnb   : user's LNB info

    """
    getcontext().prec = 8

    if (sat['band'].lower() == "ku"):
        if (lnb['universal']):
            assert(isinstance(lnb['lo_freq'], list)), \
                "A Universal LNB must have a list with two LO frequencies"
            assert(len(lnb['lo_freq']) == 2), \
                "A Universal LNB must have two LO frequencies"

            if (sat['dl_freq'] > defs.ku_band_thresh):
                lo_freq = lnb['lo_freq'][1]
            else:
                lo_freq = lnb['lo_freq'][0]
        else:
            lo_freq = lnb['lo_freq']

        if_freq = float(Decimal(sat['dl_freq']) - Decimal(lo_freq))

    elif (sat['band'].lower() == "c"):
        lo_freq = lnb['lo_freq']
        if_freq = float(Decimal(lo_freq) - Decimal(sat['dl_freq']))
    else:
        raise ValueError("Unknown satellite band")

    if (if_freq < setup['tun_range'][0] or if_freq > setup['tun_range'][1]):
        logging.error("Your LNB yields an L-band frequency that is out of "
                      "the tuning range of the {} receiver.".format(
                          _get_rx_marketing_name(setup)))
        sys.exit(1)

    return {'dl': sat['dl_freq'], 'lo': lo_freq, 'l_band': if_freq}


def _cfg_chan_conf(info, chan_file):
    """Generate the channels.conf file"""

    util.print_header("Channel Configuration")

    print(
        textwrap.fill("This step will generate the channel configuration "
                      "file that is required when launching the USB "
                      "receiver in Linux.") + "\n")

    if (os.path.isfile(chan_file)):
        print("Found previous %s file:" % (chan_file))

        if (not util.ask_yes_or_no("Remove and regenerate file?")):
            print("Configuration aborted.")
            return
        else:
            os.remove(chan_file)

    with open(chan_file, 'w') as f:
        f.write('[blocksat-ch]\n')
        f.write('\tDELIVERY_SYSTEM = DVBS2\n')
        f.write('\tFREQUENCY = %u\n' % (int(info['sat']['dl_freq'] * 1000)))
        if (info['lnb']['pol'].lower() == "dual"
                and 'v1_pointed' in info['lnb'] and info['lnb']['v1_pointed']):
            # If a dual-polarization LNB is already pointed for Blocksat v1,
            # then we must use the polarization that the LNB was pointed to
            # originally, regardless of the satellite signal's polarization. In
            # v1, what mattered the most was the power supply voltage, which
            # determined the polarization of the dual polarization LNBs. If the
            # power supply provides voltage >= 18 (often the case), then the
            # LNB necessarily operates currently with horizontal polarization.
            # Thus, on channels.conf we must use the same polarization in order
            # for the DVB adapter to supply the 18VDC voltage.
            if (info['lnb']["v1_psu_voltage"] >= 16):  # 16VDC threshold
                f.write('\tPOLARIZATION = HORIZONTAL\n')
            else:
                f.write('\tPOLARIZATION = VERTICAL\n')
        else:
            if (info['sat']['pol'] == 'V'):
                f.write('\tPOLARIZATION = VERTICAL\n')
            else:
                f.write('\tPOLARIZATION = HORIZONTAL\n')
        f.write('\tSYMBOL_RATE = {}\n'.format(
            defs.sym_rate[info['sat']['alias']]))
        f.write('\tINVERSION = AUTO\n')
        f.write('\tMODULATION = QPSK\n')
        pids = "+".join([str(x) for x in defs.pids])
        f.write('\tVIDEO_PID = {}\n'.format(pids))

    print("File \"%s\" saved." % (chan_file))

    with open(chan_file, 'r') as f:
        logging.debug(f.read())


Convert channel . conf file contents to dictionary

def _parse_chan_conf(chan_file):
    """Convert channel.conf file contents to dictionary"""
    chan_conf = {}
    with open(chan_file, 'r') as f:
        lines = f.read().splitlines()
        for line in lines[1:]:
            key, val = line.split('=')
            chan_conf[key.strip()] = val.strip()
    return chan_conf

def _parse_chan_conf ( chan_file ) : chan_conf = { } with open ( chan_file , 'r' ) as f : lines = f . read ( ) . splitlines ( ) for line in lines [ 1 : ] : key , val = line . split ( '=' ) chan_conf [ key . strip ( ) ] = val . strip ( ) return chan_conf


def _cfg_file_name(cfg_name, directory):
    """Get the name of the configuration JSON file"""
    # Remove paths
    basename = os.path.basename(cfg_name)
    # Remove extension
    noext = os.path.splitext(basename)[0]
    # Add JSON extension
    json_file = noext + ".json"
    return os.path.join(directory, json_file)

def
_cfg_file_name
(
cfg_name
,
directory
)
:
# Remove paths
basename
=
os
.
path
.
basename
(
cfg_name
)
# Remove extension
noext
=
os
.
path
.
splitext
(
basename
)
[
0
]
# Add JSON extension
json_file
=
noext
+
".json"
return
os
.
path
.
join
(
directory
,
json_file
)


def _read_cfg_file(cfg_file):
    """Read configuration file"""

    if (os.path.isfile(cfg_file)):
        with open(cfg_file) as fd:
            info = json.load(fd)
        return info

def _read_cfg_file ( cfg_file ) : if ( os . path . isfile ( cfg_file ) ) : with open ( cfg_file ) as fd : info = json . load ( fd ) return info


def _write_cfg_file(cfg_file, user_info):
    """Write configuration file"""
    with open(cfg_file, 'w') as fd:
        json.dump(user_info, fd)

def _write_cfg_file ( cfg_file , user_info ) : with open ( cfg_file , 'w' ) as fd : json . dump ( user_info , fd )


def _rst_cfg_file(cfg_file):
    """Reset a previous configuration file in case it exists"""
    info = _read_cfg_file(cfg_file)

    if (info is not None):
        print("Found previous configuration:")
        pprint(info, width=80, compact=False)
        if (util.ask_yes_or_no("Reset?")):
            os.remove(cfg_file)
        else:
            print("Configuration aborted.")
            return False
    return True

def
_rst_cfg_file
(
cfg_file
)
:
info
=
_read_cfg_file
(
cfg_file
)
if
(
info
is
not
)
:
print
(
"Found previous configuration:"
)
pprint
(
info
,
width
=
80
,
compact
=
False
)
if
(
util
.
ask_yes_or_no
(
"Reset?"
)
)
:
os
.
remove
(
cfg_file
)
else
:
print
(
"Configuration aborted."
)
return
False
return
True


def read_cfg_file(cfg_name, directory):
    """Read configuration file

    If not available, run configuration helper.

    """
    cfg_file = _cfg_file_name(cfg_name, directory)
    info = _read_cfg_file(cfg_file)

    while (info is None):
        print("Missing {} configuration file".format(cfg_file))
        if (util.ask_yes_or_no("Run configuration helper now?")):
            configure(Namespace(cfg_dir=directory, cfg=cfg_name))
        else:
            print("Abort")
            return

        info = _read_cfg_file(cfg_file)

    return info

def
read_cfg_file
(
cfg_name
,
directory
)
:
cfg_file
=
_cfg_file_name
(
cfg_name
,
directory
)
info
=
_read_cfg_file
(
cfg_file
)
while
(
info
is
)
:
print
(
"Missing {} configuration file"
.
format
(
cfg_file
)
)
if
(
util
.
ask_yes_or_no
(
"Run configuration helper now?"
)
)
:
configure
(
Namespace
(
cfg_dir
=
directory
,
cfg
=
cfg_name
)
)
else
:
print
(
"Abort"
)
return
info
=
_read_cfg_file
(
cfg_file
)
return
info


def write_cfg_file(cfg_name, directory, user_info):
    """Write configuration file"""
    cfg_file = _cfg_file_name(cfg_name, directory)
    _write_cfg_file(cfg_file, user_info)


def get_rx_model(user_info):
    """Return string with the receiver vendor and model

    Note: this function differs from _get_rx_marketing_name(). The latter
    includes the satellite kit name. In contrast, this function returns the raw
    vendeor-model string.

    """
    return (user_info['setup']['vendor'] + " " +
            user_info['setup']['model']).strip()

def
get_rx_model
(
user_info
)
:
return
(
user_info
[
'setup'
]
[
'vendor'
]
+
" "
+
user_info
[
'setup'
]
[
'model'
]
)
.
strip
(
)


def get_antenna_model(user_info):
    """Return string with the antenna model"""
    antenna_info = user_info['setup']['antenna']
    antenna_type = antenna_info.get('type')

    if (antenna_type == 'dish' or antenna_info['label'] == 'custom'):
        dish_size = antenna_info['size']
        if (dish_size >= 100):
            antenna = "{:g}m dish".format(dish_size / 100)
        else:
            antenna = "{:g}cm dish".format(dish_size)
    else:
        return antenna_info['label']

    return antenna

def
get_antenna_model
(
user_info
)
:
antenna_info
=
user_info
[
'setup'
]
[
'antenna'
]
antenna_type
=
antenna_info
.
get
(
'type'
)
if
(
antenna_type
==
'dish'
or
antenna_info
[
'label'
]
==
'custom'
)
:
dish_size
=
antenna_info
[
'size'
]
if
(
dish_size
>=
100
)
:
antenna
=
"{:g}m dish"
.
format
(
dish_size
/
100
)
else
:
antenna
=
"{:g}cm dish"
.
format
(
dish_size
)
else
:
return
antenna_info
[
'label'
]
return
antenna


def get_lnb_model(user_info):
    """Return string with the LNB model"""
    lnb_info = user_info['lnb']
    if (lnb_info['vendor'] == "" and lnb_info['model'] == ""):
        if (lnb_info['universal']):
            lnb = "Custom Universal LNB"
        else:
            lnb = "Custom {}-band LNB".format(lnb_info['band'])
    else:
        lnb = "{} {}".format(lnb_info['vendor'], lnb_info['model'])
    return lnb

def
get_lnb_model
(
user_info
)
:
lnb_info
=
user_info
[
'lnb'
]
if
(
lnb_info
[
'vendor'
]
==
""
and
lnb_info
[
'model'
]
==
""
)
:
if
(
lnb_info
[
'universal'
]
)
:
lnb
=
"Custom Universal LNB"
else
:
lnb
=
"Custom {}-band LNB"
.
format
(
lnb_info
[
'band'
]
)
else
:
lnb
=
"{} {}"
.
format
(
lnb_info
[
'vendor'
]
,
lnb_info
[
'model'
]
)
return
lnb


def get_net_if(user_info):
    """Get the network interface used by the given setup"""
    if (user_info is None):
        raise ValueError("Failed to read local configuration")

    setup_type = user_info['setup']['type']

    if (setup_type in [defs.sdr_setup_type, defs.sat_ip_setup_type]):
        interface = "lo"
    elif (setup_type == defs.linux_usb_setup_type):
        if ('adapter' not in user_info['setup']):
            interface = "dvb0_0"
            logger.warning("Could not find the dvbnet interface name. "
                           "Is the {} receiver running?".format(
                               get_rx_model(user_info)))
            logger.warning("Assuming interface name {}.".format(interface))
        else:
            adapter = user_info['setup']['adapter']
            interface = "dvb{}_0".format(adapter)
    elif (setup_type == defs.standalone_setup_type):
        interface = user_info['setup']['netdev']
    else:
        raise ValueError("Unknown setup type")
    return interface


def subparser(subparsers):
    """Argument parser of config command"""
    p = subparsers.add_parser('config',
                              aliases=['cfg'],
                              description="Configure Blocksat Rx setup",
                              help='Define receiver and Bitcoin Satellite \
                              configurations',
                              formatter_class=ArgumentDefaultsHelpFormatter)
    p.set_defaults(func=configure)

    subsubparsers = p.add_subparsers(title='subcommands',
                                     help='Target sub-command')
    p2 = subsubparsers.add_parser(
        'show',
        description="Display the local configuration",
        help='Display the local configuration',
        formatter_class=ArgumentDefaultsHelpFormatter)
    p2.set_defaults(func=show)

    p3 = subsubparsers.add_parser(
        'channel',
        description="Configure the channels file used by the USB receiver",
        help='Configure the channels file used by the USB receiver',
        formatter_class=ArgumentDefaultsHelpFormatter)
    p3.set_defaults(func=channel)
    return p


def configure(args):
    """Configure Blocksat Receiver setup

    """
    cfg_file = _cfg_file_name(args.cfg, args.cfg_dir)
    rst_ok = _rst_cfg_file(cfg_file)
    if (not rst_ok):
        return

    try:
        user_sat = _cfg_satellite()
        user_setup = _cfg_rx_setup()
        user_lnb = _cfg_lnb(user_sat, user_setup)
        user_freqs = _cfg_frequencies(user_sat, user_lnb, user_setup)
    except KeyboardInterrupt:
        print("\nAbort")
        sys.exit(1)

    user_info = {
        'sat': user_sat,
        'setup': user_setup,
        'lnb': user_lnb,
        'freqs': user_freqs
    }

    logging.debug(pformat(user_info))

    if not os.path.exists(args.cfg_dir):
        os.makedirs(args.cfg_dir)

    # Channel configuration file
    if (user_setup['type'] == defs.linux_usb_setup_type):
        chan_file = os.path.join(args.cfg_dir, args.cfg + "-channel.conf")
        _cfg_chan_conf(user_info, chan_file)
        user_info['setup']['channel'] = chan_file

    # Create the JSON configuration file
    _write_cfg_file(cfg_file, user_info)

    os.system('clear')
    util.print_header("JSON configuration file")
    print("Saved configurations on %s" % (cfg_file))

    util.print_header("Next Steps")

    print(textwrap.fill("Please check setup instructions by running:"))
    print("""
    blocksat-cli instructions
    """)

"""
    blocksat-cli instructions
    """


def show(args):
    """Print the local configuration"""
    info = read_cfg_file(args.cfg, args.cfg_dir)
    if (info is None):
        return
    print("| {:30s} | {:25s} |".format("Receiver",
                                       _get_rx_marketing_name(info['setup'])))
    print("| {:30s} | {:25s} |".format("LNB", get_lnb_model(info)))
    print("| {:30s} | {:25s} |".format("Antenna", get_antenna_model(info)))
    pr_cfgs = {
        'freqs': {
            'dl': ("Downlink frequency", "MHz", None),
            'lo': ("LNB LO frequency", "MHz", None),
            'l_band': ("Receiver L-band frequency", "MHz", None)
        },
        'sat': {
            'name': ("Satellite name", "", None),
            'band': ("Signal band", "", None),
            'pol': ("Signal polarization", "", lambda x: "Horizontal"
                    if x == "H" else "vertical"),
        }
    }
    for category in pr_cfgs:
        for key, pr_cfg in pr_cfgs[category].items():
            label = pr_cfg[0]
            unit = pr_cfg[1]
            val = info[category][key]
            # Transform the value if a callback is defined
            if (pr_cfg[2] is not None):
                val = pr_cfg[2](val)
            print("| {:30s} | {:25s} |".format(label, str(val) + " " + unit))


Configure the channels . conf file directly

def channel(args):
    """Configure the channels.conf file directly"""
    user_info = read_cfg_file(args.cfg, args.cfg_dir)
    if (user_info is None):
        return

    # Channel configuration file
    if (user_info['setup']['type'] != defs.linux_usb_setup_type):
        raise TypeError("Invalid command for {} receivers".format(
            user_info['setup']['type']))

    chan_file = os.path.join(args.cfg_dir, args.cfg + "-channel.conf")
    _cfg_chan_conf(user_info, chan_file)

    # Overwrite the channels.conf path in case it is changing from a previous
    # version of the CLI when the conf file name was not bound to the cfg name
    user_info['setup']['channel'] = chan_file
    write_cfg_file(args.cfg, args.cfg_dir, user_info)


def _print_s400_instructions(info):
    """Print instructions for configuration of the Novra S400
    """
    util.print_header("Novra S400")

    _print("""
    The Novra S400 is a standalone receiver, which will receive data from
    satellite and output IP packets to the host over the network. Hence, you
    will need to configure both the S400 and the host.
    """)

    util.print_sub_header("Connections")

    _print("The Novra S400 can be connected as follows:")

    print(("LNB ----> S400 (RF1 Interface) -- "
           "S400 (LAN 1 Interface) ----> Host / Network\n"))

    _item("Connect the LNB directly to interface RF1 of the S400 using a "
          "coaxial cable (an RG6 cable is recommended).")
    _item("Connect the S400's LAN1 interface to your computer or network.")

    util.prompt_for_enter()

    util.print_sub_header("Network Connection")

    _print("Next, make sure the S400 receiver is reachable by the host.")

    _print("First, configure your host's network interface to the same subnet "
           "as the S400. By default, the S400 is configured with IP address "
           "192.168.1.2 on LAN1 and 192.168.2.2 on LAN2. Hence, if you "
           "connect to LAN1, make sure your host's network interface has IP "
           "address 192.168.1.x, where \"x\" could be any number higher than "
           "2. For example, you could configure your host's network interface "
           "with IP address 192.168.1.3.")

    _print("After that, open the browser and access 192.168.1.2 (or "
           "192.168.2.2 if connected to LAN 2). The web management console "
           "should open up successfully.")

    print()

    util.prompt_for_enter()

    util.print_sub_header("Software Requirements")

    _print("Next, install all software pre-requisites on your host. Run:")

    print("    blocksat-cli deps install\n")

    _print("""
    NOTE: this command supports the apt, dnf, and yum package managers.""")

    util.prompt_for_enter()

    util.print_sub_header("Receiver and Host Configuration")

    print("Now, configure the S400 receiver and the host by running:")

    print("\n    blocksat-cli standalone cfg\n")

    _print("""If you would like to review the changes that will be made to the
    host before applying them, first run the command in dry-run mode:""")

    print("    blocksat-cli standalone cfg --dry-run\n\n")

    util.prompt_for_enter()

    util.print_sub_header("Monitoring")

    print("Finally, you can monitor your receiver by running:")

    print("\n    blocksat-cli standalone monitor\n\n")

    util.prompt_for_enter()


def _print_usb_rx_instructions(info):
    """Print instructions for runnning with a Linux USB receiver
    """

    name = (info['setup']['vendor'] + " " + info['setup']['model']).strip()

    util.print_header(name)

    _print("""
    The {0} is a USB receiver, which will receive data from satellite and will
    output data to the host over USB. The host, in turn, is responsible for
    configuring the receiver using specific DVB-S2 tools. Hence, next, you need
    to prepare the host for driving the {0}.
    """.format(name))

    util.print_sub_header("Hardware Connections")

    print("The {} should be connected as follows:\n".format(name))

    print(("LNB ----> {0} (LNB Interface) -- "
           "{0} (USB Interface) ----> Host\n".format(name)))

    _item("Connect the LNB directly to \"LNB IN\" of the {} using a coaxial"
          " cable (an RG6 cable is recommended).".format(name))
    _item("Connect the {}'s USB interface to your computer.".format(name))

    util.prompt_for_enter()

    util.print_sub_header("Drivers")

    _print("""
    Before anything else, note that specific device drivers are required in
    order to use the {0}. Please, do note that driver installation can cause
    corruptions and, therefore, it is safer and **strongly recommended** to use
    a virtual machine for running the {0}. If you do so, please note that all
    commands recommended in the remainder of this page are supposed to be
    executed in the virtual machine.
    """.format(name))

    _print("Next, install the drivers for the {0} by running:".format(name))

    print("""
    blocksat-cli deps tbs-drivers
    """)

    print("Once the script completes the installation, reboot the virtual "
          "machine.")

    util.prompt_for_enter()

    util.print_sub_header("Host Requirements")

    print(
        "Now, install all pre-requisites (in the virtual machine) by running:")

    print("""
    blocksat-cli deps install
    """)

    _print("""
    NOTE: this command supports the apt, dnf, and yum package managers. For
    other package managers, refer to the instructions at:""")
    print(defs.user_guide_url + "doc/tbs.html")

    util.prompt_for_enter()

    util.print_sub_header("Configure the Host")

    _print(
        """Next, you need to create and configure the network interfaces that
    will output the IP traffic received via the TBS5927. You can apply all
    configurations by running the following command:""")

    print("\n    blocksat-cli usb config\n")

    _print("""If you would like to review the changes that will be made before
    applying them, first run the command in dry-run mode:
    """)

    print("\n    blocksat-cli usb config --dry-run\n")

    _print("""
    Note this command will define arbitrary IP addresses to the interfaces. If
    you need (or want) to define specific IP addresses instead, for example to
    avoid IP address conflicts, use command-line argument `--ip`.
    """)

    _print(
        """Furthermore, note that this configuration is not persistent across
    reboots. After a reboot, you need to run `blocksat-cli usb config`
    again.""")

    util.prompt_for_enter()

    util.print_sub_header("Launch")

    print("Finally, start the receiver by running:")

    print("\n    blocksat-cli usb launch\n")

    util.prompt_for_enter()

"""
    blocksat-cli deps tbs-drivers
    """

"""
    blocksat-cli deps install
    """


def _print_sdr_instructions(info):
    """Print instruction for configuration of an SDR setup
    """
    util.print_header("SDR Setup")

    util.print_sub_header("Connections")

    print("The SDR setup is connected as follows:\n")

    print("LNB ----> Power Supply ----> RTL-SDR ----> Host\n")

    _item("Connect the RTL-SDR USB dongle to your host PC.")
    _item("Connect the **non-powered** port of the power supply (labeled as "
          "\"Signal to IRD\") to the RTL-SDR using an SMA cable and an "
          "SMA-to-F adapter.")
    _item("Connect the **powered** port (labeled \"Signal to SWM\") of the "
          "power supply to the LNB using a coaxial cable (an RG6 cable is "
          "recommended).")

    print()
    _print("IMPORTANT: Do NOT connect the powered port of the power supply "
           "to the SDR interface. Permanent damage may occur to your SDR "
           "and/or your computer.")

    util.prompt_for_enter()

    util.print_sub_header("Software Requirements")

    print("The SDR-based setup relies on the applications listed below:\n")

    _item("leandvb: a software-based DVB-S2 receiver application.")
    _item("rtl_sdr: reads samples taken by the RTL-SDR and feeds them into "
          "leandvb.")
    _item("TSDuck: unpacks the output of leandvb and produces "
          "IP packets to be fed to Bitcoin Satellite.")
    _item("Gqrx: useful for spectrum visualization during antenna pointing.")

    print("\nTo install them, run:")
    print("""
    blocksat-cli deps install
    """)

    _print("""
        NOTE: This command supports the two most recent Ubuntu LTS, Fedora, and
        CentOS releases. In case you are using another Linux distribution or
        version, please refer to the manual compilation and installation
        instructions at:""")
    print(defs.user_guide_url + "doc/sdr.html")

    util.prompt_for_enter()

    util.print_sub_header("Configuration")

    _print(
        "Next, you can generate the configurations that are needed for gqrx "
        "by running:")

    print("    blocksat-cli gqrx-conf")

    util.prompt_for_enter()

    util.print_sub_header("Running")

    _print(
        "You should now be ready to launch the SDR receiver. You can run it "
        "by executing:")

    print("    blocksat-cli sdr\n")
    print("Or, in GUI mode:\n")
    print("    blocksat-cli sdr --gui\n")

    util.prompt_for_enter()

"""
    blocksat-cli deps install
    """


def _print_freq_info(info):
    """Print summary of frequencies of interest"""
    sat = info['sat']
    setup = info['setup']
    lnb = info['lnb']
    lo_freq = info['freqs']['lo']
    l_freq = info['freqs']['l_band']

    util.print_header("Frequencies")

    print("For your information, your setup relies on the following "
          "frequencies:\n")
    print("| Downlink %2s band frequency            | %8.2f MHz |" %
          (sat['band'], sat['dl_freq']))
    print("| LNB local oscillator (LO) frequency   | %8.2f MHz |" % (lo_freq))
    print("| Receiver L-band frequency             | %7.2f MHz  |" % (l_freq))
    print()

    if (lnb['universal'] and (setup['type'] == defs.sdr_setup_type)):
        if (sat['dl_freq'] > defs.ku_band_thresh):
            print("NOTE regarding Universal LNB:\n")

            print(
                textwrap.fill(
                    ("The DL frequency of {} is in Ku high "
                     "band (> {:.1f} MHz). Hence, you need to use "
                     "the higher frequency LO ({:.1f} MHz) of your "
                     "Universal LNB. This requires a 22 kHz tone "
                     "to be sent to the LNB.").format(sat['alias'],
                                                      defs.ku_band_thresh,
                                                      lo_freq)))
            print()
            print(
                textwrap.fill(("With a software-defined setup, you will "
                               "need to place a 22 kHz tone generator "
                               "inline between the LNB and the power "
                               "inserter. Typically the tone generator "
                               "uses power from the power inserter while "
                               "delivering the tone directly to the "
                               "LNB.")))

    util.prompt_for_enter()


def _print_lnb_info(info):
    """Print important waraning based on LNB choice"""
    lnb = info['lnb']
    sat = info['sat']
    setup = info['setup']

    if ((lnb['pol'] != "Dual") and (lnb['pol'] != sat['pol'])):
        util.print_header("LNB Information")
        lnb_pol = "Vertical" if lnb['pol'] == "V" else "Horizontal"
        logging.warning(
            textwrap.fill(
                "Your LNB has {} polarization and the signal from {} has the "
                "opposite polarization.".format(lnb_pol, sat['name'])))
        util.prompt_for_enter()

    if ((lnb['pol'] == "Dual") and (setup['type'] == defs.sdr_setup_type)):
        util.print_header("LNB Information")
        logging.warning(
            textwrap.fill(
                "Your LNB has dual polarization. Check the voltage of your "
                "power supply in order to discover the polarization on which "
                "your LNB will operate."))
        util.prompt_for_enter()


def subparser(subparsers):
    """Argument parser of instructions command"""
    p = subparsers.add_parser('instructions',
                              description="Instructions for Blocksat Rx setup",
                              help='Read instructions for the receiver setup',
                              formatter_class=ArgumentDefaultsHelpFormatter)
    p.set_defaults(func=show)
    return p


def show(args):
    """Show instructions"""
    info = config.read_cfg_file(args.cfg, args.cfg_dir)

    if (info is None):
        return

    os.system('clear')
    _print_lnb_info(info)

    if (info['setup']['type'] == defs.standalone_setup_type):
        _print_s400_instructions(info)
    elif (info['setup']['type'] == defs.sdr_setup_type):
        _print_sdr_instructions(info)
    elif (info['setup']['type'] == defs.linux_usb_setup_type):
        _print_usb_rx_instructions(info)
    elif (info['setup']['type'] == defs.sat_ip_setup_type):
        _print_sat_ip_instructions(info)

    _print_next_steps()

def show ( args ) : info = config . read_cfg_file ( args . cfg , args . cfg_dir ) if ( info is None ) : return os . system ( 'clear' ) _print_lnb_info ( info ) if ( info [ 'setup' ] [ 'type' ] == defs . standalone_setup_type ) : _print_s400_instructions ( info ) elif ( info [ 'setup' ] [ 'type' ] == defs . sdr_setup_type ) : _print_sdr_instructions ( info ) elif ( info [ 'setup' ] [ 'type' ] == defs . linux_usb_setup_type ) : _print_usb_rx_instructions ( info ) elif ( info [ 'setup' ] [ 'type' ] == defs . sat_ip_setup_type ) : _print_sat_ip_instructions ( info ) _print_next_steps ( )


Main - parse command - line arguments and call subcommands

def main():
    """Main - parse command-line arguments and call subcommands
    """
    default_cfg_dir = os.path.join(util.get_home_dir(), ".blocksat")

    parser = ArgumentParser(
        prog="blocksat-cli",
        description="Blockstream Satellite Command-Line Interface",
        formatter_class=ArgumentDefaultsHelpFormatter)
    parser.add_argument('-d',
                        '--debug',
                        action='store_true',
                        help='Set debug mode')
    parser.add_argument('--cfg',
                        default="config",
                        help="Target configuration set")
    parser.add_argument('--cfg-dir',
                        default=default_cfg_dir,
                        help="Directory to use for configuration files")
    parser.add_argument('--utc',
                        action='store_true',
                        help="Print log timestamps in UTC")
    parser.add_argument('-v',
                        '--version',
                        action='version',
                        version='%(prog)s {}'.format(__version__))

    subparsers = parser.add_subparsers(title='subcommands',
                                       help='Target sub-command',
                                       dest='subcommand')

    config.subparser(subparsers)
    instructions.subparser(subparsers)
    dependencies.subparser(subparsers)
    usb.subparser(subparsers)
    standalone.subparser(subparsers)
    rp.subparser(subparsers)
    firewall.subparser(subparsers)
    gqrx.subparser(subparsers)
    bitcoin.subparser(subparsers)
    sdr.subparser(subparsers)
    api.subparser(subparsers)
    satip.subparser(subparsers)

    args = parser.parse_args()

    # Filter commands that are Linux-only
    if (args.subcommand not in ["cfg", "instructions", "api", "btc"]):
        assert(platform.system() == 'Linux'), \
            "Command {} is currently Linux-only".format(args.subcommand)

    # Logging
    logging_fmt = '%(asctime)s %(levelname)-8s %(name)s %(message)s' if \
                  args.debug else '%(asctime)s %(levelname)-8s %(message)s'
    logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO,
                        format=logging_fmt,
                        datefmt='%Y-%m-%d %H:%M:%S')
    if (args.utc):
        logging.Formatter.converter = time.gmtime
    logging.debug('[Debug Mode]')

    # Check CLI updates
    update.check_cli_updates(args, __version__)

    if hasattr(args, 'func'):
        args.func(args)
    else:
        parser.print_help()


def configure(args):
    """Configure GQRX"""
    info = config.read_cfg_file(args.cfg, args.cfg_dir)

    if (info is None):
        return

    util.print_header("Gqrx Conf Generator")

    if args.path is None:
        home = os.path.expanduser("~")
        path = os.path.join(home, ".config", "gqrx")
    else:
        path = args.path

    conf_file = "default.conf"
    abs_path = os.path.join(path, conf_file)

    default_gains = r'@Variant(\0\0\0\b\0\0\0\x2\0\0\0\x6\0L\0N\0\x41\0\0' + \
        r'\0\x2\0\0\x1\x92\0\0\0\x4\0I\0\x46\0\0\0\x2\0\0\0\xcc)'

    cfg = """
    [General]
    configversion=2

    [fft]
    averaging=80
    db_ranges_locked=true
    pandapter_min_db=-90
    waterfall_min_db=-90

    [input]
    bandwidth=1000000
    device="rtl=0"
    frequency={}
    gains={}
    lnb_lo={}
    sample_rate=2400000

    [receiver]
    demod=0
    """.format(
        int(info['freqs']['dl'] * 1e6),
        default_gains,
        int(info['freqs']['lo'] * 1e6),
    )

    print("Save {} at {}/".format(conf_file, path))

    if (not util.ask_yes_or_no("Proceed?")):
        print("Aborted")
        return

    if not os.path.exists(path):
        os.makedirs(path)

    if os.path.exists(abs_path):
        if (not util.ask_yes_or_no("File already exists. Overwrite?")):
            print("Aborted")
            return

    with open(abs_path, "w") as file:
        file.write(textwrap.dedent(cfg))

    print("Saved")


def _setup_registered(self):
        """Setup for a receiver that is already registered

        If already registered, confirm that the registered GPG key is available
        in the keyring and prompt for the GPG private key passphrase required
        to sign report data

        """
        fingerprint = self.user_info['monitoring']['fingerprint']
        matching_keys = self.gpg.gpg.list_keys(True, keys=fingerprint)
        if (len(matching_keys) == 0):
            logger.error("Could not find key {} in the local "
                         "keyring.".format(fingerprint))
            try_again = util.ask_yes_or_no(
                "Reset the Monitoring API credentials and try registering "
                "with a new key?",
                default="n")
            if (try_again):
                self._delete_credentials()
                self._register()
            else:
                raise RuntimeError("Monitoring configuration failed")

            # Return regardless. If the key is not available, don't bother
            # about the password.
            return
        self.gpg.prompt_passphrase('Please enter your GPG passphrase to '
                                   'sign receiver reports: ')


def _delete_credentials(self):
        """Remove the registration info from the local config file"""
        self.user_info.pop('monitoring')
        config.write_cfg_file(self.cfg, self.cfg_dir, self.user_info)
        self.registered = False

def _delete_credentials ( self ) : self . user_info . pop ( 'monitoring' ) config . write_cfg_file ( self . cfg , self . cfg_dir , self . user_info ) self . registered = False


def _save_credentials(self, uuid, fingerprint):
        """Record the successful registration locally on the JSON config"""
        self.user_info['monitoring'] = {
            'registered': True,
            'uuid': uuid,
            'fingerprint': fingerprint
        }
        config.write_cfg_file(self.cfg, self.cfg_dir, self.user_info)
        self.registered = True

def _save_credentials ( self , uuid , fingerprint ) : self . user_info [ 'monitoring' ] = { 'registered' : True , 'uuid' : uuid , 'fingerprint' : fingerprint } config . write_cfg_file ( self . cfg , self . cfg_dir , self . user_info ) self . registered = True


def _register(self):
        """Run the registration procedure

        The procedure is divided in two steps:

        1) Interaction with the user;
        2) Interaction with the API.

        This method implements step 1, where it sets up a GPG keyring, collects
        the user address, and prepares the request parameters for registering
        with the monitoring API. In the end, it dispatches step 2 on a thread.

        Note step 2 has to run on a thread because it needs the receiver lock
        first. By running on a thread, the main (parent) thread can proceed to
        initializing the receiver.

        """
        self.registration_running = True
        self.registration_failure = False

        # Save some state on the local cache
        cache = Cache(self.cfg_dir)

        # Show the explainer when running the registration for the first time
        # for this configuration
        if (cache.get(self.cfg + '.monitoring.explainer') is None):
            _register_explainer()

        # Cache flag indicating that the explainer has been shown already
        cache.set(self.cfg + '.monitoring.explainer', True)
        cache.save()

        # Create a GPG keyring and a keypair if necessary
        api.config_keyring(self.gpg)

        # Make sure the GPG passphrase is available. The passphrase can be
        # collected on key creation for a new keyring. On the other hand, it
        # wouldn't be available yet at this point for a pre-existing keyring.
        if (self.gpg.passphrase is None):
            util.fill_print("Please inform your GPG passphrase to decode the "
                            "encrypted verification code sent over satellite")
            self.gpg.prompt_passphrase("GPG passphrase: ")

        os.system('clear')

        # Get the user's location
        util.fill_print(
            "Please inform you city, state (if applicable), and country:")
        confirmed = False
        while (not confirmed):
            # City (required)
            city = util.string_input("City")

            # State (optional)
            state = input("State: ")
            if (len(state) > 0):
                address = "{}, {}".format(city, state)
            else:
                address = city

            # Country (required)
            country = util.string_input("Country")

            # Wait until the user confirms the full address
            address += ", {}".format(country)
            confirmed = util.ask_yes_or_no("\"{}\"?".format(address))

        os.system('clear')

        # Registration parameters
        fingerprint = self.gpg.get_default_priv_key()['fingerprint']
        pubkey = self.gpg.gpg.export_keys(fingerprint)
        satellite = self.user_info['sat']['alias']
        rx_type = config.get_rx_model(self.user_info)
        antenna = config.get_antenna_model(self.user_info)
        lnb = config.get_lnb_model(self.user_info)

        # Run step 2 on a thread
        t1 = threading.Thread(target=self._register_thread,
                              daemon=True,
                              args=(fingerprint, pubkey, address, satellite,
                                    rx_type, antenna, lnb))
        t1.start()


def _register_thread(self,
                         fingerprint,
                         pubkey,
                         address,
                         satellite,
                         rx_type,
                         antenna,
                         lnb,
                         attempts=5):
        """Complete the registration procedure after receiver locking

        Sends the registration request to the Monitoring API and waits for the
        validation code sent over satellite through a Satellite API message.

        Args:
            fingerprint      : User GnuPG fingerprint
            pubkey           : User GnuPG public key
            address          : User address
            satellite        : Satellite covering the user location
            rx_type          : Receiver type (vendor and model)
            antenna          : Antenna type and size
            lnb              : LNB model
            attempts         : Maximum number of registration attempts

        """
        logger.info(
            "Waiting for Rx lock to initiate the registration with the "
            "monitoring server")
        self.rx_lock_event.wait()

        logger.info("Receiver locked. Ready to initiate the registration with "
                    "the monitoring server.")
        logger.info("Launching the API listener to receive the verification "
                    "code sent over satellite")

        # Run the API listener loop
        #
        # Note the validation code is:
        # - Encrypted to us (the Monitoring API encrypts using our public key)
        # - Sent as a raw (non-encapsulated) API message
        # - Sent over the API channel dedicated for authentication messages
        download_dir = None  # Don't save messages
        interface = config.get_net_if(self.user_info)
        recv_queue = queue.Queue()  # save API donwloads on this queue
        listen_loop = ApiListener(recv_queue=recv_queue)
        listen_thread = threading.Thread(target=listen_loop.run,
                                         daemon=True,
                                         args=(self.gpg, download_dir,
                                               defs.api_dst_addr, interface),
                                         kwargs={
                                             'channel': ApiChannel.AUTH.value,
                                             'plaintext': False,
                                             'save_raw': True,
                                             'no_save': True
                                         })
        listen_thread.start()

        failure = False
        while (attempts > 0):
            rv = requests.post(account_endpoint,
                               json={
                                   'fingerprint': fingerprint,
                                   'publickey': pubkey,
                                   'city': address,
                                   'satellite': satellite,
                                   'receiver_type': rx_type,
                                   'antenna': antenna,
                                   'lnb': lnb
                               })

            if (rv.status_code != requests.codes.ok):
                logger.error("Failed to register receiver with the monitoring "
                             "server")
                logger.error("{} ({})".format(rv.reason, rv.status_code))

                # If the initial registration call fails, declare failure right
                # away and break the loop. The problem likely won't go away if
                # we try again (e.g., a server error or invalid pubkey).
                failure = True
                break

            uuid = rv.json()['uuid']
            verified = rv.json()['verified']

            if (verified):
                logger.info("Receiver already registered and verified")
                self._save_credentials(uuid, fingerprint)
                break

            try:
                validation_key = recv_queue.get(timeout=10)
                recv_queue.task_done()
            except queue.Empty:
                logger.warning("Failed to receive the verification code. "
                               "Trying again...")
                attempts -= 1
                continue

            rv = requests.patch(account_endpoint,
                                json={
                                    'uuid': uuid,
                                    'validation_key': validation_key.decode()
                                })

            if (rv.status_code != requests.codes.ok):
                logger.error("Verification failed")
                logger.error("{} ({})".format(rv.reason, rv.status_code))
                attempts -= 1
                if (attempts > 0):
                    logger.warning("Trying again...")
                    time.sleep(1)
            else:
                logger.info("Functional receiver successfully validated")
                logger.info("Ready to report Rx metrics to the monitoring "
                            "server")
                self._save_credentials(uuid, fingerprint)
                break

        if (attempts == 0):
            logger.error("Maximum number of registration attempts " "reached")
            logger.error("Please check if your receiver is running properly "
                         "and restart the application to try again")
            failure = True

        self.registration_running = False
        self.registration_failure = failure

        # Stop the API listener
        listen_loop.stop()
        listen_thread.join()


def sign_report(self, data):
        """Sign a dictionary of metrics to be reported to the monitoring API

        First, add the monitored receiver's UUID to the dictionary of
        metrics. Then, use the default local GPG privkey to generate a detached
        signature corresponding to the JSON-dumped version of the dictionary
        (including the UUID). Lastly, append the detached signature. The
        resulting dictionary (including both the UUID and the signature) is
        ready to be sent to the API.

        Args:
            data : Dictionary with receiver data to be reported

        """
        assert (self.registered)
        data['uuid'] = self.user_info['monitoring']['uuid']
        fingerprint = self.user_info['monitoring']['fingerprint']
        data['signature'] = str(
            self.gpg.sign(json.dumps(data),
                          fingerprint,
                          clearsign=False,
                          detach=True))
        if (data['signature'] == ''):
            logger.error('GPG signature failed')

def
sign_report
(
self
,
data
)
:
assert
(
self
.
registered
)
data
[
'uuid'
]
=
self
.
user_info
[
'monitoring'
]
[
'uuid'
]
fingerprint
=
self
.
user_info
[
'monitoring'
]
[
'fingerprint'
]
data
[
'signature'
]
=
str
(
self
.
gpg
.
sign
(
json
.
dumps
(
data
)
,
fingerprint
,
clearsign
=
False
,
detach
=
True
)
)
if
(
data
[
'signature'
]
==
''
)
:
logger
.
error
(
'GPG signature failed'
)


def _check_distro(setup_type):
    """Check if distribution is supported"""
    if (distro.id() in supported_distros):
        return

    base_url = defs.user_guide_url + "doc/"
    instructions_url = {
        defs.sdr_setup_type: "sdr.md",
        defs.linux_usb_setup_type: "tbs.md",
        defs.standalone_setup_type: "s400.md",
        defs.sat_ip_setup_type: "sat-ip.md"
    }
    full_url = base_url + instructions_url[setup_type]
    logger.error("{} is not a supported Linux distribution".format(
        distro.name()))
    logger.info(
        textwrap.fill("Please, refer to the {} receiver setup "
                      "instructions at {}".format(setup_type, full_url)))
    raise ValueError("Unsupported Linux distribution")


def _get_apt_repo(distro_id):
    """Find the APT package repository for the given distro"""
    if distro_id == "ubuntu":
        apt_repo = "ppa:blockstream/satellite"
    elif distro_id in ["debian", "raspbian"]:
        apt_repo = ("https://aptly.blockstream.com/"
                    "satellite/{}/").format(distro_id)
    else:
        raise ValueError("Unsupported distribution {}".format(distro_id))
    return apt_repo

def
_get_apt_repo
(
distro_id
)
:
if
distro_id
==
"ubuntu"
:
apt_repo
=
"ppa:blockstream/satellite"
elif
distro_id
in
[
"debian"
,
"raspbian"
]
:
apt_repo
=
(
"https://aptly.blockstream.com/"
"satellite/{}/"
)
.
format
(
distro_id
)
else
:
raise
ValueError
(
"Unsupported distribution {}"
.
format
(
distro_id
)
)
return
apt_repo


Check if Blockstream Satellite s binary package repository is enabled

def _check_pkg_repo(distro_id):
    """Check if Blockstream Satellite's binary package repository is enabled

    Args:
        distro_id: Linux distribution ID

    Returns:
        (bool) True if the repository is already enabled

    """
    found = False
    if (which("apt")):
        apt_repo = _get_apt_repo(distro_id)
        grep_str = apt_repo.replace("ppa:", "")
        apt_sources = glob.glob("/etc/apt/sources.list.d/*")
        apt_sources.append("/etc/apt/sources.list")
        cmd = ["grep", grep_str]
        cmd.extend(apt_sources)
        res = runner.run(cmd, stdout=subprocess.DEVNULL, nocheck=True)
        found = (res.returncode == 0)
    elif (which("dnf")):
        res = runner.run(["dnf", "copr", "list", "--enabled"],
                         root=True,
                         capture_output=True)
        pkgs = res.stdout.decode().splitlines()
        found = ("copr.fedorainfracloud.org/blockstream/satellite" in pkgs)
    elif (which("yum")):
        yum_sources = glob.glob("/etc/yum.repos.d/*")
        cmd = ["grep", "blockstream/satellite"]
        cmd.extend(yum_sources)
        res = runner.run(cmd, stdout=subprocess.DEVNULL, nocheck=True)
        found = (res.returncode == 0)
    else:
        raise RuntimeError("Could not find a supported package manager")

    if (found):
        logger.debug("blockstream/satellite repository already enabled")

    return found


Enable Blockstream Satellite s binary package repository

def _enable_pkg_repo(distro_id, interactive):
    """Enable Blockstream Satellite's binary package repository

    Args:
        distro_id: Linux distribution ID
        interactive: Interactive mode

    """
    cmds = list()
    if (which("apt")):
        apt_repo = _get_apt_repo(distro_id)

        # On debian, add the apt repository through "add-apt-repository". On
        # raspbian, where "add-apt-repository" does not work, manually add a
        # file into /etc/apt/sources.list.d/.
        if distro_id == "raspbian":
            release_codename = distro.codename()
            apt_list_filename = "blockstream-satellite-{}-{}.list".format(
                distro_id, release_codename)
            apt_list_file = os.path.join("/etc/apt/sources.list.d/",
                                         apt_list_filename)
            apt_component = "main"
            apt_file_content = "deb {0} {1} {2}\n# deb-src {0} {1} {2}".format(
                apt_repo, release_codename, apt_component)
            # In execution mode, create a temporary file and move it to
            # /etc/apt/sources.list.d/. With that, only the move command needs
            # root permissions, whereas the temporary file does not. In dry-run
            # mode, print an equivalent echo command.
            if (runner.dry):
                cmd = [
                    "echo", "-e",
                    repr(apt_file_content), ">>", apt_list_file
                ]
            else:
                tmp_file = tempfile.NamedTemporaryFile(mode="w", delete=False)
                with tmp_file as fd:
                    fd.write(apt_file_content)
                cmd = ["mv", tmp_file.name, apt_list_file]
        else:
            cmd = ["add-apt-repository", apt_repo]
            if (not interactive):
                cmd.append("-y")

        cmds.append(cmd)

        if distro_id in ["debian", "raspbian"]:
            cmd = [
                "apt-key", "adv", "--keyserver", "keyserver.ubuntu.com",
                "--recv-keys", defs.blocksat_pubkey
            ]
            cmds.append(cmd)

        cmd = ["apt", "update"]
        if (not interactive):
            cmd.append("-y")
        cmds.append(cmd)
    elif (which("dnf")):
        cmd = ["dnf", "copr", "enable", "blockstream/satellite"]
        if (not interactive):
            cmd.append("-y")
        cmds.append(cmd)
    elif (which("yum")):
        cmd = ["yum", "copr", "enable", "blockstream/satellite"]
        if (not interactive):
            cmd.append("-y")
        cmds.append(cmd)
    else:
        raise RuntimeError("Could not find a supported package manager")

    for cmd in cmds:
        runner.run(cmd, root=True)


def _update_pkg_repo(interactive):
    """Update APT's package index

    NOTE: this function updates APT only. On dnf/yum, there is no need to run
    an update manually. The tool (dnf/yum) updates the package index
    automatically when an install/upgrade command is called.

    """

    if (not which("apt")):
        return

    cmd = ["apt", "update"]
    if (not interactive):
        cmd.append("-y")

    runner.run(cmd, root=True)

def _update_pkg_repo ( interactive ) : if ( not which ( "apt" ) ) : return cmd = [ "apt" , "update" ] if ( not interactive ) : cmd . append ( "-y" ) runner . run ( cmd , root = True )


def _install_packages(apt_list,
                      dnf_list,
                      yum_list,
                      interactive=True,
                      update=False):
    """Install binary packages

    Args:
        apt_list    : List of package names for installation via apt
        dnf_list    : List of package names for installation via dnf
        dnf_list    : List of package names for installation via yum
        interactive : Whether to run an interactive install (w/ user prompting)
        update      : Whether to update pre-installed packages instead

    """
    if (which("apt")):
        manager = "apt"
        cmd = ["apt-get", "install"]
        if (update):
            cmd.append("--only-upgrade")
        cmd.extend(apt_list)
    elif (which("dnf")):
        manager = "dnf"
        if (update):
            cmd = ["dnf", "upgrade"]
        else:
            cmd = ["dnf", "install"]
        cmd.extend(dnf_list)
    elif (which("yum")):
        manager = "yum"
        if (update):
            cmd = ["yum", "upgrade"]
        else:
            cmd = ["yum", "install"]
        cmd.extend(yum_list)
    else:
        raise RuntimeError("Could not find a supported package manager")

    env = None
    if (not interactive):
        cmd.append("-y")
        if (manager == "apt"):
            env = os.environ.copy()
            env["DEBIAN_FRONTEND"] = "noninteractive"

    runner.run(cmd, root=True, env=env)


def _install_common(interactive=True, update=False, btc=False):
    """Install dependencies that are common to all setups"""
    util.print_header("Installing Common Dependencies")
    apt_pkg_list = ["software-properties-common"]
    dnf_pkg_list = ["dnf-plugins-core"]
    yum_pkg_list = ["yum-plugin-copr"]

    # "gnupg" is installed as a dependency of "software-properties-common" on
    # Ubuntu. In contrast, it is not a dependency on Debian. Hence, add it
    # explicitly to the list. Add also "dirmngr", which may not be
    # automatically installed with gnupg (it is only automatically installed
    # from buster onwards). Lastly, add "apt-transport-https" to fetch debian
    # and raspbian packages from Aptly using HTTPS.
    distro_id = distro.id()
    if distro_id in ["debian", "raspbian"]:
        apt_pkg_list.extend(["gnupg", "dirmngr", "apt-transport-https"])

    if distro_id == "centos":
        dnf_pkg_list.append("epel-release")
        yum_pkg_list.append("epel-release")

    _install_packages(apt_pkg_list, dnf_pkg_list, yum_pkg_list, interactive,
                      update)

    # Enable our binary package repository
    if runner.dry or (not _check_pkg_repo(distro_id)):
        _enable_pkg_repo(distro_id, interactive)

    # Install bitcoin-satellite
    if (btc):
        apt_pkg_list = [
            "bitcoin-satellite", "bitcoin-satellite-qt", "bitcoin-satellite-tx"
        ]
        dnf_pkg_list = ["bitcoin-satellite", "bitcoin-satellite-qt"]
        yum_pkg_list = ["bitcoin-satellite", "bitcoin-satellite-qt"]

        if (which("dnf")):
            # dnf matches partial package names, and so it seems that when
            # bitcoin-satellite and bitcoin-satellite-qt are installed in one
            # go, only the latter gets installed. The former is matched to
            # bitcoin-satellite-qt and assumed as installed. Hence, as a
            # workaround, install the two packages separately.
            for pkg in dnf_pkg_list:
                _install_packages(apt_pkg_list, [pkg], [pkg], interactive,
                                  update)
        else:
            _install_packages(apt_pkg_list, [], yum_pkg_list, interactive,
                              update)


def _install_specific(target, interactive=True, update=False):
    """Install setup-specific dependencies"""
    key = next(key for key, val in target_map.items() if val == target)
    pkg_map = {
        'sdr': {
            'apt': ["gqrx-sdr", "rtl-sdr", "leandvb", "tsduck"],
            'dnf': ["gqrx", "rtl-sdr", "leandvb", "tsduck"],
            'yum': ["rtl-sdr", "leandvb", "tsduck"]
        },
        'usb': {
            'apt': ["iproute2", "iptables", "dvb-apps", "dvb-tools"],
            'dnf': ["iproute", "iptables", "dvb-apps", "v4l-utils"],
            'yum': ["iproute", "iptables", "dvb-apps", "v4l-utils"]
        },
        'standalone': {
            'apt': ["iproute2", "iptables"],
            'dnf': ["iproute", "iptables"],
            'yum': ["iproute", "iptables"]
        },
        'sat-ip': {
            'apt': ["iproute2", "tsduck"],
            'dnf': ["iproute", "tsduck"],
            'yum': ["iproute", "tsduck"]
        }
    }
    # NOTE:
    # - leandvb and tsduck come from our repository. Also, note gqrx is not
    #   available on CentOS (hence not included on the yum list).
    # - In the USB setup, the only package from our repository is dvb-apps in
    #   the specific case of dnf (RPM) installation, given that dvb-apps is not
    #   available on the latest mainstream fedora repositories.

    util.print_header("Installing {} Receiver Dependencies".format(target))
    _install_packages(pkg_map[key]['apt'], pkg_map[key]['dnf'],
                      pkg_map[key]['yum'], interactive, update)


def _print_help(args):
    """Re-create argparse's help menu"""
    parser = ArgumentParser()
    subparsers = parser.add_subparsers(title='', help='')
    parser = subparser(subparsers)
    print(parser.format_help())

def _print_help ( args ) : parser = ArgumentParser ( ) subparsers = parser . add_subparsers ( title = '' , help = '' ) parser = subparser ( subparsers ) print ( parser . format_help ( ) )


def subparser(subparsers):
    """Parser for install command"""
    p = subparsers.add_parser('dependencies',
                              aliases=['deps'],
                              description="Manage dependencies",
                              help='Manage dependencies',
                              formatter_class=ArgumentDefaultsHelpFormatter)

    p.set_defaults(func=_print_help)
    p.add_argument("-y",
                   "--yes",
                   action='store_true',
                   default=False,
                   help="Non-interactive mode. Answers \"yes\" automatically \
                   to binary package installation prompts")
    p.add_argument("--dry-run",
                   action='store_true',
                   default=False,
                   help="Print all commands but do not execute them")

    subsubp = p.add_subparsers(title='subcommands', help='Target sub-command')

    p1 = subsubp.add_parser('install',
                            description="Install software dependencies",
                            help='Install software dependencies')
    p1.add_argument("--target",
                    choices=list(target_map.keys()),
                    default=None,
                    help="Target setup type for installation of dependencies")
    p1.add_argument("--btc",
                    action='store_true',
                    default=False,
                    help="Install bitcoin-satellite")
    p1.set_defaults(func=run, update=False)

    p2 = subsubp.add_parser('update',
                            aliases=['upgrade'],
                            description="Update software dependencies",
                            help='Update software dependencies')
    p2.add_argument("--target",
                    choices=list(target_map.keys()),
                    default=None,
                    help="Target setup type for the update of dependencies")
    p2.add_argument("--btc",
                    action='store_true',
                    default=False,
                    help="Update bitcoin-satellite")
    p2.set_defaults(func=run, update=True)

    p3 = subsubp.add_parser('tbs-drivers',
                            description="Install TBS USB receiver drivers",
                            help='Install TBS USB receiver drivers')
    p3.set_defaults(func=drivers)

    return p


def run(args):
    """Run installations"""

    # Interactive installation? I.e., requires user to press "y/n"
    interactive = (not args.yes)

    # Configure the subprocess runner
    runner.set_dry(args.dry_run)

    if (args.target is not None):
        target = target_map[args.target]
    else:
        info = config.read_cfg_file(args.cfg, args.cfg_dir)
        if (info is None):
            return
        target = info['setup']['type']

    _check_distro(target)

    if (os.geteuid() != 0 and not args.dry_run):
        util.fill_print("Some commands require root access and may prompt "
                        "for a password")

    if (args.dry_run):
        util.print_header("Dry Run Mode")

    # Update package index
    _update_pkg_repo(interactive)

    # Common dependencies (regardless of setup)
    _install_common(interactive=interactive, update=args.update, btc=args.btc)

    # Install setup-specific dependencies
    _install_specific(target, interactive=interactive, update=args.update)


def check_apps(apps):
    """Check if required apps are installed"""
    for app in apps:
        if (not which(app)):
            logging.error("Couldn't find {}. Is it installed?".format(app))
            print("\nTo install software dependencies, run:")
            print("""
            blocksat-cli deps install
            """)
            return False
    # All apps are installed
    return True

"""
            blocksat-cli deps install
            """


def gen_args(self, target, btc=False):
        """Mock command-line argument"""
        logging.basicConfig(level=logging.DEBUG)
        parser = argparse.ArgumentParser()
        subparsers = parser.add_subparsers()
        dependencies.subparser(subparsers)
        args = ["deps", "-y", "install", "--target", target]
        if (btc):
            args.append("--btc")
        return parser.parse_args(args)

def gen_args ( self , target , btc = False ) : logging . basicConfig ( level = logging . DEBUG ) parser = argparse . ArgumentParser ( ) subparsers = parser . add_subparsers ( ) dependencies . subparser ( subparsers ) args = [ "deps" , "-y" , "install" , "--target" , target ] if ( btc ) : args . append ( "--btc" ) return parser . parse_args ( args )


def test_usb_deps(self):
        """Test the installation of USB receiver dependencies"""
        args = self.gen_args("usb")
        dependencies.run(args)
        expected_apps = [
            "dvbnet", "dvb-fe-tool", "dvbv5-zap", "ip", "iptables"
        ]
        self.assertTrue(dependencies.check_apps(expected_apps))

def test_usb_deps ( self ) : args = self . gen_args ( "usb" ) dependencies . run ( args ) expected_apps = [ "dvbnet" , "dvb-fe-tool" , "dvbv5-zap" , "ip" , "iptables" ] self . assertTrue ( dependencies . check_apps ( expected_apps ) )


def test_sdr_deps(self):
        """Test the installation of SDR receiver dependencies"""
        args = self.gen_args("sdr")
        dependencies.run(args)
        expected_apps = ["rtl_sdr", "leandvb", "ldpc_tool", "tsp"]
        self.assertTrue(dependencies.check_apps(expected_apps))

def test_sdr_deps ( self ) : args = self . gen_args ( "sdr" ) dependencies . run ( args ) expected_apps = [ "rtl_sdr" , "leandvb" , "ldpc_tool" , "tsp" ] self . assertTrue ( dependencies . check_apps ( expected_apps ) )


def test_standalone_deps(self):
        """Test the installation of standalone receiver dependencies"""
        args = self.gen_args("standalone")
        dependencies.run(args)
        expected_apps = ["ip", "iptables"]
        self.assertTrue(dependencies.check_apps(expected_apps))

def test_standalone_deps ( self ) : args = self . gen_args ( "standalone" ) dependencies . run ( args ) expected_apps = [ "ip" , "iptables" ] self . assertTrue ( dependencies . check_apps ( expected_apps ) )


Test the installation of sat - ip receiver dependencies

def test_sat_ip_deps(self):
        """Test the installation of sat-ip receiver dependencies"""
        args = self.gen_args("sat-ip")
        dependencies.run(args)
        expected_apps = ["tsp"]
        self.assertTrue(dependencies.check_apps(expected_apps))

def test_sat_ip_deps ( self ) : args = self . gen_args ( "sat-ip" ) dependencies . run ( args ) expected_apps = [ "tsp" ] self . assertTrue ( dependencies . check_apps ( expected_apps ) )


Test the installation of bitcoin - satellite

def test_bitcoin_satellite(self):
        """Test the installation of bitcoin-satellite"""
        args = self.gen_args("standalone", btc=True)
        dependencies.run(args)
        expected_apps = ["bitcoind", "bitcoin-cli", "bitcoin-tx", "bitcoin-qt"]
        self.assertTrue(dependencies.check_apps(expected_apps))

def test_bitcoin_satellite ( self ) : args = self . gen_args ( "standalone" , btc = True ) dependencies . run ( args ) expected_apps = [ "bitcoind" , "bitcoin-cli" , "bitcoin-tx" , "bitcoin-qt" ] self . assertTrue ( dependencies . check_apps ( expected_apps ) )


Return the udpmulticast configuration line for bitcoin . conf

def _udpmulticast(dev,
                  src_addr,
                  dst_addr=defs.btc_dst_addr,
                  trusted="1",
                  label=""):
    """Return the udpmulticast configuration line for bitcoin.conf"""
    return dev + "," + dst_addr + "," + src_addr + "," + trusted + "," + label

def _udpmulticast ( dev , src_addr , dst_addr = defs . btc_dst_addr , trusted = "1" , label = "" ) : return dev + "," + dst_addr + "," + src_addr + "," + trusted + "," + label


def _gen_cfgs(info, interface):
    """Generate configurations"""
    cfg = Cfg()
    cfg.add_opt("debug", "udpnet")
    cfg.add_opt("debug", "udpmulticast")
    cfg.add_opt("udpmulticastloginterval", "600")

    if (info['setup']['type'] == defs.sdr_setup_type):
        src_addr = "127.0.0.1"
        label = "blocksat-sdr"
    elif (info['setup']['type'] == defs.linux_usb_setup_type):
        src_addr = info['sat']['ip']
        label = "blocksat-tbs"
    elif (info['setup']['type'] == defs.standalone_setup_type):
        src_addr = info['sat']['ip']
        label = "blocksat-s400"
    elif (info['setup']['type'] == defs.sat_ip_setup_type):
        src_addr = "127.0.0.1"
        label = "blocksat-sat-ip"
    else:
        raise ValueError("Unknown setup type")

    cfg.add_opt("udpmulticast",
                _udpmulticast(dev=interface, src_addr=src_addr, label=label))

    return cfg

def
_gen_cfgs
(
info
,
interface
)
:
cfg
=
Cfg
(
)
cfg
.
add_opt
(
"debug"
,
"udpnet"
)
cfg
.
add_opt
(
"debug"
,
"udpmulticast"
)
cfg
.
add_opt
(
"udpmulticastloginterval"
,
"600"
)
if
(
info
[
'setup'
]
[
'type'
]
==
defs
.
sdr_setup_type
)
:
src_addr
=
"127.0.0.1"
label
=
"blocksat-sdr"
elif
(
info
[
'setup'
]
[
'type'
]
==
defs
.
linux_usb_setup_type
)
:
src_addr
=
info
[
'sat'
]
[
'ip'
]
label
=
"blocksat-tbs"
elif
(
info
[
'setup'
]
[
'type'
]
==
defs
.
standalone_setup_type
)
:
src_addr
=
info
[
'sat'
]
[
'ip'
]
label
=
"blocksat-s400"
elif
(
info
[
'setup'
]
[
'type'
]
==
defs
.
sat_ip_setup_type
)
:
src_addr
=
"127.0.0.1"
label
=
"blocksat-sat-ip"
else
:
raise
ValueError
(
"Unknown setup type"
)
cfg
.
add_opt
(
"udpmulticast"
,
_udpmulticast
(
dev
=
interface
,
src_addr
=
src_addr
,
label
=
label
)
)
return
cfg


Argument parser of bitcoin - conf command

def subparser(subparsers):
    """Argument parser of bitcoin-conf command"""
    p = subparsers.add_parser(
        'bitcoin-conf',
        aliases=['btc'],
        description="Generate Bitcoin configuration file",
        help='Generate Bitcoin configuration file',
        formatter_class=ArgumentDefaultsHelpFormatter)
    p.add_argument('-d',
                   '--datadir',
                   default=None,
                   help='Path to the data directory where the generated '
                   'bitcoin.conf will be saved')
    p.add_argument('--stdout',
                   action='store_true',
                   default=False,
                   help='Print the generated bitcoin.conf file to the '
                   'standard output instead of saving the file')
    p.add_argument('--concat',
                   action='store_true',
                   default=False,
                   help='Concatenate configurations to pre-existing '
                   'bitcoin.conf file')
    p.set_defaults(func=configure)

    return subparser


def configure(args):
    """Generate bitcoin.conf configuration"""
    info = config.read_cfg_file(args.cfg, args.cfg_dir)

    if (info is None):
        return

    if (not args.stdout):
        util.print_header("Bitcoin Conf Generator")

    if args.datadir is None:
        home = os.path.expanduser("~")
        path = os.path.join(home, ".bitcoin")
    else:
        path = os.path.abspath(args.datadir)

    conf_file = "bitcoin.conf"
    abs_path = os.path.join(path, conf_file)

    # Network interface
    ifname = config.get_net_if(info)

    # Generate configuration object
    cfg = _gen_cfgs(info, ifname)

    # Load and concatenate pre-existing configurations
    if args.concat and os.path.exists(abs_path):
        with open(abs_path, "r") as fd:
            prev_cfg_text = fd.read()
            cfg.load_text_cfg(prev_cfg_text)

    # Export configurations to text format
    cfg_text = cfg.text()

    # Print configurations to stdout and don't save them
    if (args.stdout):
        print(cfg_text)
        return

    # Proceed to saving configurations
    print("Save {} at {}".format(conf_file, path))

    if (not util.ask_yes_or_no("Proceed?")):
        print("Aborted")
        return

    if not os.path.exists(path):
        os.makedirs(path)

    if os.path.exists(abs_path) and not args.concat:
        if (not util.ask_yes_or_no("File already exists. Overwrite?")):
            print("Aborted")
            return

    with open(abs_path, "w") as fd:
        fd.write(cfg_text)

    print("Saved")

    if (info['setup']['type'] == defs.linux_usb_setup_type):
        print()
        util.fill_print(
            "NOTE: {} was configured assuming the dvbnet interface is "
            "named {}. You can check if this is the case after launching the "
            "receiver by running:".format(conf_file, ifname))
        print("    ip link show | grep dvb\n")
        util.fill_print("If the dvb interfaces are numbered differently, "
                        "please update {} accordingly.".format(conf_file))
    elif (info['setup']['type'] == defs.standalone_setup_type):
        print("\n" + textwrap.fill(
            ("NOTE: {0} was configured assuming the Novra S400 receiver will "
             "be connected to interface {1}. If this is not the case anymore, "
             "please update {0} accordingly.").format(conf_file, ifname)))


Add key - value pair to configuration dictionary

def add_opt(self, key, val):
        """Add key-value pair to configuration dictionary"""

        # The options that appear more than once become lists on the
        # resulting dictionary.
        if (key in self.cfg):
            # If this key is not a list yet in the dictionary, make it a list
            if (not isinstance(self.cfg[key], list)):
                if (val == self.cfg[key]):
                    return  # identical value found

                new_val = list()
                new_val.append(self.cfg[key])
                new_val.append(val)
                self.cfg[key] = new_val
            else:
                if (val in self.cfg[key]):
                    return  # identical value found

                self.cfg[key].append(val)
        else:
            self.cfg[key] = val


def load_text_cfg(self, text):
        """Load configuration from text"""
        for line in text.splitlines():
            key = line.split("=")[0]
            val = line.split("=")[1]
            self.add_opt(key, val)

def load_text_cfg ( self , text ) : for line in text . splitlines ( ) : key = line . split ( "=" ) [ 0 ] val = line . split ( "=" ) [ 1 ] self . add_opt ( key , val )


def text(self):
        """Export configuration to text version"""
        text = ""
        for k in self.cfg:
            if (isinstance(self.cfg[k], list)):
                for e in self.cfg[k]:
                    text += k + "=" + e + "\n"
            else:
                text += k + "=" + self.cfg[k] + "\n"
        return text

def text ( self ) : text = "" for k in self . cfg : if ( isinstance ( self . cfg [ k ] , list ) ) : for e in self . cfg [ k ] : text += k + "=" + e + "\n" else : text += k + "=" + self . cfg [ k ] + "\n" return text


def _parse_address(user_info, arg_addr):
    """Parse the receiver's IP address"""
    # If the command-line address argument is defined, prioritize it over the
    # address configured on the JSON config file
    if (arg_addr is not None):
        raw_addr = arg_addr
    elif "rx_ip" in user_info["setup"]:
        raw_addr = user_info["setup"]["rx_ip"]
    else:
        raw_addr = defs.default_standalone_ip_addr

    try:
        validated_addr = ip_address(raw_addr)
    except ValueError:
        logger.error("{} is not a valid IPv4 address".format(raw_addr))
        return
    return str(validated_addr)


def cfg_standalone(args):
    """Configure the standalone receiver and the host

    Set all parameters required on the standalone receiver: signal, LNB, and
    MPE parameters. Then, configure the host to communicate with the the
    standalone DVB-S2 receiver by setting reverse-path filters and firewall
    configurations.

    """
    user_info = config.read_cfg_file(args.cfg, args.cfg_dir)
    if (user_info is None):
        return

    # Configure the subprocess runner
    runner.set_dry(args.dry_run)

    # IP Address
    rx_ip_addr = _parse_address(user_info, args.address)

    if (not args.rx_only):
        if 'netdev' not in user_info['setup']:
            assert(args.interface is not None), \
                ("Please specify the network interface through option "
                 "\"-i/--interface\"")

        interface = args.interface if (args.interface is not None) else \
            user_info['setup']['netdev']

        # Check if all dependencies are installed
        if (not dependencies.check_apps(["iptables"])):
            return

        rp.set_filters([interface], prompt=(not args.yes), dry=args.dry_run)
        firewall.configure([interface],
                           defs.src_ports,
                           user_info['sat']['ip'],
                           igmp=True,
                           prompt=(not args.yes),
                           dry=args.dry_run)

    if (not args.host_only):
        util.print_header("Receiver Configuration")
        s400 = S400Client(args.demod, rx_ip_addr, args.port, dry=args.dry_run)
        s400.configure(user_info)


Monitor the standalone DVB - S2 receiver

def monitor(args):
    """Monitor the standalone DVB-S2 receiver"""

    # User info
    user_info = config.read_cfg_file(args.cfg, args.cfg_dir)
    if (user_info is None):
        return

    # IP Address
    rx_ip_addr = _parse_address(user_info, args.address)

    # Client to the S400's SNMP agent
    s400 = S400Client(args.demod, rx_ip_addr, args.port)

    util.print_header("Novra S400 Receiver")

    if (not s400.print_demod_config()):
        logger.error("s400 receiver at {} is unreachable".format(s400.address))
        return

    # Log Monitoring
    monitor = monitoring.Monitor(args.cfg_dir,
                                 logfile=args.log_file,
                                 scroll=args.log_scrolling,
                                 min_interval=args.log_interval,
                                 server=args.monitoring_server,
                                 port=args.monitoring_port,
                                 report=args.report,
                                 report_opts=monitoring.get_report_opts(args),
                                 utc=args.utc)

    util.print_header("Receiver Monitoring")

    # Fetch the receiver stats periodically
    c_time = time.time()
    while (True):
        try:
            stats = s400.get_stats()

            if (stats is None):
                return

            monitor.update(stats)

            next_print = c_time + args.log_interval
            if (next_print > c_time):
                time.sleep(next_print - c_time)
            c_time = time.time()

        except KeyboardInterrupt:
            break

def
monitor
(
args
)
:
# User info
user_info
=
config
.
read_cfg_file
(
args
.
cfg
,
args
.
cfg_dir
)
if
(
user_info
is
)
:
return
# IP Address
rx_ip_addr
=
_parse_address
(
user_info
,
args
.
address
)
# Client to the S400's SNMP agent
s400
=
S400Client
(
args
.
demod
,
rx_ip_addr
,
args
.
port
)
util
.
print_header
(
"Novra S400 Receiver"
)
if
(
not
s400
.
print_demod_config
(
)
)
:
logger
.
error
(
"s400 receiver at {} is unreachable"
.
format
(
s400
.
address
)
)
return
# Log Monitoring
monitor
=
monitoring
.
Monitor
(
args
.
cfg_dir
,
logfile
=
args
.
log_file
,
scroll
=
args
.
log_scrolling
,
min_interval
=
args
.
log_interval
,
server
=
args
.
monitoring_server
,
port
=
args
.
monitoring_port
,
report
=
args
.
report
,
report_opts
=
monitoring
.
get_report_opts
(
args
)
,
utc
=
args
.
utc
)
util
.
print_header
(
"Receiver Monitoring"
)
# Fetch the receiver stats periodically
c_time
=
time
.
time
(
)
while
(
True
)
:
try
:
stats
=
s400
.
get_stats
(
)
if
(
stats
is
)
:
return
monitor
.
update
(
stats
)
next_print
=
c_time
+
args
.
log_interval
if
(
next_print
>
c_time
)
:
time
.
sleep
(
next_print
c_time
)
c_time
=
time
.
time
(
)
except
KeyboardInterrupt
:
break


Re - create argparse s help menu for the standalone command

def print_help(args):
    """Re-create argparse's help menu for the standalone command"""
    parser = ArgumentParser()
    subparsers = parser.add_subparsers(title='', help='')
    parser = subparser(subparsers)
    print(parser.format_help())

def print_help ( args ) : parser = ArgumentParser ( ) subparsers = parser . add_subparsers ( title = '' , help = '' ) parser = subparser ( subparsers ) print ( parser . format_help ( ) )


def __init__(self, address, port, mib, dry=False):
        """Constructor

        Args:
            address : SNMP agent's IP address
            port    : SNMP agent's port
            mib     : Target SNMP MIB
            dry     : Dry run mode

        """
        assert (ip_address(address))  # parse address
        self.address = address
        self.port = port
        self.mib = mib
        self.dry = dry
        self.engine = SnmpEngine()
        self._dump_mib()

def
__init__
(
self
,
address
,
port
,
mib
,
dry
=
False
)
:
assert
(
ip_address
(
address
)
)
# parse address
self
.
address
=
address
self
.
port
=
port
self
.
mib
=
mib
self
.
dry
=
dry
self
.
engine
=
SnmpEngine
(
)
self
.
_dump_mib
(
)


def _dump_mib(self):
        """Generate the compiled (.py) MIB file"""

        # Check if the compiled MIB (.py file) already exists
        home = util.get_home_dir()
        self.mib_dir = mib_dir = os.path.join(home, ".pysnmp/mibs/")
        if (os.path.exists(os.path.join(mib_dir, self.mib + ".py"))):
            return

        cli_dir = os.path.dirname(os.path.abspath(__file__))
        mib_path = os.path.join(cli_dir, "mib")
        cmd = ["mibdump.py", "--mib-source={}".format(mib_path), self.mib]
        runner.run(cmd)


def _get(self, *variables):
        """Get one or more variables via SNMP

        Args:
            Tuple with the variables to fetch via SNMP.

        Returns:
            List of tuples with the fetched keys and values.

        """
        obj_types = []
        for var in variables:
            if isinstance(var, tuple):
                obj = ObjectType(
                    ObjectIdentity(self.mib, var[0],
                                   var[1]).addMibSource(self.mib_dir))
            else:
                obj = ObjectType(
                    ObjectIdentity(self.mib, var,
                                   0).addMibSource(self.mib_dir))
            obj_types.append(obj)

        errorIndication, errorStatus, errorIndex, varBinds = next(
            getCmd(self.engine, CommunityData('public'),
                   UdpTransportTarget((self.address, self.port)),
                   ContextData(), *obj_types))

        if errorIndication:
            logger.error(errorIndication)
        elif errorStatus:
            logger.error(
                '%s at %s' %
                (errorStatus.prettyPrint(),
                 errorIndex and varBinds[int(errorIndex) - 1][0] or '?'))
        else:
            res = list()
            for varBind in varBinds:
                logger.debug(' = '.join([x.prettyPrint() for x in varBind]))
                res.append(tuple([x.prettyPrint() for x in varBind]))
            return res

def
_get
(
self
,
*
variables
)
:
obj_types
=
[
]
for
var
in
variables
:
if
isinstance
(
var
,
tuple
)
:
obj
=
ObjectType
(
ObjectIdentity
(
self
.
mib
,
var
[
0
]
,
var
[
1
]
)
.
addMibSource
(
self
.
mib_dir
)
)
else
:
obj
=
ObjectType
(
ObjectIdentity
(
self
.
mib
,
var
,
0
)
.
addMibSource
(
self
.
mib_dir
)
)
obj_types
.
append
(
obj
)
errorIndication
,
errorStatus
,
errorIndex
,
varBinds
=
next
(
getCmd
(
self
.
engine
,
CommunityData
(
'public'
)
,
UdpTransportTarget
(
(
self
.
address
,
self
.
port
)
)
,
ContextData
(
)
,
*
obj_types
)
)
if
errorIndication
:
logger
.
error
(
errorIndication
)
elif
errorStatus
:
logger
.
error
(
'%s at %s'
%
(
errorStatus
.
prettyPrint
(
)
,
errorIndex
and
varBinds
[
int
(
errorIndex
)
1
]
[
0
]
or
'?'
)
)
else
:
res
=
list
(
)
for
varBind
in
varBinds
:
logger
.
debug
(
' = '
.
join
(
[
x
.
prettyPrint
(
)
for
x
in
varBind
]
)
)
res
.
append
(
tuple
(
[
x
.
prettyPrint
(
)
for
x
in
varBind
]
)
)
return
res


def _set(self, *key_vals):
        """Set variable via SNMP

        Args:
            variable : variable to set via SNMP.
            value    : value to set on the given variable.

        """
        if (self.dry):
            # Convert to the corresponding net-snmp command
            for key, val in key_vals:
                # SNMP OID
                if (isinstance(key, tuple)):
                    key = ".".join([str(x) for x in key])
                else:
                    key += ".0"  # scalar values must have a .0 suffix
                # Translate values
                # TODO: Fix s400LOFrequency.0, which is not writable due to
                # being a Counter64 object.
                if ("s400ModulationStandard" in key):
                    if (val in standard_snmp_table):
                        val = standard_snmp_table[val]
                if ("s400Modcod" in key):
                    if (val in modcod_snmp_table):
                        val = modcod_snmp_table[val]
                if ("RowStatus" in key):
                    if (val in snmp_row_status_table):
                        val = snmp_row_status_table[val]
                if (val in ["disable", "enable"]):
                    val = int(val == "enable")
                if (val in ["disabled", "enabled"]):
                    val = int(val == "enabled")
                if (val in ["vertical", "horizontal"]):
                    val = int(val == "horizontal")
                # Value type
                if isinstance(val, str):
                    val_type = "s"
                elif (("LBandFrequency" in key)
                      or ("SymbolRate" in key and "SymbolRateAuto" not in key)
                      or ("s400MpePid1Pid" in key)):
                    val_type = "u"
                else:
                    val_type = "i"
                print("> snmpset -v 2c -c private {}:{} {}::{} {} {}".format(
                    self.address, self.port, self.mib, key, val_type, val))
            return

        obj_types = []
        for key, val in key_vals:
            if isinstance(key, tuple):
                obj = ObjectType(
                    ObjectIdentity(self.mib, key[0],
                                   key[1]).addMibSource(self.mib_dir), val)
            else:
                obj = ObjectType(
                    ObjectIdentity(self.mib, key,
                                   0).addMibSource(self.mib_dir), val)
            obj_types.append(obj)

        errorIndication, errorStatus, errorIndex, varBinds = next(
            setCmd(self.engine, CommunityData('private'),
                   UdpTransportTarget((self.address, self.port), timeout=10.0),
                   ContextData(), *obj_types))

        if errorIndication:
            logger.error(errorIndication)
        elif errorStatus:
            logger.error(
                '%s at %s' %
                (errorStatus.prettyPrint(),
                 errorIndex and varBinds[int(errorIndex) - 1][0] or '?'))
        else:
            for varBind in varBinds:
                logger.debug(' = '.join([x.prettyPrint() for x in varBind]))


def _walk(self):
        """SNMP Walk

        Returns:
            Dictionary with configurations fetched via SNMP.

        """
        iterator = nextCmd(
            self.engine, CommunityData('public'),
            UdpTransportTarget((self.address, self.port)), ContextData(),
            ObjectType(
                ObjectIdentity(self.mib, '', 0).addMibSource(self.mib_dir)))
        res = {}
        for errorIndication, errorStatus, errorIndex, varBinds in iterator:
            if errorIndication:
                logger.error(errorIndication)
            elif errorStatus:
                logger.error(
                    '%s at %s' %
                    (errorStatus.prettyPrint(),
                     errorIndex and varBinds[int(errorIndex) - 1][0] or '?'))
            else:
                for varBind in varBinds:
                    logger.debug(varBind)
                    varBindList = [x.prettyPrint() for x in varBind]
                    key = varBindList[0].replace(self.mib + "::", "")
                    if (len(varBindList) > 2):
                        res[key] = tuple(varBindList[1:])
                    else:
                        res[key] = varBindList[1]
        return res

def
_walk
(
self
)
:
iterator
=
nextCmd
(
self
.
engine
,
CommunityData
(
'public'
)
,
UdpTransportTarget
(
(
self
.
address
,
self
.
port
)
)
,
ContextData
(
)
,
ObjectType
(
ObjectIdentity
(
self
.
mib
,
''
,
0
)
.
addMibSource
(
self
.
mib_dir
)
)
)
res
=
{
}
for
errorIndication
,
errorStatus
,
errorIndex
,
varBinds
in
iterator
:
if
errorIndication
:
logger
.
error
(
errorIndication
)
elif
errorStatus
:
logger
.
error
(
'%s at %s'
%
(
errorStatus
.
prettyPrint
(
)
,
errorIndex
and
varBinds
[
int
(
errorIndex
)
1
]
[
0
]
or
'?'
)
)
else
:
for
varBind
in
varBinds
:
logger
.
debug
(
varBind
)
varBindList
=
[
x
.
prettyPrint
(
)
for
x
in
varBind
]
key
=
varBindList
[
0
]
.
replace
(
self
.
mib
+
"::"
,
""
)
if
(
len
(
varBindList
)
>
2
)
:
res
[
key
]
=
tuple
(
varBindList
[
1
:
]
)
else
:
res
[
key
]
=
varBindList
[
1
]
return
res


def get_stats(self):
        """Get demodulator statistics

        Returns:
            Dictionary with the receiver stats following the format expected by
            the Monitor class (from monitor.py), i.e., each dictionary element
            as a tuple "(value, unit)".

        """
        res = self._get('s400SignalLockStatus' + self.demod,
                        's400SignalStrength' + self.demod,
                        's400CarrierToNoise' + self.demod,
                        's400UncorrectedPackets' + self.demod,
                        's400BER' + self.demod)

        if res is None:
            return

        signal_lock_raw = res[0][1]
        signal_raw = res[1][1]
        c_to_n_raw = res[2][1]
        uncorr_raw = res[3][1]
        ber_raw = res[4][1]

        # Parse
        signal_lock = (signal_lock_raw == 'locked')
        stats = {'lock': (signal_lock, None)}

        # Metrics that require locking
        #
        # NOTE: the S400 does not return the signal level if unlocked.
        if (signal_lock):
            level = float('nan') if (signal_raw
                                     == '< 70') else float(signal_raw)
            cnr = float('nan') if (c_to_n_raw == '< 3') else float(c_to_n_raw)
            stats['snr'] = (cnr, "dB")
            stats['level'] = (level, "dBm")
            stats['ber'] = (float(ber_raw), None)
            stats['pkt_err'] = (int(uncorr_raw), None)

        return stats


def print_demod_config(self):
        """Get demodulator configurations via SNMP

        Returns:
            Bool indicating whether the demodulator configurations were printed
            successfully.

        """
        res = self._get(
            's400FirmwareVersion',
            # Demodulator
            's400ModulationStandard' + self.demod,
            's400LBandFrequency' + self.demod,
            's400SymbolRate' + self.demod,
            's400Modcod' + self.demod,
            # LNB
            's400LNBSupply',
            's400LOFrequency',
            's400Polarization',
            's400Enable22KHzTone',
            's400LongLineCompensation',
            # MPE
            ('s400MpePid1Pid', 0),
            ('s400MpePid1Pid', 1),
            ('s400MpePid1RowStatus', 0),
            ('s400MpePid1RowStatus', 1))

        if (res is None):
            return False

        # Form dictionary with the S400 configs
        cfg = {}
        for res in res:
            key = res[0].replace('NOVRA-s400-MIB::s400', '')
            val = res[1]
            cfg[key] = val

        # Map dictionary to more informative labels
        demod_label_map = {
            'ModulationStandard' + self.demod + '.0': "Standard",
            'LBandFrequency' + self.demod + '.0': "L-band Frequency",
            'SymbolRate' + self.demod + '.0': "Symbol Rate",
            'Modcod' + self.demod + '.0': "MODCOD",
        }
        lnb_label_map = {
            'LNBSupply.0': "LNB Power Supply",
            'LOFrequency.0': "LO Frequency",
            'Polarization.0': "Polarization",
            'Enable22KHzTone.0': "22 kHz Tone",
            'LongLineCompensation.0': "Long Line Compensation"
        }
        mpe_label_map = {
            'MpePid1Pid.0': "MPE PID",
            'MpePid1RowStatus.0': "MPE PID Status"
        }
        label_map = {
            "Demodulator": demod_label_map,
            "LNB Options": lnb_label_map,
            "MPE Options": mpe_label_map
        }

        print("Firmware Version: {}".format(cfg['FirmwareVersion.0']))
        for map_key in label_map:
            print("{}:".format(map_key))
            for key in cfg:
                if key in label_map[map_key]:
                    label = label_map[map_key][key]
                    if (label == "MODCOD"):
                        val = "VCM" if cfg[key] == "31" else cfg[key]
                    elif (label == "Standard"):
                        val = cfg[key].upper()
                    else:
                        val = cfg[key]
                    print("- {}: {}".format(label, val))
        return True


def configure(self, info):
        """Configure the S400"""
        logger.info("Configuring the S400 receiver at {} via SNMP".format(
            self.address))

        # Local parameters
        l_band_freq = int(info['freqs']['l_band'] * 1e6)
        lo_freq = int(info['freqs']['lo'] * 1e6)
        sym_rate = defs.sym_rate[info['sat']['alias']]
        if (info['lnb']['pol'].lower() == "dual"
                and 'v1_pointed' in info['lnb'] and info['lnb']['v1_pointed']):
            pol = "horizontal" if (info['lnb']["v1_psu_voltage"] >= 16) else \
                "vertical"
        else:
            pol = "horizontal" if (info['sat']['pol'] == "H") else "vertical"
        if (info['lnb']['universal']
                and info['freqs']['dl'] > defs.ku_band_thresh):
            tone = 'enable'
        else:
            tone = 'disable'

        # Fetch and delete the current MPE PIDs
        current_cfg = self._walk()
        mpe_prefix = 's400MpePid' + self.demod
        has_mpe_table = any(
            [mpe_prefix + "RowStatus" in key for key in current_cfg])
        if (has_mpe_table):
            logger.info("Resetting MPE PIDs")
        for key in current_cfg:
            mpe_row_status = mpe_prefix + "RowStatus"
            if mpe_row_status in key:
                self._set((tuple(key.split(".")), 'destroy'))

        # Configure via SNMP
        logger.info("Configuring interface RF{}".format(self.demod))
        self._set(('s400ModulationStandard' + self.demod, 'dvbs2'))
        self._set(('s400LBandFrequency' + self.demod, l_band_freq))
        self._set(('s400SymbolRate' + self.demod, sym_rate))
        self._set(('s400SymbolRateAuto' + self.demod, 'disable'))

        logger.info("Setting LNB parameters")
        self._set(('s400LOFrequency', lo_freq))
        self._set(('s400Polarization', pol))
        self._set(('s400LongLineCompensation', 'disable'))

        logger.info("Configuring the RF{} service".format(self.demod))
        self._set(('s400Modcod' + self.demod, 'auto'))
        self._set(('s400ForwardEntireStream' + self.demod, 'disable'))

        # Set the target PIDs
        for i_pid, pid in enumerate(defs.pids):
            logger.info("Adding MPE PID {}".format(pid))
            self._set((('s400MpePid' + self.demod + 'RowStatus', i_pid),
                       'createAndWait'))
            self._set((('s400MpePid' + self.demod + 'Pid', i_pid), pid))
            self._set(
                (('s400MpePid' + self.demod + 'RowStatus', i_pid), 'active'))

        logger.info("Turning on the LNB power")
        self._set(('s400LNBSupply', 'enabled'))
        if (tone == 'enable'):
            logger.info("Enabling the LNB 22kHz tone")
        self._set(('s400Enable22KHzTone', tone))

        if (not self.dry):
            logger.info("Receiver configured successfully")


Find suitable LNB within v4l - utils preset LNBs

def _find_v4l_lnb(info):
    """Find suitable LNB within v4l-utils preset LNBs

    The LNBs on list v4l_lnbs are defined at lib/libdvbv5/dvb-sat.c

    """

    target_lo_freq_raw = info['lnb']['lo_freq']
    target_is_universal = info['lnb']['universal']

    if (target_is_universal):
        assert (isinstance(target_lo_freq_raw, list))
        assert (len(target_lo_freq_raw) == 2)
        target_lo_freq = [int(x) for x in target_lo_freq_raw]
    else:
        target_lo_freq = [int(target_lo_freq_raw)]

    # Find options that match the LO freq
    options = list()
    for lnb in defs.v4l_lnbs:
        is_universal_option = 'highfreq' in lnb

        if (target_is_universal and (not is_universal_option)):
            continue

        if (info['lnb']['pol'].lower() == "dual" and  # user has dual-pol LNB
            (
                info['sat']['pol'] == "H" or  # and satellite requires H pol
                (
                    'v1_pointed' in info['lnb'] and info['lnb']['v1_pointed']
                    and info['lnb']['v1_psu_voltage'] >=
                    16  # or LNB already operates with H pol
                )) and "rangeswitch"
                not in lnb):  # but LNB candidate is single-pol
            continue  # not a validate candidate, skip

        if (target_is_universal):
            if (is_universal_option and lnb['lowfreq'] == target_lo_freq[0]
                    and lnb['highfreq'] == target_lo_freq[1]):
                options.append(lnb)
        else:
            if (lnb['lowfreq'] == target_lo_freq[0]):
                options.append(lnb)

    assert (len(options) > 0), "LNB doesn't match a valid option"
    logger.debug("Matching LNB options: {}".format(pformat(options)))

    return options[0]

def
_find_v4l_lnb
(
info
)
:
target_lo_freq_raw
=
info
[
'lnb'
]
[
'lo_freq'
]
target_is_universal
=
info
[
'lnb'
]
[
'universal'
]
if
(
target_is_universal
)
:
assert
(
isinstance
(
target_lo_freq_raw
,
list
)
)
assert
(
len
(
target_lo_freq_raw
)
==
2
)
target_lo_freq
=
[
int
(
x
)
for
x
in
target_lo_freq_raw
]
else
:
target_lo_freq
=
[
int
(
target_lo_freq_raw
)
]
# Find options that match the LO freq
options
=
list
(
)
for
lnb
in
defs
.
v4l_lnbs
:
is_universal_option
=
'highfreq'
in
lnb
if
(
target_is_universal
and
(
not
is_universal_option
)
)
:
continue
if
(
info
[
'lnb'
]
[
'pol'
]
.
lower
(
)
==
"dual"
and
# user has dual-pol LNB
(
info
[
'sat'
]
[
'pol'
]
==
"H"
or
# and satellite requires H pol
(
'v1_pointed'
in
info
[
'lnb'
]
and
info
[
'lnb'
]
[
'v1_pointed'
]
and
info
[
'lnb'
]
[
'v1_psu_voltage'
]
>=
16
# or LNB already operates with H pol
)
)
and
"rangeswitch"
not
in
lnb
)
:
# but LNB candidate is single-pol
continue
# not a validate candidate, skip
if
(
target_is_universal
)
:
if
(
is_universal_option
and
lnb
[
'lowfreq'
]
==
target_lo_freq
[
0
]
and
lnb
[
'highfreq'
]
==
target_lo_freq
[
1
]
)
:
options
.
append
(
lnb
)
else
:
if
(
lnb
[
'lowfreq'
]
==
target_lo_freq
[
0
]
)
:
options
.
append
(
lnb
)
assert
(
len
(
options
)
>
0
)
,
"LNB doesn't match a valid option"
logger
.
debug
(
"Matching LNB options: {}"
.
format
(
pformat
(
options
)
)
)
return
options
[
0
]