Added iperf and netperf to DUTs, cleaned modems

BUG=chromium:272493
TEST=run_all_tests.py

Change-Id: Iec34502f4a8b8cfec2cac8458350556566710b4b
Reviewed-on: https://chromium-review.googlesource.com/182871
Reviewed-by: Ben Chan <benchan@chromium.org>
Tested-by: Byron Kubert <byronk@chromium.org>
Commit-Queue: Byron Kubert <byronk@chromium.org>
diff --git a/self_tests/unit_tests_software/test_network_data_source.py b/self_tests/unit_tests_software/test_network_data_source.py
index a3da097..6939011 100644
--- a/self_tests/unit_tests_software/test_network_data_source.py
+++ b/self_tests/unit_tests_software/test_network_data_source.py
@@ -61,4 +61,8 @@
         """
         config = network_data_source.NetworkDataSource.get_default_config()
         with LocalIperf(config) as iperf:
-            network_data_source.NetworkDataSource(iperf.config)
+            nds = network_data_source.NetworkDataSource(iperf.config)
+            results = nds.run_iperf_download_test()
+            assert results is not None
+            print 'results:'
+            print (results)
diff --git a/self_tests/unit_tests_with_hardware/test_chrome_dut.py b/self_tests/unit_tests_with_hardware/test_chrome_dut.py
new file mode 100644
index 0000000..19e22ee
--- /dev/null
+++ b/self_tests/unit_tests_with_hardware/test_chrome_dut.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Testing the chrome dut object, with real hardware
+"""
+
+from wireless_automation.aspects import wireless_automation_error
+from wireless_automation.duts import chrome_dut
+
+import unittest
+
+
+class test_chrome_dut(unittest.TestCase):
+    """
+    Test the chrome dut with real hardware
+    """
+    def setUp(self):
+        # Build a DUT
+        dut_config = chrome_dut.ChromeDut.get_default_config()
+        self.dut = chrome_dut.ChromeDut(dut_config, low_level_modem_driver=None)
+
+    def test_localhost_iperf_with_no_server_raises_connection_failure(self):
+        with self.assertRaises(wireless_automation_error.ConnectionFailure):
+            results = self.dut.run_iperf_throughput_test('10.0.0.1')
+
+    def test_localhost_iperf_with_server_returns_high_throughput(self):
+        self.dut.start_local_iperf_server()
+        results = self.dut.run_iperf_throughput_test('localhost',
+                                                     transfer_seconds=3)
+        assert results > 100
diff --git a/self_tests/unittests/test_chrome_device_factory.py b/self_tests/unittests/test_chrome_device_factory.py
deleted file mode 100644
index c65c9a9..0000000
--- a/self_tests/unittests/test_chrome_device_factory.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-# A stub file to remind us to write this class.
-
-#from wireless_automation.chrome_device import chrome_device_factory
-#from wireless_automation.chrome_device import chrome_device_base
-#import unittest
-#
-#
-#class test_DeviceFactory(unittest.TestCase):
-#    def setUp(self):
-#        pass
-#
-#    def tearDown(self):
-#        pass
-#
-#    def test_make_one(self):
-#        config = chrome_device_factory.DutFactory.get_default_config()
-#        cd = chrome_device_factory.DutFactory(config)
-#        assert isinstance(cd, chrome_device_base.DutInterface)
-#
-#    def _bad_config_breaks(self):
-#        config = chrome_device_factory.DutFactory.get_default_config()
-#        config['bad_value'] = 'here'
-#        cd = chrome_device_factory.DutFactory(config)
diff --git a/wireless_automation/chrome_device/chrome_device_factory.py b/wireless_automation/chrome_device/chrome_device_factory.py
deleted file mode 100644
index 310f3ce..0000000
--- a/wireless_automation/chrome_device/chrome_device_factory.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""
-How to build the right dut
-"""
-
-from wireless_automation.aspects.wireless_automation_logging import \
-    setup_logging
-from wireless_automation.chrome_device import chrome_device_base
-from wireless_automation.chrome_device.modems import modem_factory
-
-
-class ChromeDeviceFactory(object):
-    """
-    TODO:
-    A place holder, this class should return an object composed
-    of all the objected needed to talk to the chromebook systems:
-    shill, modemmanager, the modem direct, open and close switch,
-    power reporting, etc.
-    """
-    log = setup_logging('ChromeDevice')
-
-    def __init__(self):
-        pass
-
-    @classmethod
-    def get_chrome_device(cls):
-        """
-        @param cls: The class.
-        @return: A ChromeDevice with it's inner parts populated
-        """
-        cls.log.info('making new Chrome Device')
-        # Only one choice now.
-        config = chrome_device_base.ChromeDevice.get_default_config()
-        modem = modem_factory.ModemFactory.get_modem()
-        device = chrome_device_base.ChromeDevice(config)
-        device.modem = modem
-        return device
diff --git a/wireless_automation/duts/chrome_devices/__init__.py b/wireless_automation/duts/chrome_devices/__init__.py
deleted file mode 100644
index 55ce177..0000000
--- a/wireless_automation/duts/chrome_devices/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-Chrome device specific code.
-"""
diff --git a/wireless_automation/duts/chrome_devices/modems/__init__.py b/wireless_automation/duts/chrome_devices/modems/__init__.py
deleted file mode 100644
index de1ccfa..0000000
--- a/wireless_automation/duts/chrome_devices/modems/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-Modem code.
-"""
diff --git a/wireless_automation/duts/chrome_devices/modems/gobi3k.py b/wireless_automation/duts/chrome_devices/modems/gobi3k.py
deleted file mode 100644
index b4ecf25..0000000
--- a/wireless_automation/duts/chrome_devices/modems/gobi3k.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""
-A placeholder for a gobi3k modem controller
-"""
diff --git a/wireless_automation/duts/chrome_devices/wifi/__init__.py b/wireless_automation/duts/chrome_devices/wifi/__init__.py
deleted file mode 100644
index bdc9842..0000000
--- a/wireless_automation/duts/chrome_devices/wifi/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-Wifi code.
-"""
diff --git a/wireless_automation/duts/chrome_dut.py b/wireless_automation/duts/chrome_dut.py
new file mode 100644
index 0000000..e91544d
--- /dev/null
+++ b/wireless_automation/duts/chrome_dut.py
@@ -0,0 +1,259 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""
+ChromeOS Device Under Test.
+"""
+
+import subprocess
+
+from wireless_automation.duts import dut_interface
+from wireless_automation.aspects import wireless_automation_logging
+from wireless_automation.aspects import wireless_automation_error
+import os
+
+from wireless_automation.utils import run_shell_command
+
+
+class ChromeDut(dut_interface.DutInterface):
+    """
+    ChromeOS specific dut functions.
+    """
+
+    def __init__(self, config, low_level_modem_driver=None):
+        dut_interface.DutInterface.__init__(self, config)
+        self.log = wireless_automation_logging.setup_logging('ChromeDut')
+        self.modem_driver = low_level_modem_driver
+        self.fake_hardware = config['fake_hardware']
+
+    def cellular_modem_reset(self):
+        """
+        Reset the cellular modem using OS level tools.
+        """
+        cmd = ["modem", "reset"]
+        self._send_shell_command(cmd)
+
+    def cellular_controllers_stop(self):
+        """
+        stop all the daemons that may talk to the modem.
+        Used before controlling the modem with low level tools.
+        """
+        cmd = ["stop", "shill"]
+        self._send_shell_command(cmd)
+
+    def cellular_connect(self, technology='LTE'):
+        """
+        connect the cellular modem using OS level tools
+        """
+        cmd = ["modem", "connect"]
+        self._send_shell_command(cmd)
+
+    def _send_shell_command_list(self, cmds):
+        for cmd in cmds:
+            self.log.info(cmd)
+            self._send_shell_command(cmd)
+
+    def _send_shell_command(self, cmd):
+        """
+        Run a shell command on the dut
+        """
+        self.log.debug(cmd)
+        shell_env = os.environ
+        self.log.info(cmd)
+        command = subprocess.Popen(cmd,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE,
+                                   env=shell_env)
+        (std_out, std_err) = command.communicate()
+        self.log.debug(std_out)
+        self.log.debug(std_err)
+
+    def start_local_netperf_server(self, timeout_secs=5):
+        """
+        Start a local netperf server.
+        This is not very robust yet, just used in some unit tests.
+        :timeout: seconds
+        :return:
+        """
+        cmd1 = ['killall', 'netserver']
+        cmd2 = ['timeout', str(timeout_secs), 'netserver']
+        self._send_shell_command_list([cmd1, cmd2])
+
+    def start_local_iperf_server(self, port=5001, timeout_secs=5):
+        """
+        Start a local iperf server.
+        This is not very robust yet, just used in some unit tests.
+        :param port: port number, default=5001
+        :return:
+        """
+        cmd1 = ['killall', '-9', 'iperf']  # -9 because iperf hangs
+        cmd2 = ['timeout', str(timeout_secs), 'iperf',
+                '--daemon', '-s', '--port', str(port),
+                '2>/dev/null',
+                '1>/dev/null', '&']
+        for cmd in [cmd1, cmd2]:
+            self.log.info(cmd)
+            # This hangs for an unkown reason.
+            #self._send_shell_command_list([cmd1, cmd2])
+            # Use os.system until the CL with utils/run_shell_command lands
+            os.system(' '.join(cmd))
+
+    def parse_netperf_output(self, out_str):
+        out = out_str.rstrip()
+        out = out.split()
+        if len(out) < 5:
+            msg = ('Netperf returned a too short result : %s ' % out)
+            self.log.error(msg)
+            raise wireless_automation_error.BadState(msg)
+        try:
+            results = {"rx_socket_bytes": float(out[0]),
+                       "tx_socket_bytes": float(out[1]),
+                       "message_size_bytes": float(out[2]),
+                       "time": float(out[3]),
+                       "Mbits_sec": float(out[4]),
+            }
+        except ValueError:
+            self.log.info('netperf results : "%s" ' % out_str)
+            mesg = 'Netperf test returned non floats as values'
+            self.log.error(mesg)
+            raise wireless_automation_error.ConnectionFailure(mesg)
+        if results['Mbits_sec'] == '0':
+            mesg = ('Netperf test returned 0 bytes transferred.'
+                    ' Maybe the netperf server was down?')
+            self.log.error(mesg)
+            raise wireless_automation_error.ConnectionFailure(mesg)
+        return results
+
+    def _check_throughput_params(self, direction):
+        valid = ['uplink', 'downlink']
+        if direction not in valid:
+            msg = "netperf test can not run direction = '%s', only %s " % \
+                  (direction, valid)
+            raise wireless_automation_error.BadExternalConfig(msg)
+
+    def run_netperf_throughput_test(self, server_ip, port=12865,
+                                    transfer_seconds=10, direction='uplink'):
+        """
+        Run a netperf speed test.
+        :param server_ip:
+        :param port: port to use
+        :param transfer_seconds: seconds to run the test
+        :param direction:  uplink or downlink
+        :return: Mega bits per second for the direction specified.
+        """
+        self._check_throughput_params(direction)
+
+        if direction == 'uplink':
+            netperf_test = 'TCP_STREAM'
+        else:
+            netperf_test = 'TCP_MAERTS'
+        self._ping_host_or_raise(server_ip)
+
+        if self.fake_hardware:
+            self.log.info('Fake running a download test..')
+            return 123.456
+
+        self.log.info('Starting the download test..')
+        cmd = ["netperf", server_ip,
+               "-P", "0",  # Don't print any headers in the output
+               "-l", str(transfer_seconds),
+               "-t", str(netperf_test),
+               "-p", str(port)
+               ]
+        self.log.info(' '.join(cmd))
+        netperf = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+        #  communicate() blocks until there are results
+        (out_str, out_e) = netperf.communicate()
+        self.log.info('netperf stderr: %s ' % out_e)
+        results = self.parse_netperf_output(out_str)
+
+        self.log.info('Netperf %s Mbps: %s' % (direction, results['Mbits_sec']))
+        return results['Mbits_sec']
+
+    def _parse_iperf_output(self, direction, out, transfer_seconds):
+        out = out.rstrip()
+        out = out.split(',')
+        if len(out) < 8:
+            mesg = 'iperf returned too short of a result : %s ' % out
+            self.log.error(mesg)
+            raise wireless_automation_error.ConnectionFailure(mesg)
+        results = {"date": out[0],
+                   "source_ip": out[1],
+                   "source_port": out[2],
+                   "dest_ip": out[3],
+                   "dest_port": out[4],
+                   "id": out[5],
+                   "seconds": out[6],
+                   "bytes": out[7],
+                   "bytes_per_second": out[8],
+                   }
+        if results['bytes'] == '0':
+            mesg = ('Iperf test returned 0 bytes transferred.'
+                    ' Maybe the iperf server was down?')
+            self.log.error(mesg)
+            raise wireless_automation_error.ConnectionFailure(mesg)
+        # Calc the rate. Sometimes iperf doesn't return the
+        # last element in the list, but we seem to have bytes
+        # and seconds all the time.
+        bits_per_second = 8 * float(results['bytes']) / transfer_seconds
+        mega_bits_per_seconds = bits_per_second / 1e6
+        self.log.info('iperf %s Mbps: %s' % (direction, mega_bits_per_seconds))
+        return mega_bits_per_seconds
+
+    def run_iperf_throughput_test(self, iperf_server_ip, iperf_port=5001,
+                                  direction='uplink', transfer_seconds=10):
+        """
+        Use Iperf to test throughput speeds. This expects there to be
+        a iperf server running at the iperf_server_ip.
+        :param iperf_server_ip: IP of the server that has iperf in server mode
+        :param iperf_port: Port that iperf is using on the server
+        :param bidirectional:
+            How to test both direction['no','serial,'parallel' ]
+        :param transfer_seconds:
+        :return:
+        """
+        self._check_throughput_params(direction)
+        self._ping_host_or_raise(iperf_server_ip)
+        if self.fake_hardware:
+            self.log.info('Fake running a download test..')
+            return 123.456
+
+        self.log.info('Starting the download test..')
+        cmd = ["iperf",
+               "--client", iperf_server_ip,
+               "--reportstyle", "C",
+               "--time", str(transfer_seconds),
+               "--port", str(iperf_port)]
+        if direction == 'downlink':
+            cmd.extend(['--tradeoff'])
+
+        self.log.info(' '.join(cmd))
+        shell_cmd = run_shell_command.Command(cmd)
+        shell_cmd.run(timeout=transfer_seconds + 5, shell=False)
+        out = shell_cmd.output
+        mega_bits_per_seconds = self._parse_iperf_output(direction,
+                                                         out,
+                                                         transfer_seconds)
+        return mega_bits_per_seconds
+
+    def _ping_host_or_raise(self, ip_address):
+        """
+        Check if the net/iperf host is reachable.
+        :return: Raises an exception if ping can't connect
+        """
+        if self.fake_hardware:
+            return
+        cmd = "ping -c 1 -w 2 " + ip_address + " > /dev/null 2>&1"
+        self.log.debug(cmd)
+        if self.fake_hardware:
+            self.log.debug("In Fake mode, don't actually ping")
+            return
+        response = os.system(cmd)
+        #and then check the response...
+        if response == 0:
+            self.log.debug('ping %s worked' % ip_address)
+            return
+        else:
+            self.log.debug('ping %s failed' % ip_address)
+            raise wireless_automation_error.ConnectionFailure(
+                'net/iperf host is not responding to ping: %s ' % ip_address)
diff --git a/wireless_automation/duts/dummy_modem.py b/wireless_automation/duts/dummy_modem.py
deleted file mode 100644
index b347ccb..0000000
--- a/wireless_automation/duts/dummy_modem.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""
-A dummy modem class, to provide modem functionality on workstations that
-have no modems.
-"""
-
-from wireless_automation.duts import modem_interface
-
-
-class DummyModem(modem_interface.ModemInterface):
-    """
-    A dummy modem class.
-
-    """
-    CONFIGSPEC = ['name = string(default="DummyMode")']
-
-    def __init__(self, config):
-        """
-        @param config: a Config object.
-        """
-        super(DummyModem, self).__init__(config)
-
-    def power_on(self):
-        """
-        Turn on a powered modem.
-        """
-        pass
-
-    def power_off(self):
-        """
-        Turn off the modem, but do not disconnect the power lines.
-        """
-        pass
-
-    def register(self):
-        """
-        Register with the network.
-        """
-        pass
-
-    def deregister(self):
-        """
-        Deregister with the network.
-        """
-        pass
-
-    def connect(self):
-        """
-        Connect
-        """
-
-        pass
-
-    def disconnect(self):
-        """
-        Disconnect from the network.
-        """
-        pass
-
-    def go_to_low_power(self):
-        """
-        Put the modem to sleep.
-        @return:
-        """
-        pass
-
-    def hard_power_cycle(self, block=True):
-        """
-        Powers off the modem by killing the power lines to it.
-        Or as close to that as possible. This should be a
-        the hardest reset available. Then powers the modem back up.
-        This blocks until the modem is responsive.
-
-        @block: return only after the modem responsive
-        """
-        pass
-
-    def is_modem_there(self):
-        """
-        Can the modem be reached. If the modem is off or
-        non responsive, returns a False.
-        @return: Boolean.
-        """
-        pass
-
-    def config_for_pxt(self):
-        """
-        Configs the modem to talk to the PXT call box.
-        """
-        pass
diff --git a/wireless_automation/duts/dut_factory.py b/wireless_automation/duts/dut_factory.py
deleted file mode 100644
index 1a2d640..0000000
--- a/wireless_automation/duts/dut_factory.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""
-How to build the right dut
-"""
-
-from wireless_automation.aspects import configurable
-from wireless_automation.aspects.wireless_automation_logging import \
-    setup_logging
-from wireless_automation.duts import dut_interface
-from wireless_automation.duts.chrome_devices.modems import modem_factory
-
-
-class DutFactory(configurable.Configurable):
-    """
-    Composes a DUT from the parts specified in the config.
-    """
-    CONFIGSPEC = \
-        ['type=option("chrome","android","iOS",default="chrome")'] + \
-        configurable.nest_configspecs([
-            ('DutInterface', dut_interface.DutInterface)
-        ])
-    log = setup_logging('DutInterface')
-
-    def __init__(self, config):
-        super(DutFactory, self).__init__(config)
-
-    @classmethod
-    def get_dut(cls):
-        """
-        @param cls: The class.
-        @return: A DutInterface with it's inner parts populated
-        """
-        cls.log.info('making new Chrome Device')
-        # Only one choice now.
-        config = dut_interface.DutInterface.get_default_config()
-        modem = modem_factory.ModemFactory.get_modem()
-        device = dut_interface.DutInterface(config)
-        device.modem = modem
-        return device
-
-
-def build_one(config):
-    """
-    Dut factory function.
-    @param config:
-    @return:
-    """
-    factory = DutFactory(config)
-    return factory.get_dut()
diff --git a/wireless_automation/duts/dut_interface.py b/wireless_automation/duts/dut_interface.py
index 0fe277c..6c935c2 100644
--- a/wireless_automation/duts/dut_interface.py
+++ b/wireless_automation/duts/dut_interface.py
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """
-Base class for all chrome_devices
+Base class for all modem_drivers
 """
 
 from wireless_automation.aspects import configurable
@@ -13,21 +13,52 @@
     Interface definition for Devices Under Test. Could be Chromebooks,
     could be iOS phones, could be Android devices.
     """
-    CONFIGSPEC = ['modem_interface=option("altair","dummy",default="dummy")']
+    CONFIGSPEC = ['fake_hardware=boolean(default=False)']
 
-    def __init__(self, config):
+    def __init__(self, config, low_level_modem_driver=None):
         """
-        @param config:  Configurable object.
-        @return: DutInterface
+        :param config:  ConfigObj that contains setup info
+        :param low_level_modem_driver: driver to control the
+            modem directly. Using this is optional, the dut
+            has methods to control the modem. The driver is
+            needed only when these methods are not detailed
+            enough. The method stop
+        :return:
         """
         super(DutInterface, self).__init__(config)
-        self.modem = None
+        self.modem = low_level_modem_driver
+        self.fake_hardware = config['fake_hardware']
 
-    @staticmethod
-    def measure_download_speed(url):
+    def cellular_connect(self, technology):
         """
-        @param url:  Where to download from.
-        @return: Speed of the download
+        Instruct the dut to connect it's cellular system
+        to the specified technology.
+        This is not a command given directly to the modem.
+        This instruct the dut to use it's normal methods
+        to establish a connection. In ChromeOS, this would
+        be done by shill.
+        :param technology:
+        :return:
         """
-        str(url)
-        return 1
+        raise NotImplementedError
+
+    def cellular_controllers_stop(self):
+        """
+        Turn off any software that may try to control the modem.
+        Use this before using any modem driver.
+        For example: in ChromeOS this stops Shill and ModemManager.
+        """
+        raise NotImplementedError
+
+    def run_iperf_throughput_test(self, iperf_server_ip, iperf_port=5001,
+                                  direction='uplink', seconds=10):
+        """
+        Run one iperf test from the device we are on to
+        the iperf server running on iperf_server_ip.
+        This assumes the server is ready and waiting.
+
+        :param iperf_server_ip: Address of server
+        :param seconds: seconds to run the test.
+        :return: a dict of results.
+        """
+        raise NotImplementedError
diff --git a/wireless_automation/duts/chrome_devices/modems/modem_factory.py b/wireless_automation/duts/modem_factory.py
similarity index 93%
rename from wireless_automation/duts/chrome_devices/modems/modem_factory.py
rename to wireless_automation/duts/modem_factory.py
index e13694c..78e1a60 100644
--- a/wireless_automation/duts/chrome_devices/modems/modem_factory.py
+++ b/wireless_automation/duts/modem_factory.py
@@ -7,7 +7,7 @@
 """
 
 from wireless_automation.aspects import configurable
-from wireless_automation.duts import dummy_modem
+from wireless_automation.duts.modem_drivers import dummy_modem
 
 
 class ModemFactory(configurable.Configurable):
diff --git a/wireless_automation/duts/modem_interface.py b/wireless_automation/duts/modem_interface.py
deleted file mode 100644
index f0039d5..0000000
--- a/wireless_automation/duts/modem_interface.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""
-Base class for all modems.
-"""
-
-from wireless_automation.aspects import configurable
-
-
-class ModemInterface(configurable.Configurable):
-    """
-    A base class for controlling the modem.
-
-    """
-
-    def __init__(self, config):
-        """
-        @param config: Config object.
-        """
-        super(ModemInterface, self).__init__(config)
-
-    def power_on(self):
-        """
-        Turn on a powered modem.
-        """
-        raise NotImplementedError
-
-    def power_off(self):
-        """
-        Turn off the modem, but do not disconnect the power lines.
-        """
-        raise NotImplementedError
-
-    def register(self):
-        """
-        Register with the network.
-        """
-        raise NotImplementedError
-
-    def deregister(self):
-        """
-        Deregister with the network.
-        """
-        raise NotImplementedError
-
-    def connect(self):
-        """
-        Connect
-        """
-
-        raise NotImplementedError
-
-    def disconnect(self):
-        """
-        Disconnect from the network.
-        """
-        raise NotImplementedError
-
-    def go_to_low_power(self):
-        """
-        Put the modem to sleep.
-        @return:
-        """
-        raise NotImplementedError
-
-    def hard_power_cycle(self, block=True):
-        """
-        Powers off the modem by killing the power lines to it.
-        Or as close to that as possible. This should be a
-        the hardest reset available. Then powers the modem back up.
-        This blocks until the modem is responsive.
-
-        @block: return only after the modem responsive
-        """
-        raise NotImplementedError
-
-    def is_modem_there(self):
-        """
-        Can the modem be reached. If the modem is off or
-        non responsive, returns a False.
-        @return: Boolean.
-        """
-        raise NotImplementedError
-
-    def config_for_pxt(self):
-        """
-        Configs the modem to talk to the PXT call box.
-        """
-        raise NotImplementedError