camrasdevices.py 5.38 KB
Newer Older
1
2
3
4
5
"""HPIB wrappers for devices in the Dwingeloo telescope

This module contains some classes for conveniently working with the devices in the Dwingeloo
Telescope that communicate over GPIB (General Purpuse Interface Bus) or HPIB (presumably the
HP variant of GPIB). Currently, three of these devices exist, on different HPIB addresses:
6
7
8
9

- Receiver (address 1)
- Local Oscillator (address 28), used for mixing signals to downconvert them
- Clock Generator (address 14), used as the clock for the FPGA
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

For each of the three devices a class exists. Setting and getting the frequency of these
devices is done by using their `frequency` attribute.

Example:
    >>> import camrasdevices
    >>> lo = LocalOscillator()
    >>> lo.frequency
    <Quantity 1.0 GHz>
    >>> lo.frequency = 1.1e9
    >>> lo.frequency
    <Quantity 1.1 GHz>

By default, the setter method of `frequency` checks if setting the device succeeded. This is a
blocking operation, but the HPIB bus is quite fast.
25

26
27
28
    >>> lo.frequency = 1.e18
    Traceback (most recent call last):
    RuntimeError: Setting frequency failed: tried to set to 1e+18 Hz, it is now 1100000000.0 Hz
29

30
This checking can be disabled:
31

32
33
34
35
36
    >>> non_checking_lo = camrasdevices.LocalOscillator(check_frequency = False)
    >>> non_checking_lo.frequency = 1.e18
    >>> non_checking_lo.frequency
    <Quantity 1.1 GHz>

37
38
39
40
41
42
Other HPIB commands can be sent using the `command` function:
    >>> lo.command("loc 28") # Set device to local mode

Also generic HPIB commands (without specific address) can be sent:
    >>> lo.command("++eoi 1", prepend_address=False)

43
44
45
46
47
48
49
50
51
The CamrasHpibDevice class spawns one thread (shared with all instances) to perform the HPIB
commands. This is to make sure that instances living in separate threads do not interfere with
each other. The thread is a daemon, so it will be killed on program exit.

On initialization of the first CamrasDevice (or subclass), the command thread is initialized, and
some initialization commands are sent to the HPIB bus to tell it to use the appropriate line
endings etc.
"""

52
53
import astropy.units as u
from astropy.units import Quantity
54

55
56
import hpib
import serial.threaded
57
import threading
58

59
60
61

class CamrasHpibDevice(object):
    """Wrapper around HPIB commands"""
62

Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
63
    command_thread = None
64
    _initialization_lock = threading.Lock()
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
65

66
    def __init__(self, address, check_frequency=True):
67
        self.hpib_address = address
68
        self._check_frequency = check_frequency
69

70
71
72
73
74
75
76
77
        with self._initialization_lock:
            if not CamrasHpibDevice.command_thread:
                serial_port = serial.Serial("/dev/ttyUSB0")
                CamrasHpibDevice.command_thread = serial.threaded.ReaderThread(serial_port,
                                                                               hpib.GPIBProtocol)
                CamrasHpibDevice.command_thread.start()
                CamrasHpibDevice.command_thread._connection_made.wait()
                CamrasHpibDevice.command_thread.protocol.init_hpib()
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
78

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
    def command(self, command_string, prepend_address=True):
        """Send a command to the HPIB command thread

        Args:
            command_string (str): HPIB command
            prepend_address (bool): first send the address of this device

        Returns:
            str: output of the command (in case it was a query)
        """
        if prepend_address:
            address = self.address
        else:
            address = None

        return CamrasHpibDevice.command_thread.protocol.command(command_string, address)

96
97
    @property
    def frequency(self):
98
99
100
101
102
103
104
105
        """Frequency of the device as an astropy Quantity.

        Returns:
            Quantity: frequency (in Hz) of the device

        Raises:
            RuntimeError: If the device does not respond
        """
106
107
        freq_str = self.command("freq?")

108
109
110
        if not freq_str or len(freq_str) == 0:
            raise RuntimeError("Camras device at address {} is not responding".format(
                self.hpib_address))
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
111
        return int(float(freq_str)) * u.Hz
112
113
114

    @frequency.setter
    def frequency(self, freq):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
115
116
117
118
        """Set the device to the specified frequency. Throws a RuntimeError if failed

        Args:
            freq (Union[float, Quantity]): new frequency. If no unit present, assume Hz
119
120

        Raises:
121
122
            RuntimeError: On communication error
            ValueError: If the device sticks to a different frequency (e.g. if the specified
123
                frequency is outside the accepted range for the device)
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
124
        """
125
126
        if isinstance(freq, Quantity):
            freq = freq.to(u.Hz).value
127
128
        self.command("freq {:d} Hz".format(int(freq)))

129
        new_freq = self.frequency
130
        if self._check_frequency and new_freq.to(u.Hz).value != int(freq):
131
            raise ValueError("Setting frequency failed: tried to set to {}, it is now {}".format(
132
133
                freq, new_freq))

134
135
136
137

class Receiver(CamrasHpibDevice):
    """Wrapper around HPIB commands for the Rohde & Schwartz receiver"""

138
    def __init__(self, **kwargs):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
139
        super(Receiver, self).__init__(1)
140

141

142
class LocalOscillator(CamrasHpibDevice):
143
144
    """Wrapper around HPIB commands for the local oscillator"""

145
    def __init__(self, **kwargs):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
146
        super(LocalOscillator, self).__init__(28)
147

148

149
150
class ClockGenerator(CamrasHpibDevice):
    """Wrapper around HPIB commands for the clock generator (should be at 140MHz)"""
151

152
    def __init__(self, **kwargs):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
153
        super(ClockGenerator, self).__init__(14)