diff --git a/tests/run-tests b/tests/run-tests
index bd2cbfd029c5f51c2220a4c0754f68f21fd702a9..b4baff2b8393e568b1004603adbc9ca6fdd24571 100755
--- a/tests/run-tests
+++ b/tests/run-tests
@@ -266,17 +266,19 @@ def run_tests(pyb, tests, args):
 def main():
     cmd_parser = argparse.ArgumentParser(description='Run tests for Micro Python.')
     cmd_parser.add_argument('--target', default='unix', help='the target platform')
-    cmd_parser.add_argument('--device', default='/dev/ttyACM0', help='the serial device of the target board')
+    cmd_parser.add_argument('--device', default='/dev/ttyACM0', help='the serial device or the IP address of the pyboard')
+    cmd_parser.add_argument('-b', '--baudrate', default=115200, help='the baud rate of the serial device')
+    cmd_parser.add_argument('-u', '--user', default='micro', help='the telnet login username')
+    cmd_parser.add_argument('-p', '--password', default='python', help='the telnet login password')
     cmd_parser.add_argument('-d', '--test-dirs', nargs='*', help='input test directories (if no files given)')
     cmd_parser.add_argument('--write-exp', action='store_true', help='save .exp files to run tests w/o CPython')
     cmd_parser.add_argument('--emit', default='bytecode', help='Micro Python emitter to use (bytecode or native)')
-    cmd_parser.add_argument('-b', '--baudrate', default=115200, help='the baud rate of the serial device')
     cmd_parser.add_argument('files', nargs='*', help='input test files')
     args = cmd_parser.parse_args()
 
     if args.target == 'pyboard' or args.target == 'wipy':
         import pyboard
-        pyb = pyboard.Pyboard(args.device, args.baudrate)
+        pyb = pyboard.Pyboard(args.device, args.baudrate, args.user, args.password)
         pyb.enter_raw_repl()
     elif args.target == 'unix':
         pyb = None
diff --git a/tools/pyboard.py b/tools/pyboard.py
index 223f3be16f451a596c764c1ee219095879a89752..9157c6058fc9694fa2940bfed1bbeaffef11c03c 100755
--- a/tools/pyboard.py
+++ b/tools/pyboard.py
@@ -10,6 +10,13 @@ Example usage:
 
     import pyboard
     pyb = pyboard.Pyboard('/dev/ttyACM0')
+
+Or:
+
+    pyb = pyboard.Pyboard('192.168.1.1')
+
+Then:
+
     pyb.enter_raw_repl()
     pyb.exec('pyb.LED(1).on()')
     pyb.exit_raw_repl()
@@ -31,7 +38,6 @@ Or:
 
 import sys
 import time
-import serial
 
 def stdout_write_bytes(b):
     sys.stdout.buffer.write(b)
@@ -40,9 +46,76 @@ def stdout_write_bytes(b):
 class PyboardError(BaseException):
     pass
 
+class TelnetToSerial:
+    def __init__(self, ip, user, password, read_timeout=None):
+        import telnetlib
+        self.tn = telnetlib.Telnet(ip, timeout=15)
+        self.read_timeout = read_timeout
+        if b'Login as:' in self.tn.read_until(b'Login as:', timeout=read_timeout):
+            self.tn.write(bytes(user, 'ascii') + b"\r\n")
+
+            if b'Password:' in self.tn.read_until(b'Password:', timeout=read_timeout):
+                # needed because of internal implementation details of the telnet server
+                time.sleep(0.2)
+                self.tn.write(bytes(password, 'ascii') + b"\r\n")
+
+                if b'for more information.' in self.tn.read_until(b'Type "help()" for more information.', timeout=read_timeout):
+                    # login succesful
+                    from collections import deque
+                    self.fifo = deque()
+                    return
+
+        raise PyboardError('Failed to establish a telnet connection with the board')
+
+    def __del__(self):
+        self.close()
+
+    def close(self):
+        try:
+            self.tn.close()
+        except:
+            # the telnet object might not exist yet, so ignore this one
+            pass
+
+    def read(self, size=1):
+        while len(self.fifo) < size:
+            timeout_count = 0
+            data = self.tn.read_eager()
+            if len(data):
+                self.fifo.extend(data)
+                timeout_count = 0
+            else:
+                time.sleep(0.25)
+                if self.read_timeout is not None and timeout_count > 4 * self.read_timeout:
+                    break
+                timeout_count += 1
+
+        data = b''
+        while len(data) < size and len(self.fifo) > 0:
+            data += bytes([self.fifo.popleft()])
+        return data
+
+    def write(self, data):
+        self.tn.write(data)
+        return len(data)
+
+    def inWaiting(self):
+        n_waiting = len(self.fifo)
+        if not n_waiting:
+            data = self.tn.read_eager()
+            self.fifo.extend(data)
+            return len(data)
+        else:
+            return n_waiting
+
 class Pyboard:
-    def __init__(self, serial_device, baudrate=115200):
-        self.serial = serial.Serial(serial_device, baudrate=baudrate, interCharTimeout=1)
+    def __init__(self, device, baudrate=115200, user='micro', password='python'):
+        if device and device[0].isdigit() and device[-1].isdigit() and device.count('.') == 3:
+            # device looks like an IP address
+            self.serial = TelnetToSerial(device, user, password, read_timeout=10)
+        else:
+            import serial
+            self.serial = serial.Serial(device, baudrate=baudrate, interCharTimeout=1)
 
     def close(self):
         self.serial.close()
@@ -155,85 +228,28 @@ class Pyboard:
         t = str(self.eval('pyb.RTC().datetime()'), encoding='utf8')[1:-1].split(', ')
         return int(t[4]) * 3600 + int(t[5]) * 60 + int(t[6])
 
-def execfile(filename, device='/dev/ttyACM0'):
-    pyb = Pyboard(device)
+def execfile(filename, device='/dev/ttyACM0', baudrate=115200, user='micro', password='python'):
+    pyb = Pyboard(device, baudrate, user, password)
     pyb.enter_raw_repl()
     output = pyb.execfile(filename)
     stdout_write_bytes(output)
     pyb.exit_raw_repl()
     pyb.close()
 
-def run_test(device):
-    pyb = Pyboard(device)
-    pyb.enter_raw_repl()
-    print('opened device {}'.format(device))
-
-    pyb.exec('import pyb')  # module pyb no longer imported by default, required for pyboard tests
-    print('seconds since boot:', pyb.get_time())
-
-    pyb.exec('def apply(l, f):\r\n for item in l:\r\n  f(item)\r\n')
-
-    pyb.exec('leds=[pyb.LED(l) for l in range(1, 5)]')
-    pyb.exec('apply(leds, lambda l:l.off())')
-
-    ## USR switch test
-
-    pyb.exec('switch = pyb.Switch()')
-
-    for i in range(2):
-        print("press USR button")
-        pyb.exec('while switch(): pyb.delay(10)')
-        pyb.exec('while not switch(): pyb.delay(10)')
-
-    print('USR switch passed')
-
-    ## accel test
-
-    if True:
-        print("hold level")
-        pyb.exec('accel = pyb.Accel()')
-        pyb.exec('while abs(accel.x()) > 10 or abs(accel.y()) > 10: pyb.delay(10)')
-
-        print("tilt left")
-        pyb.exec('while accel.x() > -10: pyb.delay(10)')
-        pyb.exec('leds[0].on()')
-
-        print("tilt forward")
-        pyb.exec('while accel.y() < 10: pyb.delay(10)')
-        pyb.exec('leds[1].on()')
-
-        print("tilt right")
-        pyb.exec('while accel.x() < 10: pyb.delay(10)')
-        pyb.exec('leds[2].on()')
-
-        print("tilt backward")
-        pyb.exec('while accel.y() > -10: pyb.delay(10)')
-        pyb.exec('leds[3].on()')
-
-        print('accel passed')
-
-    print('seconds since boot:', pyb.get_time())
-
-    pyb.exec('apply(leds, lambda l:l.off())')
-
-    pyb.exit_raw_repl()
-    pyb.close()
-
 def main():
     import argparse
     cmd_parser = argparse.ArgumentParser(description='Run scripts on the pyboard.')
-    cmd_parser.add_argument('--device', default='/dev/ttyACM0', help='the serial device of the pyboard')
+    cmd_parser.add_argument('--device', default='/dev/ttyACM0', help='the serial device or the IP address of the pyboard')
+    cmd_parser.add_argument('-b', '--baudrate', default=115200, help='the baud rate of the serial device')
+    cmd_parser.add_argument('-u', '--user', default='micro', help='the telnet login username')
+    cmd_parser.add_argument('-p', '--password', default='python', help='the telnet login password')
     cmd_parser.add_argument('--follow', action='store_true', help='follow the output after running the scripts [default if no scripts given]')
-    cmd_parser.add_argument('--test', action='store_true', help='run a small test suite on the pyboard')
     cmd_parser.add_argument('files', nargs='*', help='input files')
     args = cmd_parser.parse_args()
 
-    if args.test:
-        run_test(device=args.device)
-
     for filename in args.files:
         try:
-            pyb = Pyboard(args.device)
+            pyb = Pyboard(args.device, args.baudrate, args.user, args.password)
             pyb.enter_raw_repl()
             with open(filename, 'rb') as f:
                 pyfile = f.read()
@@ -251,7 +267,7 @@ def main():
 
     if args.follow or len(args.files) == 0:
         try:
-            pyb = Pyboard(args.device)
+            pyb = Pyboard(args.device, args.baudrate, args.user, args.password)
             ret, ret_err = pyb.follow(timeout=None, data_consumer=stdout_write_bytes)
             pyb.close()
         except PyboardError as er: