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

class CamrasHpibDevice(object):
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    """Wrapper around HPIB commands

    Args:
        address (int): GPIB address
        check_frequency (bool): check the frequency after setting it, raise an error if setting
            the frequency failed
        return_to_local (bool): return the device to local mode after each HPIB command. Some
            devices switch out of manual mode when a HPIB command is set; undo this.

    Attributes:
        hpib_address (int): GPIB address
        check_frequency (bool): check the frequency after setting it, raise an error if setting
            the frequency failed
        return_to_local (bool): return the device to local mode after each HPIB command. Some
            devices switch out of manual mode when a HPIB command is set; undo this.
    """
77

Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
78
    command_thread = None
79
    _initialization_lock = threading.Lock()
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
80

81
    def __init__(self, address, check_frequency=True, return_to_local=False):
82
        self.hpib_address = address
83
        self._check_frequency = check_frequency
84
        self.return_to_local = return_to_local
85

86
87
88
89
90
91
92
93
        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
94

95
    def command(self, command_string, prepend_address=True, return_to_local=False):
96
97
98
99
100
        """Send a command to the HPIB command thread

        Args:
            command_string (str): HPIB command
            prepend_address (bool): first send the address of this device
101
            return_to_local (bool): send a 'return to local' command after the command
102
103
104
105
106

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

111
        return CamrasHpibDevice.command_thread.protocol.command(command_string, address=address,
112
                                                                return_to_local=self.return_to_local)
113

114
115
    @property
    def frequency(self):
116
117
118
119
120
121
122
123
        """Frequency of the device as an astropy Quantity.

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

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

126
127
128
        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
129
        return int(float(freq_str)) * u.Hz
130
131
132

    @frequency.setter
    def frequency(self, freq):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
133
134
135
136
        """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
137
138

        Raises:
139
140
            RuntimeError: On communication error
            ValueError: If the device sticks to a different frequency (e.g. if the specified
141
                frequency is outside the accepted range for the device)
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
142
        """
143
144
        if isinstance(freq, Quantity):
            freq = freq.to(u.Hz).value
145
146
        self.command("freq {:d} Hz".format(int(freq)))

147
        new_freq = self.frequency
148
        if self._check_frequency and new_freq.to(u.Hz).value != int(freq):
149
            raise ValueError("Setting frequency failed: tried to set to {}, it is now {}".format(
150
151
                freq, new_freq))

152
153
154
155

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

156
    def __init__(self, **kwargs):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
157
        super(Receiver, self).__init__(1)
158

159

160
class LocalOscillator(CamrasHpibDevice):
161
162
    """Wrapper around HPIB commands for the local oscillator"""

163
    def __init__(self, **kwargs):
164
        super(LocalOscillator, self).__init__(28)
165

166

167
168
class ClockGenerator(CamrasHpibDevice):
    """Wrapper around HPIB commands for the clock generator (should be at 140MHz)"""
169

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