camrasdevices.py 5.21 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

58
59
60

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

Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
62
63
    command_thread = None

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

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

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
    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)

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

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

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

105
106
107
        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
108
        return int(float(freq_str)) * u.Hz
109
110
111

    @frequency.setter
    def frequency(self, freq):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
112
113
114
115
        """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
116
117
118
119

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

125
        new_freq = self.frequency
126
        if self._check_frequency and new_freq.to(u.Hz).value != int(freq):
127
            raise RuntimeError("Setting frequency failed: tried to set to {}, it is now {}".format(
128
129
                freq, new_freq))

130
131
132
133

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

134
    def __init__(self, **kwargs):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
135
        super(Receiver, self).__init__(1)
136

137

138
class LocalOscillator(CamrasHpibDevice):
139
140
    """Wrapper around HPIB commands for the local oscillator"""

141
    def __init__(self, **kwargs):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
142
        super(LocalOscillator, self).__init__(28)
143

144

145
146
class ClockGenerator(CamrasHpibDevice):
    """Wrapper around HPIB commands for the clock generator (should be at 140MHz)"""
147

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