1   
  2  """SpecConnection module 
  3   
  4  Low-level module for communicating with a 
  5  remove Spec server 
  6   
  7  Classes : 
  8  SpecClientNotConnectedError -- exception class 
  9  SpecConnection 
 10  SpecConnectionDispatcher 
 11  """ 
 12   
 13  __author__ = 'Matias Guijarro' 
 14  __version__ = '1.0' 
 15   
 16  import asyncore 
 17  import socket 
 18  import weakref 
 19  import string 
 20  import logging 
 21  import time 
 22  from SpecClient.SpecClientError import SpecClientNotConnectedError 
 23  import SpecEventsDispatcher 
 24  import SpecChannel 
 25  import SpecMessage 
 26  import SpecReply 
 27   
 28  asyncore.dispatcher.ac_in_buffer_size = 32768  
 29   
 30  (DISCONNECTED, PORTSCANNING, WAITINGFORHELLO, CONNECTED) = (1,2,3,4) 
 31  (MIN_PORT, MAX_PORT) = (6510, 6530) 
 32   
 34      """Represent a connection to a remote Spec 
 35   
 36      Signals: 
 37      connected() -- emitted when the required Spec version gets connected 
 38      disconnected() -- emitted when the required Spec version gets disconnected 
 39      replyFromSpec(reply id, SpecReply object) -- emitted when a reply comes from the remote Spec 
 40      error(error code) -- emitted when an error event is received from the remote Spec 
 41      """ 
 50   
 51   
 53          return str(self.dispatcher) 
  54   
 55   
 57          """Delegate access to the underlying SpecConnectionDispatcher object""" 
 58          if not attr.startswith('__'): 
 59              return getattr(self.dispatcher, attr) 
 60          else: 
 61              raise AttributeError 
  62   
 63   
 67   
 68   
 72   
 73   
 74       
 75       
 76       
 77   
 78   
  82   
 83   
 85      """SpecConnection class 
 86   
 87      Signals: 
 88      connected() -- emitted when the required Spec version gets connected 
 89      disconnected() -- emitted when the required Spec version gets disconnected 
 90      replyFromSpec(reply id, SpecReply object) -- emitted when a reply comes from the remote Spec 
 91      error(error code) -- emitted when an error event is received from the remote Spec 
 92      """ 
 94          """Constructor 
 95   
 96          Arguments: 
 97          specVersion -- a 'host:port' string 
 98          """ 
 99          asyncore.dispatcher.__init__(self) 
100   
101          self.state = DISCONNECTED 
102          self.connected = False 
103          self.receivedStrings = [] 
104          self.message = None 
105          self.serverVersion = None 
106          self.scanport = False 
107          self.scanname = '' 
108          self.registeredChannels = {} 
109          self.registeredReplies = {} 
110          self.sendq = [] 
111          self.outputStrings = [] 
112          self.simulationMode = False 
113          self.valid_socket = False 
114   
115           
116          self.macro       = self.send_msg_cmd_with_return 
117          self.macro_noret = self.send_msg_cmd 
118          self.abort         = self.send_msg_abort 
119   
120          tmp = str(specVersion).split(':') 
121          self.host = tmp[0] 
122   
123          if len(tmp) > 1: 
124              self.port = tmp[1] 
125          else: 
126              self.port = 6789 
127   
128          try: 
129              self.port = int(self.port) 
130          except: 
131              self.scanname = self.port 
132              self.port = None 
133              self.scanport = True 
134   
135           
136           
137           
138          self.registerChannel('error', self.error, dispatchMode = SpecEventsDispatcher.FIREEVENT) 
139          self.registerChannel('status/simulate', self.simulationStatusChanged) 
 140   
142          return '<connection to Spec, host=%s, port=%s>' % (self.host, self.port or self.scanname) 
 143   
145        self.valid_socket = True 
146        asyncore.dispatcher.set_socket(self, s) 
 147   
149          """Establish a connection to Spec 
150   
151          If the connection is already established, do nothing. 
152          Otherwise, create a socket object and try to connect. 
153          If we are in port scanning mode, try to connect using 
154          a port defined in the range from MIN_PORT to MAX_PORT 
155          """ 
156          if not self.connected: 
157              s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
158              s.settimeout(0.2) 
159              if self.scanport: 
160                if self.port is None or self.port > MAX_PORT: 
161                  self.port = MIN_PORT 
162                else: 
163                  self.port += 1 
164              while not self.scanport or self.port < MAX_PORT: 
165                try: 
166                   if s.connect_ex( (self.host, self.port) ) == 0: 
167                     self.set_socket(s) 
168                     self.handle_connect() 
169                     break 
170                except socket.error, err: 
171                   pass  
172                if self.scanport: 
173                  self.port += 1  
174                else: 
175                  break 
 176   
178          """Register a channel 
179   
180          Tell the remote Spec we are interested in receiving channel update events. 
181          If the channel is not already registered, create a new SpecChannel object, 
182          and connect the channel 'valueChanged' signal to the receiver slot. If the 
183          channel is already registered, simply add a connection to the receiver 
184          slot. 
185   
186          Arguments: 
187          chanName -- a string representing the channel name, i.e. 'var/toto' 
188          receiverSlot -- any callable object in Python 
189   
190          Keywords arguments: 
191          registrationFlag -- internal flag 
192          dispatchMode -- can be SpecEventsDispatcher.UPDATEVALUE (default) or SpecEventsDispatcher.FIREEVENT, 
193          depending on how the receiver slot will be called. UPDATEVALUE means we don't mind skipping some 
194          channel update events as long as we got the last one (for example, a motor position). FIREEVENT means 
195          we want to call the receiver slot for every event. 
196          """ 
197          if dispatchMode is None: 
198              return 
199   
200          chanName = str(chanName) 
201   
202          if not chanName in self.registeredChannels: 
203              newChannel = SpecChannel.SpecChannel(self, chanName, registrationFlag) 
204              self.registeredChannels[chanName] = newChannel 
205   
206          SpecEventsDispatcher.connect(self.registeredChannels[chanName], 'valueChanged', receiverSlot, dispatchMode) 
207   
208          channelValue = self.registeredChannels[chanName].value 
209          if channelValue is not None: 
210               
211              self.registeredChannels[chanName].update(channelValue) 
 212   
213   
215          """Unregister a channel 
216   
217          Arguments: 
218          chanName -- a string representing the channel to unregister, i.e. 'var/toto' 
219          """ 
220          chanName = str(chanName) 
221   
222          if chanName in self.registeredChannels: 
223              self.registeredChannels[chanName].unregister() 
224              del self.registeredChannels[chanName] 
 225   
226   
228          """Return a channel object 
229   
230          If the required channel is already registered, return it. 
231          Otherwise, return a new 'temporary' unregistered SpecChannel object ; 
232          reference should be kept in the caller or the object will get dereferenced. 
233   
234          Arguments: 
235          chanName -- a string representing the channel name, i.e. 'var/toto' 
236          """ 
237          if not chanName in self.registeredChannels: 
238               
239              return SpecChannel.SpecChannel(self, chanName, SpecChannel.DONTREG) 
240   
241          return self.registeredChannels[chanName] 
 242   
243   
245          """Emit the 'error' signal when the remote Spec version signals an error.""" 
246          logging.getLogger('SpecClient').error('Error from Spec: %s', error) 
247   
248          SpecEventsDispatcher.emit(self, 'error', (error, )) 
 249   
250   
252          self.simulationMode = simulationMode 
 253   
254   
256          """Return True if the remote Spec version is connected.""" 
257          return self.state == CONNECTED 
 258   
259   
261          """Emit the 'connected' signal when the remote Spec version is connected.""" 
262          old_state = self.state 
263          self.state = CONNECTED 
264          if old_state != CONNECTED: 
265              logging.getLogger('SpecClient').info('Connected to %s:%s', self.host, (self.scanport and self.scanname) or self.port) 
266   
267              SpecEventsDispatcher.emit(self, 'connected', ()) 
 268   
270          """Emit the 'disconnected' signal when the remote Spec version is disconnected.""" 
271          SpecEventsDispatcher.dispatch() 
272   
273          old_state = self.state 
274          self.state = DISCONNECTED 
275          if old_state == CONNECTED: 
276              logging.getLogger('SpecClient').info('Disconnected from %s:%s', self.host, (self.scanport and self.scanname) or self.port) 
277   
278              SpecEventsDispatcher.emit(self, 'disconnected', ()) 
 279   
281          """Handle 'close' event on socket.""" 
282          self.connected = False 
283          self.serverVersion = None 
284          if self.socket: 
285              self.close() 
286          self.valid_socket = False 
287          self.specDisconnected() 
 288   
289   
291          """Disconnect from the remote Spec version.""" 
292          self.handle_close() 
 293   
294   
296          """Handle an uncaught error.""" 
297          return 
 298   
299   
355   
356   
358          """Check remote Spec version 
359   
360          If we are in port scanning mode, check if the name from 
361          Spec corresponds to our required Spec version. 
362          """ 
363          if self.scanport: 
364              if name == self.scanname: 
365                  return True 
366              else: 
367                   
368                  return False 
369          else: 
370              return True 
 371   
372   
374          return self.valid_socket 
 375   
376   
378          """Return True if socket should be written.""" 
379          ret = self.readable() and (len(self.sendq) > 0 or sum(map(len, self.outputStrings)) > 0) 
380           
381          return ret 
 382   
383   
393   
394   
396          """Handle 'write' events on socket 
397   
398          Send all the messages from the queue. 
399          """ 
400          while len(self.sendq) > 0: 
401              self.outputStrings.append(self.sendq.pop().sendingString()) 
402   
403          outputBuffer = ''.join(self.outputStrings) 
404   
405          sent = self.send(outputBuffer) 
406   
407          self.outputStrings = [ outputBuffer[sent:] ] 
 408   
409   
411          """Send a command message to the remote Spec server, and return the reply id. 
412   
413          Arguments: 
414          cmd -- command string, i.e. '1+1' 
415          """ 
416          if self.isSpecConnected(): 
417              import sys 
418              try: 
419                  caller = sys._getframe(1).f_locals['self'] 
420              except KeyError: 
421                  caller = None 
422   
423              return self.__send_msg_with_reply(replyReceiverObject = caller, *SpecMessage.msg_cmd_with_return(cmd, version = self.serverVersion)) 
424          else: 
425              raise SpecClientNotConnectedError 
 426   
427   
429          """Send a command message to the remote Spec server using the new 'func' feature, and return the reply id. 
430   
431          Arguments: 
432          cmd -- command string 
433          """ 
434          if self.serverVersion < 3: 
435              logging.getLogger('SpecClient').error('Cannot execute command in Spec : feature is available since Spec server v3 only') 
436          else: 
437              if self.isSpecConnected(): 
438                  import sys 
439                  try: 
440                      caller = sys._getframe(1).f_locals['self'] 
441                  except KeyError: 
442                      caller = None 
443   
444                  message = SpecMessage.msg_func_with_return(cmd, version = self.serverVersion) 
445                  return self.__send_msg_with_reply(replyReceiverObject = caller, *message) 
446              else: 
447                  raise SpecClientNotConnectedError 
 448   
449   
460   
461   
463          """Send a command message to the remote Spec server using the new 'func' feature 
464   
465          Arguments: 
466          cmd -- command string 
467          """ 
468          if self.serverVersion < 3: 
469              logging.getLogger('SpecClient').error('Cannot execute command in Spec : feature is available since Spec server v3 only') 
470          else: 
471              if self.isSpecConnected(): 
472                  self.__send_msg_no_reply(SpecMessage.msg_func(cmd, version = self.serverVersion)) 
473              else: 
474                  raise SpecClientNotConnectedError 
 475   
476   
478          """Send a channel read message, and return the reply id. 
479   
480          Arguments: 
481          chanName -- a string representing the channel name, i.e. 'var/toto' 
482          """ 
483          if self.isSpecConnected(): 
484              import sys 
485              try: 
486                  caller = sys._getframe(1).f_locals['self'] 
487              except KeyError: 
488                  caller = None 
489   
490              return self.__send_msg_with_reply(replyReceiverObject = caller, *SpecMessage.msg_chan_read(chanName, version = self.serverVersion)) 
491          else: 
492              raise SpecClientNotConnectedError 
 493   
494   
506   
507   
518   
519   
530   
531   
538   
539   
546   
547   
551   
552   
554          """Send a message to the remote Spec, and return the reply id. 
555   
556          The reply object is added to the registeredReplies dictionary, 
557          with its reply id as the key. The reply id permits then to 
558          register for the reply using the 'registerReply' method. 
559   
560          Arguments: 
561          reply -- SpecReply object which will receive the reply 
562          message -- SpecMessage object defining the message to send 
563          """ 
564          replyID = reply.id 
565          self.registeredReplies[replyID] = reply 
566   
567          if hasattr(replyReceiverObject, 'replyArrived'): 
568              SpecEventsDispatcher.connect(reply, 'replyFromSpec', replyReceiverObject.replyArrived) 
569   
570          self.sendq.insert(0, message) 
571   
572          return replyID 
 573   
574   
576          """Send a message to the remote Spec. 
577   
578          If a reply is sent depends only on the message, and not on the 
579          method to send the message. Using this method, any reply is 
580          lost. 
581          """ 
582          self.sendq.insert(0, message) 
  583