You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_corelib.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import netifaces as ni
  2. import socket, pickle
  3. from .translator import ProtocolHandler, HardwareHandler
  4. def _get_response(Connection, BUFFER_SIZE):
  5. return Connection.recv(BUFFER_SIZE)
  6. class InitNetwork(object):
  7. """
  8. Initialise server instance for L|MO control panel.\n\n
  9. :PORT.\n
  10. Port used to accept incoming connections. Range 1-65355
  11. (defaults to 10011).\n\n
  12. :MAX_CONNECTIONS.\n
  13. Maximum number of connections accepted by the server.
  14. It defaults to 1, as it makes no sense one box being
  15. controlled by more than one user at any given time.
  16. """
  17. def _detect_host_ip(self):
  18. """
  19. Detect available interfaces in the host computer. Defaults
  20. to wlan (wireless) interface unless an ethernet cable is
  21. plugged in.\n\n
  22. TODO:
  23. - Optional interface or favour wlan even with cable?
  24. - Retrieve and keep client's hostname.
  25. - As it stands, it will connect to first interface
  26. available. Is this a problem? This is going to be
  27. deployed in one specific platform. Do we care?
  28. """
  29. # Detect local network interfaces.
  30. network_interfaces = ni.interfaces()
  31. network_interfaces.remove('lo') # Discard loopback iface.
  32. for netint in network_interfaces:
  33. try:
  34. host_addr = ni.ifaddresses(netint)[2][0]['addr']
  35. host_iface = netint
  36. break
  37. except KeyError:
  38. # Interface not used. Key [2] only exists on interfaces that
  39. # are being used. RPi has at least one iface available (wlan0)
  40. # so technically there is no need to define a host_iface=None.
  41. # Define, just in case the platform changes.
  42. host_iface = "None" # Prevents "NoneType has no attribute..."
  43. continue
  44. # Confirmation msg.
  45. if host_iface.find('eth') is not -1:
  46. print("Connected using wired connection.")
  47. elif host_iface.find('wlan') is not -1:
  48. print("Connected using wireless connection.")
  49. else:
  50. raise ValueError("Interface not found, aborting.")
  51. return None, False
  52. return host_addr, host_iface
  53. def _detect_MAC(self):
  54. """
  55. Retrieve MAC address from `wlan0', which _always_ exists, and send
  56. it back to the client to keep track of the devices.
  57. """
  58. MAC_ADDR = ni.ifaddresses('wlan0')[17][0]['addr']
  59. return MAC_ADDR.replace(":", "").upper()
  60. def _init_connection(self, BUFFER_SIZE):
  61. """
  62. Initialise server and await for incoming connections. The
  63. `accept()' command will make the rest of the code stall until
  64. a client connects. Which is okay... what's the box going to
  65. do if no-one tells it what to do? Just wait for a connection.\n\n
  66. SO_REUSEADDR makes possible to keep the server alive when
  67. a client disconnects and avoids `TIME_WAIT' situations, where
  68. the socket is closed but unable to received new connections (say,
  69. the user comes back and wants to check how the protocol is going).
  70. The alternative solution would be to re-create the socket by
  71. running the code again. So, SO_REUSEADDR makes life easier.
  72. """
  73. HOST_ADDR, HOST_IFACE = self._detect_host_ip()
  74. if HOST_IFACE:
  75. Sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  76. Sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Resuse socket tagged in TIME_WAIT state (TODO: Is this any useful?).
  77. # Sock.settimeout(5.0) # NOPE. It timesout while waiting for the client...
  78. # Timing out will have to come from the client.
  79. Sock.bind((HOST_ADDR, self._PORT))
  80. Sock.listen(self._MAX_CONNECTIONS)
  81. print("Server up and running. Awaiting connection(s).")
  82. # Accept the connection(s).
  83. Conn, Addr = Sock.accept() # accept() holds program until connection.
  84. # Report connection type
  85. if HOST_IFACE.find('eth') is not -1:
  86. Conn.sendall(str.encode("True")) # Ethernet detected, send TRUE.
  87. cli = _get_response(Conn, BUFFER_SIZE)
  88. if cli.decode() == "Acknowledged":
  89. pass
  90. else:
  91. Conn.sendall(str.encode("False")) # Ethernet NOT detected, send FALSE.
  92. cli = _get_response(Conn, BUFFER_SIZE)
  93. if cli.decode() == "Acknowledged":
  94. pass
  95. # Report device's MAC address
  96. MAC_ADDR = self._detect_MAC()
  97. Conn.sendall(str.encode(MAC_ADDR))
  98. cli = _get_response(Conn, BUFFER_SIZE)
  99. if cli.decode() == "Acknowledged":
  100. pass
  101. # Confirm connection, wait for data to come...
  102. Conn.sendall(str.encode("Connection established. Listening..."))
  103. return Sock, Conn, HOST_IFACE
  104. else:
  105. raise ValueError("Interface not recognised, aborting.")
  106. return False, None, False
  107. def __init__(self, PORT=10011, MAX_CONNECTIONS=1, BUFFER_SIZE=64):
  108. """
  109. Initiate server in port PORT and allow MAX_CONNECTIONS
  110. external connections. Return Socket, (Established) Connection
  111. object and interface used for the connection.
  112. """
  113. self._PORT = PORT
  114. self._MAX_CONNECTIONS = MAX_CONNECTIONS
  115. self.Socket, self.Connection, self.Curr_Interface = self._init_connection(BUFFER_SIZE)
  116. # Before handing over the conection to the GUI, check if any data has
  117. # not been transferred. TODO.
  118. class NetworkOperator():
  119. """
  120. NetworkOperator() handles the transmission of data between
  121. the L|MO and the client. It will tell the user that a connection
  122. has been established and the L|MO is awaiting a command. It also
  123. receives a command from the user and redirects to the appropriate
  124. routine.\n\n
  125. LIST OF AVAILABLE COMMANDS:\n
  126. - Run Protocol: Reads the protocol in Rob's LIF Format (.RLF?) sent
  127. by the user. It then retrieves the information
  128. needed by SetupDevice() and SetProtocol(), from the
  129. puppeteer library, to run L|MO appropriately.\n
  130. - Abort Protocol: Self-explanatory, stops any on going experiment
  131. running in L|MO.\n
  132. - Report Progress: Reports experiment progress back to the user.
  133. WORK IN PROGRESS. Reports current state by default
  134. (STREAM) or as per client's request (say the user
  135. goes home and wants to check how things are going).\n\n
  136. More commands to be implemented (see commented code below).
  137. """
  138. def _retrieve_data(self, Connection, BUFFER_SIZE):
  139. """
  140. Receive data sent from client and store it in the variable `Stream'.
  141. """
  142. Stream = Connection.recv(BUFFER_SIZE)
  143. if len(Stream) < BUFFER_SIZE:
  144. """
  145. This snippet handles short packages (<BUFFER_SIZE) to allow the
  146. handshake between server and client.
  147. """
  148. return Stream
  149. else:
  150. """
  151. This other snippet handles larger packages (>=BUFFER_SIZE) to
  152. allow the transmission of actual data, like, say, a protocol.
  153. """
  154. while Stream:
  155. Buffer = Connection.recv(BUFFER_SIZE)
  156. Stream += Buffer
  157. if len(Buffer) < BUFFER_SIZE : break
  158. return Stream
  159. def _retrieve_protocol(self, ProtocolSize, Connection, BUFFER_SIZE):
  160. """
  161. Retrieve data for ProtocolDump.
  162. """
  163. ProtocolDump = Connection.recv(BUFFER_SIZE)
  164. if len(ProtocolDump) < ProtocolSize:
  165. ProtocolDump += Connection.recv(BUFFER_SIZE)
  166. return ProtocolDump
  167. def _manage_connection(self, Connection, BUFFER_SIZE):
  168. """
  169. Retrieve user input and runs commands accordingly. Because each
  170. option must return something ("command"), there is no point using
  171. `while True' loops here. They always break.\n\n
  172. After the handshake has been processed, _nothing_ other than the
  173. command must be sent back to the client. So, no
  174. sendall(::Trying this::).
  175. """
  176. handshake = self._retrieve_data(Connection, BUFFER_SIZE) # Waits for client.
  177. # Handshake, what do I do?
  178. if handshake.decode() == "Run Protocol":
  179. InstructionSize = int(self._retrieve_data(Connection, BUFFER_SIZE).decode())
  180. Connection.sendall(str("Acknowledged").encode()) # Unlock to proceed.
  181. instructions_dump = self._retrieve_protocol(InstructionSize, Connection, BUFFER_SIZE**3)
  182. UserInstructions = ProtocolHandler(instructions_dump)
  183. return UserInstructions
  184. elif handshake.decode() == "Hardware Settings":
  185. SettingSize = int(self._retrieve_data(Connection, BUFFER_SIZE).decode())
  186. Connection.sendall(str("Acknowledged").encode()) # Unlock to proceed.
  187. settings_dump = self._retrieve_protocol(SettingSize, Connection, BUFFER_SIZE**3)
  188. HardwareHandler(settings_dump) # Settings stored in disk. Nothing to return.
  189. # BUT: return handshake to avoid errors with srv_controller.
  190. return "Update Settings"
  191. else:
  192. return handshake.decode()
  193. def __init__(self, Connection, BUFFER_SIZE=64):
  194. """
  195. Control an (established) Connection object and manage the
  196. stream of data, sent or received. Based on TCP protocol
  197. whereby data is sent as a stream of bytes. This routine
  198. decides when a transfer is complete or when the stream
  199. is still pouring data in.\n\n
  200. BUFFER_SIZE defines the amount of data transferred at each
  201. step and is given in BYTES.
  202. """
  203. self.command = self._manage_connection(Connection, BUFFER_SIZE)