PageRenderTime 780ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/src/python/unapy/link.py

https://github.com/bewest/unapy
Python | 224 lines | 211 code | 7 blank | 6 comment | 5 complexity | 89b7e471cbfe281fa873c8692fb92742 MD5 | raw file
  1. import logging
  2. import serial
  3. import time
  4. from pprint import pprint, pformat
  5. logger = logging.getLogger(__name__)
  6. import util
  7. import lib
  8. AT_TERMINATORS = [ 'NO CARRIER', 'ERROR', 'OK', 'CONNECT' ]
  9. class AtProcessor(util.Loggable):
  10. def long_read(self, timeout=2.5, repeats=15):
  11. B = [ ]
  12. oldTimeout = self.getTimeout()
  13. self.setTimeout(timeout)
  14. for i in xrange(repeats):
  15. self.log.debug("retry: %s" % i)
  16. B += self.readlines( )
  17. if len(B) > 0:
  18. lastline = B[-1].strip( )
  19. if (lastline in AT_TERMINATORS or "ERROR" in lastline):
  20. break
  21. time.sleep(.01)
  22. self.setTimeout(oldTimeout)
  23. return B
  24. def process(self, command):
  25. """
  26. Synchronously process a single command.
  27. """
  28. # format the command
  29. message = command.format()
  30. self.log.info('process: %r' % message)
  31. # write it into the port
  32. self.write(message)
  33. self.log.info('reading...')
  34. time.sleep(.020)
  35. # read response
  36. kwds = { }
  37. if getattr(command, 'readTimeout', None) is not None:
  38. kwds = dict(timeout= command.readTimeout,
  39. repeats= command.readRepeats)
  40. response = ''.join(self.long_read())
  41. self.log.info('process.response: %r' % response)
  42. # store response in the command
  43. result = command.parse(response)
  44. self.log.info('process.parse.result: %r' % result)
  45. return command
  46. class Link(serial.Serial, AtProcessor):
  47. __port__ = None
  48. __name__ = None
  49. def __init__(self, name = None):
  50. self.__name__ = name
  51. self.__port__ = super(type(self), self).__init__(name, timeout=2)
  52. self.getLog()
  53. self.log.debug('created device: %r' % self)
  54. def dump_port_settings(self):
  55. info = [ "Settings: %s %s,%s,%s,%s" % (
  56. self.portstr, self.baudrate,
  57. self.bytesize, self.parity,
  58. self.stopbits, ),
  59. ' -- software flow control %s' % (self.xonxoff and 'active' or 'inactive'),
  60. ' -- hardware flow control %s' % (self.rtscts and 'active' or 'inactive'),
  61. ]
  62. try:
  63. if self.isOpen():
  64. info.append(' -- CTS: %s DSR: %s RI: %s CD: %s' % (
  65. ((self.isOpen() and self.getCTS()) and 'active' or 'inactive'),
  66. (self.getDSR() and 'active' or 'inactive'),
  67. (self.getRI() and 'active' or 'inactive'),
  68. (self.getCD() and 'active' or 'inactive'),
  69. ))
  70. except serial.portNotOpenError:
  71. info.append("port is not open")
  72. except serial.SerialException:
  73. # on RFC 2217 ports it can happen to no modem state notification was
  74. # yet received. ignore this error.
  75. pass
  76. return "\n".join(info)
  77. def __repr__(self):
  78. return self.dump_port_settings()
  79. def write( self, string ):
  80. r = super(type(self), self).write( string )
  81. #self.log.info( 'write: %s\n%s' % ( len( string ),
  82. # lib.hexdump( bytearray( string ) ) ) )
  83. return r
  84. def read( self, c ):
  85. r = super(type(self), self).read( c )
  86. #self.log.info( 'read: %s\n%s' % ( len( r ),
  87. # lib.hexdump( bytearray( r ) ) ) )
  88. return r
  89. def readline( self ):
  90. r = super(type(self), self).readline( )
  91. #self.log.info( 'read: %s\n%s' % ( len( r ),
  92. # lib.hexdump( bytearray( r ) ) ) )
  93. return r
  94. def readlines( self ):
  95. r = super(type(self), self).readlines( )
  96. self.log.info( 'read: %s\n%s' % ( len( r ),
  97. lib.hexdump( bytearray( ''.join( r ) ) ) ) )
  98. return r
  99. class FakeCommand(object):
  100. __examples__ = [ ("FOO", "OK") ]
  101. __example__ = ("FOO", "BAR")
  102. raw = 'ERROR'
  103. """Simulates a command and a response."""
  104. def format(self):
  105. return 'FOO'
  106. def parse(self, raw):
  107. if raw == "OK":
  108. self.raw = raw
  109. return self
  110. class FakeLink(Link):
  111. def __init__(self): pass
  112. def process(self, command):
  113. """
  114. Fake process that can read from a script.
  115. Useful for testing.
  116. >>> link = FakeLink( )
  117. >>> link.process(FakeCommand( )).raw
  118. 'OK'
  119. """
  120. k = command.format( )
  121. D = dict(command.__examples__)
  122. result = command.parse(D[k])
  123. return command
  124. class FakeKeyedLink(FakeLink):
  125. comm = { }
  126. def process(self, command):
  127. key = command.format( )
  128. raw = self.comm.get(key)
  129. res = command.parse(raw)
  130. return command
  131. class OKExampleLink(FakeLink):
  132. """OKExampleLink
  133. This one just feeds the command's __ex_ok into it's parse method.
  134. """
  135. def process(self, command):
  136. """We ignore formatting the command completely, and just parse the
  137. _ex_ok.
  138. """
  139. response = command.parse(command._ex_ok)
  140. return command
  141. class FakeListLink(FakeLink):
  142. """A fake link useful for testing.
  143. This one follows a script in the sense that it ignores formatting and
  144. writing commands, and always reads the next message in a list of messages.
  145. Eg it iterates over `comms` for each invoctation of `process`, regardless
  146. of the command given.
  147. >>> class SimpleFake(FakeListLink):
  148. ... comms = [ 1, 2, 3, 'hello', 'world', 'OK' ]
  149. >>> link = SimpleFake( )
  150. >>> link.read( )
  151. 1
  152. >>> link.read( )
  153. 2
  154. >>> link.read( )
  155. 3
  156. >>> link.read( )
  157. 'hello'
  158. >>> link.process(FakeCommand( )).raw
  159. 'ERROR'
  160. >>> link.process(FakeCommand( )).raw
  161. 'OK'
  162. """
  163. comms = [ ]
  164. def __init__(self):
  165. self.index = 0
  166. def read(self):
  167. result = self.comms[self.index]
  168. self.incr( )
  169. return result
  170. def incr(self, step=1):
  171. for i in xrange(step):
  172. self.index += 1
  173. def decr(self, step=1):
  174. for i in xrange(step):
  175. self.index -= 1
  176. def process(self, command):
  177. """We ignore formatting the command completely, and just parse the next
  178. available message using whatever command you gave us."""
  179. response = command.parse(self.read( ))
  180. return command
  181. if __name__ == '__main__':
  182. import doctest
  183. doctest.testmod()
  184. #####
  185. # EOF