diff --git a/dstat_interface/dstat_comm.py b/dstat_interface/dstat_comm.py
index ac58436aae39fa901301d1784cc6664da337b441..4060c30a10f005e8fb23f75b0125220191f9c2a5 100755
--- a/dstat_interface/dstat_comm.py
+++ b/dstat_interface/dstat_comm.py
@@ -26,23 +26,32 @@ import logging
 
 from errors import InputError, VarError
 
+from simulator import SerialSim
+
 logger = logging.getLogger("dstat.comm")
 dstat_logger = logging.getLogger("dstat.comm.DSTAT")
 exp_logger = logging.getLogger("dstat.comm.Experiment")
 
-def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe):
+def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe, simulate=False):
     ser_logger = logging.getLogger("dstat.comm._serial_process")
     
-    ser = delayedSerial(ser_port, baudrate=1000000, timeout=1)
+    if simulate is True:
+        ser_logger.warning("DStat Simulator enabled")
+        ser = SerialSim(ser_port, baudrate=1000000, timeout=1)
+    else:
+        ser = delayedSerial(ser_port, baudrate=1000000, timeout=1)
     ser_logger.info("Reattaching DStat udc")
-    ser.write("!R") # Send restart command
+    # ser.write("!R") # Send restart command
     ser.close()
     
     for i in range(5):
         time.sleep(1) # Give OS time to enumerate
     
         try:
-            ser = delayedSerial(ser_port, baudrate=1000000, timeout=1)
+            if simulate is True:
+                ser = SerialSim(ser_port, baudrate=1000000, timeout=1)
+            else:
+                ser = delayedSerial(ser_port, baudrate=1000000, timeout=1)
             ser_logger.info("Connecting")
             break
         except serial.SerialException:
@@ -60,7 +69,7 @@ def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe):
         else:
             ser.write('V')
             break
-
+    ser_logger.info("Done Init")
     while True:
         # These can only be called when no experiment is running
         if ctrl_pipe.poll(): 
@@ -92,14 +101,15 @@ def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe):
 
 
 class SerialConnection(object):
-    def __init__(self, ser_port):
+    def __init__(self, ser_port, simulate):
         self.proc_pipe_p, self.proc_pipe_c = mp.Pipe(duplex=True)
         self.ctrl_pipe_p, self.ctrl_pipe_c = mp.Pipe(duplex=True)
         self.data_pipe_p, self.data_pipe_c = mp.Pipe(duplex=True)
     
         self.proc = mp.Process(target=_serial_process, args=(ser_port,
                                 self.proc_pipe_c, self.ctrl_pipe_c,
-                                self.data_pipe_c))
+                                self.data_pipe_c, simulate))
+        logger.info("Process: {}".format(self.proc))
         self.proc.start()
         
 
@@ -150,7 +160,7 @@ class VersionCheck:
         finally:
             return status
 
-def version_check(ser_port):
+def version_check(ser_port, simulate=False):
     """Tries to contact DStat and get version. Returns a list of
     [(major, minor), serial instance]. If no response, returns empty tuple.
         
@@ -159,7 +169,7 @@ def version_check(ser_port):
     """
     try:        
         global serial_instance
-        serial_instance = SerialConnection(ser_port)
+        serial_instance = SerialConnection(ser_port, simulate)
         
         serial_instance.proc_pipe_p.send(VersionCheck())
         result = serial_instance.proc_pipe_p.recv()
@@ -216,11 +226,11 @@ class Settings:
                 dstat_logger.debug(line.lstrip().rstrip())
                 self.ser.flushInput()
                 break
-                
+           
         parted = input.rstrip().split(':')
         
-        for i in range(len(parted)):
-            settings[parted[i].split('.')[0]] = [i, parted[i].split('.')[1]]
+        for n, i in enumerate(parted):
+            settings[i.split('.')[0]] = [n, i.split('.')[1]]
         
         return settings
         
diff --git a/dstat_interface/interface/dstatinterface.glade b/dstat_interface/interface/dstatinterface.glade
index e134224eedc6842d6e319acb34100b294d71a1cd..e0eedf7b627e63e9595e6621b3c12616216481a7 100644
--- a/dstat_interface/interface/dstatinterface.glade
+++ b/dstat_interface/interface/dstatinterface.glade
@@ -943,6 +943,19 @@ Thanks to Christian Fobel for help with Dropbot Plugin</property>
                 </child>
               </object>
             </child>
+            <child>
+              <object class="GtkMenuItem" id="menu_developer">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Developer</property>
+                <child type="submenu">
+                  <object class="GtkMenu" id="menubox_dev">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                </child>
+              </object>
+            </child>
           </object>
           <packing>
             <property name="expand">False</property>
diff --git a/dstat_interface/main.py b/dstat_interface/main.py
index bca4a1e561c53c2b56e6882a3aed12913ca3da4f..8869ad95678e91cdc9c4442baddfae07d32202a2 100755
--- a/dstat_interface/main.py
+++ b/dstat_interface/main.py
@@ -59,6 +59,7 @@ import analysis
 import zmq
 import db
 from errors import InputError
+import simulator
 
 from plugin import DstatPlugin, get_hub_uri
 
@@ -82,7 +83,10 @@ class Main(object):
         self.builder.add_from_file('interface/dstatinterface.glade')
         self.builder.connect_signals(self)
         self.cell = Gtk.CellRendererText()
-
+        
+        # Add simulator item to menu
+        self.simulate = simulator.menu_setup(self.builder)
+        
         # Create instance of interface components
         self.statusbar = self.builder.get_object('statusbar')
         self.ocp_disp = self.builder.get_object('ocp_disp')
@@ -241,7 +245,8 @@ class Main(object):
         try:
             self.serial_connect.set_sensitive(False)
             self.version = comm.version_check(self.serial_liststore.get_value(
-                                    self.serial_combobox.get_active_iter(), 0))
+                                    self.serial_combobox.get_active_iter(), 0),
+                                    self.simulate.get_active())
 
             self.statusbar.remove_all(self.error_context_id)
 
diff --git a/dstat_interface/simulator.py b/dstat_interface/simulator.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c8453bbb5c5113a74be539689a689073f6f76b2
--- /dev/null
+++ b/dstat_interface/simulator.py
@@ -0,0 +1,332 @@
+from __future__ import (division, absolute_import, print_function,
+                       unicode_literals)
+from collections import OrderedDict
+from math import ceil, floor
+import logging
+import struct
+from time import sleep
+
+logger = logging.getLogger("dstat.simulator")
+
+import serial
+import numpy as np
+
+try:
+    import gi
+    gi.require_version('Gtk', '3.0')
+    from gi.repository import Gtk
+except ImportError:
+    print("ERR: GTK not available")
+    sys.exit(1)
+
+def menu_setup(builder):
+    """Take Gtk.builder instance, append menu item, and return item."""
+    
+    parent_menu_name = 'menu_developer'
+    menu_label = 'Simulate DStat'
+    menu_class = Gtk.CheckMenuItem
+    
+    menu = menu_class.new_with_label(menu_label)
+    menu.set_active(False)
+    
+    builder.get_object(parent_menu_name).get_submenu().append(menu)
+    return menu
+
+class Simulator(object):                    
+    def __init__(self):
+        self.current_state = self.main
+        self.output = b""
+        
+        self.settings_dict = OrderedDict([('max5443_offset', 0),
+                         ('tcs_enabled', 1),
+                         ('tcs_clear_threshold', 10000),
+                         ('r100_trim', 0),
+                         ('r3k_trim', 0),
+                         ('r30k_trim', 0),
+                         ('r300k_trim', 0),
+                         ('r3M_trim', 0),
+                         ('r30M_trim', 0),
+                         ('r100M_trim', 0)
+                          ])            
+    
+    def _get_input(self, size=1):
+        string = self.input_str[0:size]
+        self.input_str = self.input_str[size:]
+        return string
+    
+    def _get_params(self, n):
+        params = self.input_str.split(None, n)
+        if len(params) > n:
+            self.input_str = params.pop()
+        else:
+            self.input_str = b""
+        return params
+    
+    def input(self, string):
+        logger.debug("input: {}".format(string))
+        self.input_str = string
+        while len(self.input_str) > 0:
+            self.current_state()
+    
+    def main(self):
+        char = self._get_input()
+        
+        if char == '!':
+            self.current_state = self.command
+            self.output += b'C\r\n'
+            
+    def command(self):
+        def restart():
+            logger.info("USB restart received")
+            return
+        def version():
+            logger.info("Version Check")
+            self.output += b'V1.2\n\r'
+            return
+        
+        command_map = {'E' : self.experiment,
+                       'S' : self.settings,
+                       'R' : restart,
+                       'V' : version}
+            
+        char = self._get_input()
+        
+        try:
+            command_map[char]()
+        except KeyError:
+            logger.warning("Unrecognized command {}".format(char))
+            self.output += b"#ERR: Command {} not recognized\n\r".format(char)
+        
+        if self.current_state.__name__ != self.abort_wait.__name__:
+            self.output += b"no\n\r"
+            self.current_state = self.main
+        else:
+            logger.info("abort_wait state")
+        logger.info("Command {} Finished".format(char))
+
+    def abort_wait(self):
+        logger.info("abort_wait() called")
+        self.current_state = self.abort_wait
+        if self._get_input() == 'a':
+            logger.info("Abort signal received")
+            self.output += b"no\n\r"
+            self.current_state = self.main
+
+    def settings(self):
+        def reset():
+            logger.info("Settings reset")
+            self.settings_dict = OrderedDict([('max5443_offset', 0),
+                             ('tcs_enabled', 1),
+                             ('tcs_clear_threshold', 10000),
+                             ('r100_trim', 0),
+                             ('r3k_trim', 0),
+                             ('r30k_trim', 0),
+                             ('r300k_trim', 0),
+                             ('r3M_trim', 0),
+                             ('r30M_trim', 0),
+                             ('r100M_trim', 0)
+                              ])
+                              
+        def firmware():
+            logger.info("Firmware update mode")
+        
+        def read():
+            self.output += "S"
+            for key, value in self.settings_dict.items():
+                self.output += "{}.{}:".format(key, value)
+            self.output = self.output.rstrip(':')    
+            self.output += "\n\r"
+            
+        def write():
+            params = self._get_params(10)
+            for n, i in enumerate(self.settings_dict):
+                i = int(params[n])
+        
+        settings_map = {'D' : reset,
+                        'F' : firmware,
+                        'R' : read,
+                        'W' : write
+                        }
+        
+        char = self._get_input()
+        
+        try:
+            settings_map[char]()
+        except KeyError:
+            logger.warning("Settings control {} not found".format(char))
+        
+    def experiment(self):
+        def ads1255():
+            params = self._get_params(3)
+            logger.info("ADS1255 params: {}".format(params))
+            self.output += b"#A: {} {} {}\r\n".format(*params)
+            
+        def gain():
+            params = self._get_params(2)
+            logger.info("IV gain : {}".format(params))
+            self.output += b"#G: {} {}\r\n".format(*params)
+            
+        def lsv():
+            params = self._get_params(7)
+            start = ceil(int(params[-3])*(65536/3000)+32768)
+            stop = ceil(int(params[-2])*(65536/3000)+32768)
+            slope = params[-1]
+            
+            logger.info("LSV params: {}".format(params))
+            
+            for i in np.arange(start, stop, 1):
+                self.output += b"B\n"
+                self.output += struct.pack('<Hl', i, 5000*i)
+                self.output += b"\n"
+                
+        def cv():
+            params = self._get_params(9)
+            start = ceil(int(params[-3])*(65536/3000)+32768)
+            v1 = ceil(int(params[-5])*(65536/3000)+32768)
+            v2 = ceil(int(params[-4])*(65536/3000)+32768)
+            scans = int(params[-2])
+            slope = params[-1]
+            
+            logger.info("CV params: {}".format(params))
+            
+            for scan in range(scans):
+                for i in np.arange(v1, v2, 1):
+                    self.output += b"B\n"
+                    self.output += struct.pack('<Hl', i, 5000+100*scan)
+                    self.output += b"\n"
+                self.output += b"S\n\r"
+            self.output += b"D\n\r"
+            
+        def swv():
+            params = self._get_params(10)
+            start = ceil(int(params[-6])*(65536/3000)+32768)
+            stop = ceil(int(params[-5])*(65536/3000)+32768)
+            scans = int(params[-1])
+        
+            logger.info("SWV params: {}".format(params))
+            
+            if scans < 1:
+                scans = 1
+                
+            for scan in range(scans):
+                for i in np.arange(start, stop, 1):
+                    self.output += b"B\n"
+                    self.output += struct.pack('<Hll', i, 5000+100*scan, 0)
+                    self.output += b"\n"
+                self.output += b"S\n\r"
+            self.output += b"D\n\r" 
+            
+        def dpv():
+            params = self._get_params(10)
+            start = ceil(int(params[-6])*(65536/3000)+32768)
+            stop = ceil(int(params[-5])*(65536/3000)+32768)
+        
+            logger.info("DPV params: {}".format(params))
+        
+            for i in np.arange(start, stop, 1):
+                self.output += b"B\n"
+                self.output += struct.pack('<Hll', i, 5000+100*scan, 0)
+                self.output += b"\n"
+                
+            self.output += b"D\n\r"
+        
+        def pmt():
+            logger.info("PMT Idle mode entered")
+            self.abort_wait()
+        
+        def pot():
+            params = self._get_params(2)
+            seconds = int(params[0])
+            logger.info("POT params: {}".format(params))
+            
+            if seconds == 0:
+                self.abort_wait()
+            else:
+                for i in range(seconds):
+                    self.output += b"B\n"
+                    self.output += struct.pack('<Hll', i, 0, 100)
+                    sleep(1)
+        
+        def ca():
+            params = self._get_params(1)
+            steps = int(params[0])
+            params = self._get_params(steps*2)
+            times = [int(i) for i in params[steps:]]
+            seconds = sum(times)
+        
+            for i in range(seconds):
+                logger.info(i)
+                self.output += b"B\n"
+                self.output += struct.pack('<HHl', i, 0, 100*i)
+                self.output += b"\n"
+                sleep(1)
+        
+        def sync():
+            params = self._get_params(1)
+            logger.info("Shutter Sync {} Hz".format(params[0]))
+        
+        def shut_off():
+            logger.info("Shutter Sync Off")
+        
+        def shut_close():
+            logger.info("Shutter closed")
+        
+        def shut_open():
+            logger.info("Shutter open")
+            
+        experiment_map = {'A' : ads1255,
+                          'G' : gain,
+                          'L' : lsv,
+                          'C' : cv,
+                          'S' : swv,
+                          'D' : dpv,
+                          'M' : pmt,
+                          'P' : pot,
+                          'R' : ca,
+                          'Z' : sync,
+                          'z' : shut_off,
+                          '1' : shut_close,
+                          '2' : shut_open
+                      }
+        
+        char = self._get_input()
+        
+        try:
+            experiment_map[char]()
+        except KeyError:
+            logger.warning("Unrecognized exp command {}".format(char))
+            self.output += b"#ERR: Command {} not recognized\n\r".format(char)
+                       
+class SerialSim(object):
+    def __init__(self, *args, **kwargs):
+        self.sim = Simulator()
+        self.is_open = True
+    def open(self):
+        self.is_open = True
+    def close(self):
+        self.is_open = False
+        self.flushInput()
+    def write(self, string):
+        self.sim.input(string)
+    def read(self, size=1):
+        output = self.sim.output[0:size]
+        self.sim.output = self.sim.output[size:]
+        return output
+    def flushInput(self):
+        self.sim.output = b""   
+    def next(self):
+        if self.sim.output == b"":
+            raise StopIteration
+            
+        parted = self.sim.output.partition(b'\n')
+        output = parted[0]
+        
+        if parted[2] == b"":
+            self.sim.output = b""
+        else:
+            self.sim.output = parted[2]
+            
+        return output
+        
+    def __iter__(self):
+        return self
\ No newline at end of file