Repository: microsoft/Qcodes

Issue:
Short human readable instrument snapshot. 
@giulioungaretti 
If we use `instrument.snapshot()` we get an overview of all parameters of an instrument, this is very useful for data storage. However, there is too much information to quickly scan over it. 

Below I give an example, desired output first, current output after. I think the snippet below should (with some enhancements) be part of the base instrument class. Callable in the form of `instrument.snapshot(concise=True)`. 

Please let me know what you think. 

Output of 
```
for parname, par in FM_LaDucati.parameters.items():
    print('{}: \t{} ({})'.format(parname, par.get_latest(), par.units))
```

```
T_MClo: 	49.0 (mK)
IDN: 	{'vendor': 'QuTech', 'model': 'FridgeMon', 'firmware': '0.2', 'serial': None} ()
T_Still: 	None (mK)
T_Sorb: 	3836.0 (mK)
T_MChi: 	0.0 (mK)
fridge_name: 	LaDucati ()
update_interval: 	60 (s)
```

Output of fridge_monitor.snapshot(): 

```
{'__class__': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
 'functions': {},
 'name': 'FM_LaFerrari',
 'parameters': {'IDN': {'__class__': 'qcodes.instrument.parameter.StandardParameter',
   'instrument': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
   'instrument_name': 'FM_LaFerrari',
   'label': 'IDN',
   'name': 'IDN',
   'ts': '2016-11-19 13:23:17',
   'units': '',
   'value': {'firmware': '0.2',
    'model': 'FridgeMon',
    'serial': None,
    'vendor': 'QuTech'}},
  'T_MCStage': {'__class__': 'qcodes.instrument.parameter.StandardParameter',
   'instrument': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
   'instrument_name': 'FM_LaFerrari',
   'label': 'T_MCStage',
   'name': 'T_MCStage',
   'ts': '2016-11-19 13:23:17',
   'units': 'mK',
   'value': 0.0},
  'T_MChi': {'__class__': 'qcodes.instrument.parameter.StandardParameter',
   'instrument': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
   'instrument_name': 'FM_LaFerrari',
   'label': 'T_MChi',
   'name': 'T_MChi',
   'ts': '2016-11-19 13:23:17',
   'units': 'mK',
   'value': 0.0},
  'T_MClo': {'__class__': 'qcodes.instrument.parameter.StandardParameter',
   'instrument': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
   'instrument_name': 'FM_LaFerrari',
   'label': 'T_MClo',
   'name': 'T_MClo',
   'ts': '2016-11-19 13:23:17',
   'units': 'mK',
   'value': 32.3},
  'T_MCmid': {'__class__': 'qcodes.instrument.parameter.StandardParameter',
   'instrument': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
   'instrument_name': 'FM_LaFerrari',
   'label': 'T_MCmid',
   'name': 'T_MCmid',
   'ts': '2016-11-19 13:23:17',
   'units': 'mK',
   'value': 141.0},
  'T_Sorb': {'__class__': 'qcodes.instrument.parameter.StandardParameter',
   'instrument': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
   'instrument_name': 'FM_LaFerrari',
   'label': 'T_Sorb',
   'name': 'T_Sorb',
   'ts': '2016-11-19 13:23:17',
   'units': 'mK',
   'value': 4039.0},
  'T_Still': {'__class__': 'qcodes.instrument.parameter.StandardParameter',
   'instrument': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
   'instrument_name': 'FM_LaFerrari',
   'label': 'T_Still',
   'name': 'T_Still',
   'ts': '2016-11-19 13:23:17',
   'units': 'mK',
   'value': None},
  'fridge_name': {'__class__': 'qcodes.instrument.parameter.ManualParameter',
   'instrument': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
   'instrument_name': 'FM_LaFerrari',
   'label': 'fridge_name',
   'name': 'fridge_name',
   'ts': '2016-11-19 13:22:50',
   'units': '',
   'value': 'LaFerrari'},
  'update_interval': {'__class__': 'qcodes.instrument.parameter.ManualParameter',
   'instrument': 'pycqed.instrument_drivers.physical_instruments.Fridge_monitor.Fridge_Monitor',
   'instrument_name': 'FM_LaFerrari',
   'label': 'update_interval',
   'name': 'update_interval',
   'ts': '2016-11-19 13:22:50',
   'units': 's',
   'value': 60}}}
```


Discussion:
I like the idea a lot, it's currently a bit cumbersome to get the useful information out of a snapshot. 
Would it make more sense to call this a separate function, such as instrument.print_snapshot(), where you can specify the level of output detail you want. I'm thinking this because otherwise the snapshot() would return either a dict or nothing, which feel like quite different behaviours.

Resolving patch:
diff --git a/qcodes/instrument/base.py b/qcodes/instrument/base.py
--- a/qcodes/instrument/base.py
+++ b/qcodes/instrument/base.py
@@ -3,6 +3,7 @@
 import time
 import warnings
 import weakref
+import numpy as np
 
 from qcodes.utils.metadata import Metadatable
 from qcodes.utils.helpers import DelegateAttributes, strip_attrs, full_class
@@ -434,7 +435,48 @@ def snapshot_base(self, update=False):
                 snap[attr] = getattr(self, attr)
         return snap
 
-    #
+    def print_readable_snapshot(self, update=False, max_chars=80):
+        """
+        Prints a readable version of the snapshot.
+        The readable snapshot includes the name, value and unit of each
+        parameter.
+        A convenience function to quickly get an overview of the status of an instrument.
+
+        Args:
+            update (bool)  : If True, update the state by querying the
+                instrument. If False, just use the latest values in memory.
+                This argument gets passed to the snapshot function.
+            max_chars (int) : the maximum number of characters per line. The
+                readable snapshot will be cropped if this value is exceeded.
+                Defaults to 80 to be consistent with default terminal width.
+        """
+        floating_types = (float, np.integer, np.floating)
+        snapshot = self.snapshot(update=update)
+
+        par_lengths = [len(p) for p in snapshot['parameters']]
+
+        # Min of 50 is to prevent a super long parameter name to break this
+        # function
+        par_field_len = min(max(par_lengths)+1, 50)
+
+        print(self.name + ':')
+        print('{0:<{1}}'.format('\tparameter ', par_field_len) + 'value')
+        print('-'*80)
+        for par in sorted(snapshot['parameters']):
+            msg = '{0:<{1}}:'.format(snapshot['parameters'][par]['name'], par_field_len)
+            val = snapshot['parameters'][par]['value']
+            unit = snapshot['parameters'][par]['unit']
+            if isinstance(val, floating_types):
+                msg += '\t{:.5g} '.format(val)
+            else:
+                msg += '\t{} '.format(val)
+            if unit is not '':  # corresponds to no unit
+                msg += '({})'.format(unit)
+            # Truncate the message if it is longer than max length
+            if len(msg) > max_chars and not max_chars==-1:
+                msg = msg[0:max_chars-3] + '...'
+            print(msg)
+
     # `write_raw` and `ask_raw` are the interface to hardware                #
     # `write` and `ask` are standard wrappers to help with error reporting   #
     #


Test patch: