1
###############################################################################
2
#    pyrpl - DSP servo controller for quantum optics with the RedPitaya
3
#    Copyright (C) 2014-2016  Leonhard Neuhaus  (neuhaus@spectro.jussieu.fr)
4
#
5
#    This program is free software: you can redistribute it and/or modify
6
#    it under the terms of the GNU General Public License as published by
7
#    the Free Software Foundation, either version 3 of the License, or
8
#    (at your option) any later version.
9
#
10
#    This program is distributed in the hope that it will be useful,
11
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
#    GNU General Public License for more details.
14
#
15
#    You should have received a copy of the GNU General Public License
16
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
###############################################################################
18

19

20 3
import numpy as np
21 3
import socket
22 3
import logging
23 3
try:
24 3
    from pysine import sine  # for debugging read/write calls
25 3
except:
26 3
    def sine(frequency, duration):
27 0
        print("Called sine(frequency=%f, duration=%f)" % (frequency, duration))
28 3
from .hardware_modules.dsp import dsp_addr_base, DSP_INPUTS
29 3
from .pyrpl_utils import time
30

31
# global conter to assign a number to each client
32
# only used for debugging purposes
33 3
CLIENT_NUMBER = 0
34

35

36 3
class MonitorClient(object):
37 3
    def __init__(self, hostname="192.168.1.0", port=2222, restartserver=None):
38
        """initiates a client connected to monitor_server
39

40
        hostname: server address, e.g. "localhost" or "192.168.1.0"
41
        port:    the port that the server is running on. 2222 by default
42
        restartserver: a function to call that restarts the server in case of problems
43
        """
44 0
        self.logger = logging.getLogger(name=__name__)
45
        # update global client counter and assign a number to this client
46
        global CLIENT_NUMBER
47 0
        CLIENT_NUMBER += 1
48 0
        self.client_number = CLIENT_NUMBER
49 0
        self.logger.debug("Client number %s started", self.client_number)
50
        # start setting up client
51 0
        self._restartserver = restartserver
52 0
        self._hostname = hostname
53 0
        self._port = port
54 0
        self._read_counter = 0 # For debugging and unittests
55 0
        self._write_counter = 0 # For debugging and unittests
56 0
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
57
        # try to connect at least 5 times
58 0
        for i in range(5):
59 0
            if not self._port > 0:
60 0
                if self._port is None:
61
                    # likely means that _restartserver failed.
62 0
                    raise ValueError("Connection to hostname %s failed. "
63
                                     "Please check your connection parameters!"
64
                                     % (self._hostname))
65
                else:
66 0
                    raise ValueError("Trying to open MonitorClient for "
67
                                     "hostname %s on invalid port %s. Please "
68
                                     "check your connection parameters!"
69
                                     % (self._hostname, self._port))
70 0
            try:
71 0
                self.socket.connect((self._hostname, self._port))
72 0
            except socket.error:  # mostly because port is still closed
73 0
                self.logger.warning("Socket error during connection "
74
                                    "attempt %s.", i)
75
                # could try a different port here by putting port=-1
76 0
                self._port = self._restartserver()
77
            else:
78 0
                break
79 0
        self.socket.settimeout(1.0)  # 1 second timeout for socket operations
80

81 3
    def close(self):
82 0
        try:
83 0
            self.socket.send(
84
                b'c' + bytes(bytearray([0, 0, 0, 0, 0, 0, 0])))
85 0
            self.socket.close()
86 0
        except socket.error:
87 0
            return
88

89 3
    def __del__(self):
90 0
        self.close()
91
        
92
    # the public methods to use which will recover from connection problems
93 3
    def reads(self, addr, length):
94 0
        self._read_counter+=1
95 0
        if hasattr(self, '_sound_debug') and self._sound_debug:
96 0
            sine(440, 0.05)
97 0
        return self.try_n_times(self._reads, addr, length)
98

99 3
    def writes(self, addr, values):
100 0
        self._write_counter += 1
101 0
        if hasattr(self, '_sound_debug') and self._sound_debug:
102 0
            sine(880, 0.05)
103 0
        return self.try_n_times(self._writes, addr, values)
104
    
105
    # the actual code
106 3
    def _reads(self, addr, length):
107 0
        if length > 65535:
108 0
            length = 65535
109 0
            self.logger.warning("Maximum read-length is %d", length)
110 0
        header = b'r' + bytes(bytearray([0,
111
                                         length & 0xFF, (length >> 8) & 0xFF,
112
                                         addr & 0xFF, (addr >> 8) & 0xFF, (addr >> 16) & 0xFF, (addr >> 24) & 0xFF]))
113 0
        self.socket.send(header)
114 0
        data = self.socket.recv(length * 4 + 8)
115 0
        while (len(data) < length * 4 + 8):
116 0
            data += self.socket.recv(length * 4 - len(data) + 8)
117 0
        if data[:8] == header:  # check for in-sync transmission
118 0
            return np.frombuffer(data[8:], dtype=np.uint32)
119
        else:  # error handling
120 0
            self.logger.error("Wrong control sequence from server: %s", data[:8])
121 0
            self.emptybuffer()
122 0
            return None
123

124 3
    def _writes(self, addr, values):
125 0
        values = values[:65535 - 2]
126 0
        length = len(values)
127 0
        header = b'w' + bytes(bytearray([0,
128
                                         length & 0xFF,
129
                                         (length >> 8) & 0xFF,
130
                                         addr & 0xFF,
131
                                         (addr >> 8) & 0xFF,
132
                                         (addr >> 16) & 0xFF,
133
                                         (addr >> 24) & 0xFF]))
134
        # send header+body
135 0
        self.socket.send(header +
136
                         np.array(values, dtype=np.uint32).tobytes())
137 0
        if self.socket.recv(8) == header:  # check for in-sync transmission
138 0
            return True  # indicate successful write
139
        else:  # error handling
140 0
            self.logger.error("Error: wrong control sequence from server")
141 0
            self.emptybuffer()
142 0
            return None
143

144 3
    def emptybuffer(self):
145 0
        for i in range(100):
146 0
            n = len(self.socket.recv(16384))
147 0
            if (n <= 0):
148 0
                return
149 0
            self.logger.debug("Read %d bytes from socket...", n)
150

151 3
    def try_n_times(self, function, addr, value, n=5):
152 0
        for i in range(n):
153 0
            try:
154 0
                value = function(addr, value)
155 0
            except (socket.timeout, socket.error):
156 0
                self.logger.error("Error occured in reading attempt %s. "
157
                                  "Reconnecting at addr %s to %s value %s by "
158
                                  "client %s"
159
                                  % (i,
160
                                     hex(addr),
161
                                     function.__name__,
162
                                     value,
163
                                     self.client_number))
164 0
                if self._restartserver is not None:
165 0
                    self.restart()
166
            else:
167 0
                if value is not None:
168 0
                    return value
169

170 3
    def restart(self):
171 0
        self.close()
172 0
        port = self._restartserver()
173 0
        self.__init__(
174
            hostname=self._hostname,
175
            port=port,
176
            restartserver=self._restartserver)
177

178

179
class DummyClient(object):  # pragma: no cover
180
    """Class for unitary tests without RedPitaya hardware available"""
181
    class fpgadict(dict):
182
        def __missing__(self, key):
183
            return 1 # 0 (1 is needed to avoid division_by_zero errors for some registers)
184
    fpgamemory = fpgadict({str(0x40100014): 1})  # scope decimation initial value
185

186
    def read_fpgamemory(self, addr):
187
        # here we implement a fraction of the memory map to simulate the actual redpitaya
188
        # scope
189
        offset = addr - 0x40100000
190
        # scope curve buffer
191
        if offset >= 0x10000 and offset < 0x30000:
192
            v = int(np.random.normal(scale=2**13 - 1))//4
193
            if v > 2**13-1:
194
                v = 2*13-1
195
            elif v < -(2**13-1):
196
                v = -(2**13-1)
197
            if v < 0:
198
                v += 2**14
199
            return v
200
        # scope control register - trigger armed, trigger source etc.
201
        if offset == 0:
202
            return 0
203
        if offset == 0x15C:  # current_timestamp lv part
204
            t = int(time()*125e6)
205
            return t % (2**32)
206
        if offset == 0x160:  # current_timestamp mv part
207
            return 0
208
            t = int(time()*125e6)
209
            return t - (t % (2**32))
210
        if offset == 0x164:  # trigger_timestamp lv part
211
            return 0
212
        if offset == 0x168:  # trigger_timestamp mv part
213
            return 0
214
        #DSP modules
215
        all = DSP_INPUTS
216
        for module in DSP_INPUTS:
217
            offset = addr - dsp_addr_base(module)
218
            if module.startswith('pid'):
219
                if offset == 0x220: # FILTERSTAGES
220
                    return 4
221
                elif offset == 0x228:  # MINBW
222
                    return 1
223
            elif module.startswith('iir'):
224
                if offset == 0x200:  # IIRBITS
225
                    return 64
226
                elif offset == 0x204:  # IIRSHIFT
227
                    return 32
228
                elif offset == 0x208:  # IIRSTAGES
229
                    return 16
230
                elif offset == 0x220:  # filterstages
231
                    return 1
232
                elif offset == 0x108:  # overflow
233
                    return 0
234
            elif module.startswith('iq'):
235
                if offset == 0x220:  # filterstages
236
                    return 1
237
                # rbw filter register
238
                elif offset == 0x230:  # filterstages = 0x230
239
                    return 2
240
                elif offset == 0x234:  # shiftbits = 0x234
241
                    return 2
242
                elif offset == 0x238:  # minbw = 0x238
243
                    return 1
244
            for filter_module in ['iq', 'pid', 'iir']:
245
                if module.startswith(filter_module):
246
                    if offset == 0x220:  # filterstages
247
                        return 1
248
                    elif offset == 0x224:  # shiftbits
249
                        return 2
250
                    elif offset == 0x228:  # minbw
251
                        return 1
252

253
        # everything else is restored from the dict
254
        return self.fpgamemory[str(addr)]
255

256
    def reads(self, addr, length):
257
        val = []
258
        for i in range(length):
259
            val.append(self.read_fpgamemory(addr+0x4*i))
260
        return np.array(val, dtype=np.uint32)
261
    
262
    def writes(self, addr, values): # pragma: no-cover
263
        for i, v in enumerate(values):
264
            self.fpgamemory[str(addr+0x4*i)]=v
265
    
266
    def restart(self):
267
        pass
268
    
269
    def close(self):
270
        pass

Read our documentation on viewing source code .

Loading