import serial, serial.tools.list_ports


class Interface():
    _func_map = {
            'mug_seek':         b'a',
            'mug_get':          b'b',
            'kcup_tray_open':   b'c',
            'kcup_tray_close':  b'd',
            'kcup_load':        b'e',
            'kcup_eject':       b'f',
            'kcup_count':       b'g',
            'brew':             b'h',
            'addin_insert':     b'i'
            }
    def __init__(self, device, mock=False):
        """Open a serial interface and allow interfacing with the Teensy module
        using it. If no device is given (None) it will use the first detected
        serial device."""
        if device == None:
            devices_possible = sorted(serial.tools.list_ports.comports(),
                    key=lambda i: i.name)
            if len(devices_possible) > 0:
                device = devices_possible[0].device
        self.serial = serial.Serial() if not mock else SerialMock()
        self.serial.port = device

    def __enter__(self):
        self.open()
        return self
    def __exit__(self, exception_type, exception_val, exception_tb):
        self.close()
        return False # Don't suppress exceptions

    def open(self):
        self.serial.open()

        # Generate functions from the _func_map
        for name, letter in self._func_map.items():
            # Construct a function matching this call
            def call_serial(*args):
                # Any arguments that are not bytes or lists will be coerced used
                # bytes([arg])
                self.serial.write(bytes([0xCA, 0xFE]))
                self.serial.write(letter)
                for arg in args:
                    if type(arg) != bytes and type(args) != list:
                        arg = bytes([arg])
                    self.serial.write(arg)

                return self.serial.read()

            # Set the new function so it is accessible as interface.call
            setattr(self, name, call_serial)

    def close(self):
        self.serial.close()

class SerialMock():
    def __init__(self, port = None, *args, **kwargs):
        self._opened = False
        self.is_open = False

        self.port = port
    def __enter__(self):
        self.open()
        return self
    def __exit__(self, exception_type, exception_val, exception_tb):
        self.close()
        return False # Don't suppress exceptions

    def open(self):
        if self._opened:
            raise IOError('Tried to re-open a mocked socket');
        else:
            self._opened = True
            self.is_open = True

    def close(self):
        self.is_open = False

    def read(self, n = 1):
        if self.is_open:
            return bytes([0] * n)
        else:
            raise IOError('Tried to read from a closed mocked socket');

    def write(self, b):
        print('SerialMock \'{}\': {}'.format(self.port, b))
        return len(b)

@@ -33,17 +33,16 @@
 _ports%0A%0A
-%0A
 class In
@@ -56,381 +56,8 @@
 ():%0A
-    _func_map = %7B%0A            'mug_seek':         b'a',%0A            'mug_get':          b'b',%0A            'kcup_tray_open':   b'c',%0A            'kcup_tray_close':  b'd',%0A            'kcup_load':        b'e',%0A            'kcup_eject':       b'f',%0A            'kcup_count':       b'g',%0A            'brew':             b'h',%0A            'addin_insert':     b'i'%0A            %7D%0A
     
@@ -881,202 +881,98 @@
     
-    # Generate functions from the _func_map%0A        for name, letter in self._func_map.items():
+def close(self):%0A        self.serial.close()%0A
 %0A    
-        # Construct a function matching this call%0A            def call_serial(*args):%0A        
+def firmware_call(self, letter, *args):%0A
     
@@ -1048,24 +1048,16 @@
         
-        
 # bytes(
@@ -1059,32 +1059,24 @@
 ytes(%5Barg%5D)%0A
-        
         self
@@ -1114,32 +1114,24 @@
 %5D))%0A        
-        
 self.serial.
@@ -1144,24 +1144,16 @@
 letter)%0A
-        
         
@@ -1181,24 +1181,16 @@
         
-        
 if type(
@@ -1243,24 +1243,16 @@
         
-        
 arg = by
@@ -1258,28 +1258,24 @@
 ytes(%5Barg%5D)%0A
-    
             
@@ -1306,32 +1306,24 @@
 g)%0A%0A        
-        
 return self.
@@ -1345,136 +1345,515 @@
     
-        # Set the new function so it is accessible as interface.call%0A            setattr(self, name, call_serial)%0A%0A    def close
+def mug_seek(self, index):%0A        self.firmware_call(b'a', index)%0A%0A    def mug_get(self):%0A        self.firmware_call(b'b')%0A%0A    def kcup_tray_open(self):%0A        self.firmware_call(b'c')%0A%0A    def kcup_tray_close(self):%0A        self.firmware_call(b'd')%0A%0A    def kcup_load(self):%0A        self.firmware_call(b'e')%0A%0A    def kcup_eject(self):%0A        self.firmware_call(b'f')%0A%0A    def kcup_count(self):%0A        self.firmware_call(b'g')%0A%0A    def brew(self):%0A        self.firmware_call(b'h')%0A%0A    def addin_insert
 (sel
@@ -1869,29 +1869,34 @@
    self.
-serial.close(
+frimware_call(b'i'
 )%0A%0Aclass

Fix major code generation bug