UniSaSA  1.0
Python Gateway
gateway.py
1 """@package docstring
2 Gateway Implementation.
3 
4 This gateway reads a request from a TCP socket,
5 forwards it to a different network
6 and sends the answer to the requestor.
7 """
8 
9 import socket
10 import threading
11 from connector import Connector
12 from typesunisasa import MessageTCP
13 from typesunisasadict import dataTypeDictionary
14 
15 
16 #only for verbose table output
17 DIC_TYPE_IO = dataTypeDictionary
18 
19 
20 class RouteTable(object):
21  """This class is an internal use only.
22 
23  You could set: START_ID, SYMBOL_SENSOR and SYMBOL_ACTUATOR.
24  """
25 
26  # _RouteTable {ID: [address, connector, sizeIODevice,
27  # byteIDSen, { IDSen : (type S, Size Out, Type Out) },
28  # byteIDAct, { IDAct : (type A, Size In/, Type IN) } ] }
29 
30  POS_ADDRESS = 0
31  POS_CONNECTOR = 1
32  POS_SIZE_IO_DEVICE = 2
33 
34  POS_SIZE_ID_SEN = 3
35  POS_DICTIONARY_SEN = 4
36  POS_TYPE_SEN = 0
37  POS_SIZE_OUT = 1
38  POS_TYPE_OUT = 2
39 
40  POS_SIZE_ID_ACT = 5
41  POS_DICTIONARY_ACT = 6
42  POS_TYPE_ACT = 0
43  POS_SIZE_IN = 1
44  POS_TYPE_IN = 2
45 
46  START_ID = 1
47 
48  SYMBOL_SENSOR = '$'
49  SYMBOL_ACTUATOR = '@'
50 
51 
52 class Gateway:
53  """Gateway implementation.
54 
55  This class can:
56  create/load/save/manage a routing table
57  routing between IP network and other networks
58  """
59 
60  def __init__(self):
61  """Constructor."""
62  self._mySocket = None
63  self._routeTable = {}
64  self._listConnectors = []
65  self._sizeByteID = 0
66  self._reservedID = {} # {ID:(addr,connector)}
67 
68  '''----- Setting stage -----'''
69 
70  def addConnector(self,newConnector):
71  """Add a connector.
72 
73  Since now the gateway can communicate with this device.
74  """
75  if isinstance(newConnector,Connector) == False :# check newConnector
76  return
77  self._listConnectors.append(newConnector)
78 
79  def removeConnectors(self):
80  """Remove all connectors."""
81  self._listConnectors = []
82 
83  def loadTable(self,filePath,findConnector=True):
84  """Load a table from a file.
85 
86  :type filePath string
87  :param filePath path ot the file
88  :type: fineConnector boolean
89  :param findConnector try to find the right device connector (see addConnector())
90  """
91  table={}
92  file = open(filePath,'r')
93 
94  # Table in file
95  # ID ; Address ; TypeConnector ; SizeIODevice ; SizeIDSen ; SizeIDAct ; SYMBOL_[SEN|ACT] ;* ID S/A ; Type S/A ; Size I/O ; Type I/O
96 
97  # _RouteTable {ID: [address, connector, sizeIODevice,
98  # byteIDSen, { IDSen : (type S, Size Out, Type Out) },
99  # byteIDAct, { IDAct : (type A, Size In/, Type IN) } ] }
100 
101  #print("Route Table {")
102  for line in file:
103  if line[0] == '#':
104  continue
105  sensors = {}
106  actuators = {}
107  device = line.split(";")
108  # ID ; Address ; Type Connector ; Size IO Device ; Size ID Sen ; Size ID ACT ;
109  table[int(device[0])] = [int(device[1]), str(device[2]), int(device[3]), int(device[4]),sensors,int(device[5]),actuators]
110  for g in range(6,len(device)-1,5): # sen/act
111  if str(device[g]) == RouteTable.SYMBOL_SENSOR: # SEN
112  sensors[int(device[g+1])] = ( int(device[g+2]), int(device[g+3]), int(device[g+4]) ) # ID S : Type S : Size Out : Type Out
113  else: #ACT
114  actuators[int(device[g+1])] = ( int(device[g+2]), int(device[g+3]), int(device[g+4]) ) # ID A : Type A : Size In : Type In
115  self._updateSizeIDDevice(int(device[0]))
116  if findConnector:
117  table[int(device[0])][RouteTable.POS_CONNECTOR] = self.getConnector( int(device[1]),str(device[2]) )
118  #print(' %s : %s' % (int(device[0]),table[int(device[0])]) )
119 
120  #print("}")
121  file.close()
122  self._routeTable=table
123 
124  def getConnector(self,address,typeConnector):
125  """Internal use only.
126 
127  Try to swap a 'Type Connector' field with an instanced Connector object (addConnector())
128  """
129  for i in range(len(self._listConnectors)):
130  if self._listConnectors[i].__class__.__name__ == typeConnector:
131  if self._listConnectors[i].verifyDevice(address):
132  typeConnector = self._listConnectors[i]
133  break
134  return typeConnector
135 
136  def addReservedID(self,ID,addr,connector):
137  """ Bind a device Address with a table ID."""
138  self._reservedID[ID] = (addr,connector)
139 
140  def verifyTable(self):
141  """Verify table consistency."""
142  delIDs = []
143  for deviceID in self._routeTable:
144  connector = self._routeTable[deviceID][RouteTable.POS_CONNECTOR]
145  if isinstance(connector,Connector) == False or connector.verifyDevice(self._routeTable[deviceID][RouteTable.POS_ADDRESS]) == False:
146  delIDs.append(deviceID)
147  for currID in range(len(delIDs)):
148  del self._routeTable[ delIDs[currID] ]
149 
150  def verifyDevices(self):
151  """Alias to verifyTable()."""
152  self.verifyTable()
153 
155  """Discovery Devices and create a new table."""
156  self.deleteTable()
158 
160  """Discovery new devices and update currentTable."""
161  devices = self.discovery()
162  self.updateTable(devices)
163 
164  def discovery(self):
165  """Discovery new devices.
166 
167  This is the parent of all discovery function.
168  :return [ [ address, connector, sizeIODevice,
169  byteIDSen, {IDSen : (type S, Size Out, Type OUT)},
170  byteIDAct, {IDAct : (type A, Size In, Type IN)} ] ]
171  """
172  devices=[]
173  for i in range(len(self._listConnectors)):
174  connector = self._listConnectors[i]
175  print(connector)
176  devices += connector.discovery()
177  return devices
178 
179  def updateTable(self,devices):
180  """Add new devices to the table
181 
182  :type devices list [ [ address, connector, sizeIODevice,
183  byteIDSen, {IDSen : (type S, Size Out, Type OUT)},
184  byteIDAct, {IDAct : (type A, Size In, Type IN)} ] ]
185  """
186  nextID = RouteTable.START_ID
187  for currDev in range(len(devices)):
188  for resID in range(len(self._reservedID)):
189  if (devices[currDev][0],devices[currDev][1]) in self._reservedID.values():
190  nextID = resID
191  while nextID in self._routeTable or nextID in self._reservedID:
192  nextID += 1
193  if devices[currDev] not in self._routeTable.values() :
194  self._routeTable[nextID] = devices[currDev]
195  self._updateSizeIDDevice(nextID)
196 
197  def _updateSizeIDDevice(self,newID):
198  """Internal use only.
199 
200  Check and set max size device ID.
201  """
202  currSizeID = newID.bit_length() // 8 + (1 if newID.bit_length() % 8 != 0 else 0)
203  self._sizeByteID = currSizeID if self._sizeByteID < currSizeID else self._sizeByteID
204 
205  def addDevice(self, newDev):
206  """Add a new nodes.
207 
208  :type newNode
209  :param newNode [ address, connector, sizeIODevice,
210  byteIDSen, {IDSen : (type S, Size Out, Type OUT)},
211  byteIDAct, {IDAct : (type A, Size In, Type IN)} ]
212  """
213  dev = [ [None for x in range(2)] for x in range(1)]
214  dev[0]=newDev
215  self.updateTable(dev)
216 
217  def addDevices(self, newNodes):
218  """Add new nodes.
219 
220  :type newNodes
221  :param newNodes [ [ address, connector, sizeIODevice,
222  byteIDSen, {IDSen : (type S, Size Out, Type OUT)},
223  byteIDAct, {IDAct : (type A, Size In, Type IN)} ] ]
224  """
225  self.updateTable(newNodes)
226 
227  def removeDevice(self,idDevice):
228  """Romove a device from the table.
229 
230  :type idDevice unsigned int
231  :param idDevice
232  """
233  del self._routeTable[ idDevice ]
234 
235  def deleteTable(self):
236  """Set current table to zero device."""
237  self._sizeByteID=0
238  self._routeTable={}
239 
240  def getTable(self):
241  """Return current table
242 
243  :return {ID: [address, connector, sizeIODevice,
244  byteIDSen, { IDSen : (type S, Size Out, Type Out) },
245  byteIDAct, { IDAct : (type A, Size In/, Type IN) } ] }
246  """
247  return self._routeTable
248 
249  def saveTable(self,path,verboseFile=True):
250  """Save current table.
251 
252  :type path string
253  :param path new file
254  :type verboseFile boolean
255  :param: verboseFile=True print a human readable file
256  """
257  new_file=""
258  file = open(path,'w')
259  # _RouteTable {ID: [address, connector, sizeIODevice,
260  # byteIDSen, { IDSen : (type S, Size Out, Type Out) },
261  # byteIDAct, { IDAct : (type A, Size In/, Type IN) } ] }
262 
263  # ID ; Address ; TypeConnector ; SizeIODevice ; SizeIDSen ; SizeIDAct ; SYMBOL_[SEN|ACT] ;* ID S/A ; Type S/A ; Size I/O ; Type I/O
264  for IDs in self._routeTable:
265  address = str(self._routeTable[IDs][RouteTable.POS_ADDRESS])
266  connector = self._routeTable[IDs][RouteTable.POS_CONNECTOR]
267  if isinstance(connector,Connector):
268  connector = self._routeTable[IDs][RouteTable.POS_CONNECTOR].__class__.__name__
269  size_io_device = str(self._routeTable[IDs][RouteTable.POS_SIZE_IO_DEVICE])
270  size_id_sen = str(self._routeTable[IDs][RouteTable.POS_SIZE_ID_SEN])
271  size_id_act = str(self._routeTable[IDs][RouteTable.POS_SIZE_ID_ACT])
272 
273  verbose =""
274  verbose ="#=======================================================================\n"
275  verbose += "# Device ID: "+ str(IDs) + "\n"
276  verbose += "# address: " + address +", Connector: "+ connector + " (size data msg : "+ size_io_device + "B)\n"
277 
278  row = ""
279  row = str(IDs) + ";" + address +";"+ connector +";"+ size_io_device +";"+ size_id_sen +";"+ size_id_act
280 
281  verbose += "# (Size ID Sensor: "+ size_id_sen + "B) Sensors:\n"
282  sensors = self._routeTable[IDs][RouteTable.POS_DICTIONARY_SEN]
283  for IDsen in sensors:
284  idsen = str(IDsen)
285  type_sen = str(sensors[IDsen][RouteTable.POS_TYPE_SEN])
286  sensor = "Sensor ID: "
287  output = "Output"
288  size_out = str(sensors[IDsen][RouteTable.POS_SIZE_OUT])
289  type_out = str(sensors[IDsen][RouteTable.POS_TYPE_OUT])
290 
291  verbose +="# {0:12s} {1:6s} (type: {2:6s} | size {3:6s}: {4:3s}B | type: {5:7s})\n".format(sensor,idsen,type_sen,output,size_out,DIC_TYPE_IO[int(type_out)])
292 
293  row += ";" + RouteTable.SYMBOL_SENSOR +";" + idsen+";"+ type_sen +";"+ size_out +";"+ type_out
294 
295  verbose += "# (Size ID Actuator: "+ size_id_act + "B) Actuators:\n"
296  actuators = self._routeTable[IDs][RouteTable.POS_DICTIONARY_ACT]
297  for IDact in actuators:
298  idact = str(IDact)
299  type_act = str(actuators[IDact][RouteTable.POS_TYPE_ACT])
300  actuator = "Actuator ID: "
301  inputStr = "Input"
302  size_in = str(actuators[IDact][RouteTable.POS_SIZE_IN])
303  type_in = str(actuators[IDact][RouteTable.POS_TYPE_IN])
304 
305  verbose +="# {0:12s} {1:6s} (type: {2:6s} | size {3:6s}: {4:3s}B | type: {5:7s})\n".format(actuator,idact,type_act,inputStr,size_in,DIC_TYPE_IO[int(type_in)])
306 
307  row += ";" + RouteTable.SYMBOL_ACTUATOR +";" + idact+";"+ type_act +";"+ size_in +";"+ type_in
308 
309  row+="\n"
310 
311  if verboseFile:
312  file.write(verbose)
313  file.write(row)
314 
315  file.close()
316 
317  '''----- Communication Stage -----'''
318 
319  def createTCPServer(self,IP,port):
320  """Create a socket to receive TCP request from UniSaSA library."""
321  # Create a TCP/IP socket
322  self._mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
323 
324  # Bind the socket to the port
325  server_address = (IP,port)
326  print('starting up on %s port %s' % server_address)
327  self._mySocket.bind(server_address)
328  # Listen for incoming connections
329  self._mySocket.listen(1)
330 
331 
332  def waitTCPClient(self):
333  """Blocking to wait a 'UniSaSA library' client."""
334  print('waiting for a connection')
335  connection, client_address = self._mySocket.accept()
336  connection.setblocking(True)
337  connection.settimeout(None)
338  print('connection from', client_address)
339  return connection
340 
341 
342  def managerCommunication(self,client):
343  """Start to serve requests from an UniSaSA Library.
344 
345  It is possible to use a multithread version."""
346  data = b''
347  print("Communication with: ", client)
348  while True:
349 
350  data = client.recv(1)
351 
352  if data == b'':
353  continue
354 
355  elif data == MessageTCP.GET_DEVICES:
356  print("Request: GET DEVICES")
357 
358  # _RouteTable {ID: [address, connector, sizeIODevice,
359  # byteIDSen, { IDSen : (type S, Size Out, Type Out) },
360  # byteIDAct, { IDAct : (type A, Size In/, Type IN) } ] }
361 
362  # Reply Message: 'GetDevices', Gateway -> Library
363 
364  # ------------------------------------------
365  # 1B size size 1B
366  # ID dev ID dev
367  # sizeIDDev numDevs *(IDDev SizeIODev ...
368  # A B C D
369  # -------------------------------------------------------
370  # 1B size size 2 B 1 B 1B
371  # ID Sen ID Sen
372  # ... SizeIDSen NumSens *(IDSen TypeSen SizeOut TypeOut) ...
373  # E F G H I J
374  # --------------------------------------------------------------
375  # 1B size size 2 B 1B 1B
376  # ID Act ID Act
377  # ... SizeIDAct NumActs *(IDAct TypeAct SizeIn TypeIn))
378  # K L M N O P
379  # --------------------------------------------------------------
380  #
381 
382 
383  answer = []
384  answer += bytes([self._sizeByteID]) # (A)
385  answer += len(self._routeTable).to_bytes(self._sizeByteID, byteorder='big') # (B)
386 
387  for deviceID in self._routeTable.keys():
388  answer += deviceID.to_bytes(self._sizeByteID, byteorder='big') # (C)
389  listDevice = self._routeTable[deviceID]
390  answer += bytes([ listDevice[RouteTable.POS_SIZE_IO_DEVICE] ]) # (D)
391 
392  answer += bytes([ listDevice[RouteTable.POS_SIZE_ID_SEN] ]) # (E)
393  sensorsDic = listDevice[RouteTable.POS_DICTIONARY_SEN]
394  answer += len(sensorsDic).to_bytes(listDevice[RouteTable.POS_SIZE_ID_SEN], byteorder='big') # (F)
395  # sensors of the device
396  for sensorID in sensorsDic:
397  listSen = sensorsDic[sensorID]
398  answer += sensorID.to_bytes(listDevice[RouteTable.POS_SIZE_ID_SEN], byteorder='big') # (G)
399  answer += listSen[RouteTable.POS_TYPE_SEN].to_bytes(MessageTCP.SIZE_TYPE_SENSOR, byteorder='big') # (H)
400  answer += bytes( [ listSen[RouteTable.POS_SIZE_OUT] ] ) # (I)
401  answer += bytes( [ listSen[RouteTable.POS_TYPE_OUT] ] ) # (J)
402 
403  answer += bytes([ listDevice[RouteTable.POS_SIZE_ID_ACT] ]) # (K)
404  actuatorsDic = listDevice[RouteTable.POS_DICTIONARY_ACT]
405  answer += len(actuatorsDic).to_bytes(listDevice[RouteTable.POS_SIZE_ID_ACT], byteorder='big') # (L)
406  # actuators of the device
407  for actuatorID in actuatorsDic:
408  listAct = actuatorsDic[actuatorID]
409  answer += actuatorID.to_bytes(listDevice[RouteTable.POS_SIZE_ID_ACT], byteorder='big') # (M)
410  answer += listAct[RouteTable.POS_TYPE_ACT].to_bytes(MessageTCP.SIZE_TYPE_ACTUATOR, byteorder='big') # (N)
411  answer += bytes( [ listAct[RouteTable.POS_SIZE_IN] ] ) # (O)
412  answer += bytes( [ listAct[RouteTable.POS_TYPE_IN] ] ) # (P)
413 
414  #print(" ", answer)
415  #print(" ", bytearray(answer))
416  client.send(bytearray(answer))
417 
418  elif data == MessageTCP.GET_SENSOR_VALUE:
419  print("Request: GET SENSOR VALUE")
420  deviceID = client.recv(self._sizeByteID)
421  deviceIDInt = int.from_bytes(deviceID,byteorder='big')
422  address = self._routeTable[deviceIDInt][RouteTable.POS_ADDRESS]
423  sensorID = client.recv(self._routeTable[deviceIDInt][RouteTable.POS_SIZE_ID_SEN])
424  connector = self._routeTable[deviceIDInt][RouteTable.POS_CONNECTOR]
425  sensorValue = connector.getSensorValue(address,sensorID)
426  #print("deviceIDInt: %s, sensorID : %s sensorValue : %s" % (deviceIDInt,sensorID,sensorValue))
427  client.send(sensorValue) #sensorValue should already have the right size
428 
429  elif data == MessageTCP.SET_ACTUATOR_VALUE:
430  print("Request: SET ACTUATOR VALUE")
431  deviceID = client.recv(self._sizeByteID)
432  deviceIDInt = int.from_bytes(deviceID,byteorder='big')
433  address = self._routeTable[deviceIDInt][RouteTable.POS_ADDRESS]
434  actuatorID = client.recv(self._routeTable[deviceIDInt][RouteTable.POS_SIZE_ID_ACT])
435  actuatorIDInt = int.from_bytes(actuatorID,byteorder='big')
436  value = client.recv(self._routeTable[deviceIDInt][RouteTable.POS_DICTIONARY_ACT][actuatorIDInt][RouteTable.POS_SIZE_IN])
437  #print("deviceIDInt: %s, actuatorIDInt : %s value : %s" % (deviceIDInt,actuatorIDInt,value))
438  connector = self._routeTable[deviceIDInt][RouteTable.POS_CONNECTOR]
439  connector.setActuatorValue(address,actuatorID,value)
440 
441  elif data == MessageTCP.MSG_TO_DEVICE:
442  #print("Request: MSG_TO_DEVICE")
443  deviceID = client.recv(self._sizeByteID)
444  deviceIDInt = int.from_bytes(deviceID,byteorder='big')
445  address = self._routeTable[deviceIDInt][RouteTable.POS_ADDRESS]
446  value = client.recv(self._routeTable[deviceIDInt][RouteTable.POS_SIZE_IO_DEVICE])
447  #print("devicIDInt: %s, value : %s" % (deviceIDInt, value) )
448  connector = self._routeTable[deviceIDInt][RouteTable.POS_CONNECTOR]
449  connector.msgToDevice(address,value)
450 
451  elif data == MessageTCP.MSG_AND_ANSWER_TO_DEVICE:
452  print("Request: MSG_AND_ANSWER_TO_DEVICE")
453  deviceID = client.recv(self._sizeByteID)
454  deviceIDInt = int.from_bytes(deviceID,byteorder='big')
455  address = self._routeTable[deviceIDInt][RouteTable.POS_ADDRESS]
456  value = client.recv(self._routeTable[deviceIDInt][RouteTable.POS_SIZE_IO_DEVICE])
457  connector = self._routeTable[deviceIDInt][RouteTable.POS_CONNECTOR]
458  #print("devicIDInt: %s, value : %s" % (deviceIDInt, value) )
459  answer = connector.msgAndAnswerToDevice(address,value)
460  client.send( answer ) #value should already have the right size
461 
462  elif data == MessageTCP.CLOSE_TCP_CONNECTION:
463  print("Request: CLOSE TCP CONNECTION")
464  client.close()
465  break
466 
467  else:
468  print("TCP Message -Not implemented- ",data)
469 
470  return True
471 
472  def manageCommunicationThread(self,client):
473  """MultiThread version of manageCommunication()."""
474  t = manageClientThread(self,client)
475  t.start()
476 
477 
478 class manageClientThread(threading.Thread):
479  """Don't use directly, use: Gateway.manageCommunicationThread()."""
480 
481  def __init__(self,gateway,client):
482  """Internal use only."""
483  super(manageClientThread, self).__init__()
484  self._gateway=gateway
485  self._client=client
486 
487  def run(self):
488  """Internal use only."""
489  self._gateway.managerCommunication(self._client)
def removeConnectors(self)
Definition: gateway.py:79
def manageCommunicationThread(self, client)
Definition: gateway.py:472
def addDevices(self, newNodes)
Definition: gateway.py:217
def saveTable(self, path, verboseFile=True)
Definition: gateway.py:249
def addReservedID(self, ID, addr, connector)
Definition: gateway.py:136
def __init__(self, gateway, client)
Definition: gateway.py:481
def discovery(self)
Definition: gateway.py:164
def deleteTable(self)
Definition: gateway.py:235
def addConnector(self, newConnector)
Definition: gateway.py:70
def loadTable(self, filePath, findConnector=True)
Definition: gateway.py:83
def addDevice(self, newDev)
Definition: gateway.py:205
def managerCommunication(self, client)
Definition: gateway.py:342
def removeDevice(self, idDevice)
Definition: gateway.py:227
def _updateSizeIDDevice(self, newID)
Definition: gateway.py:197
def discoveryAndUpdateTable(self)
Definition: gateway.py:159
def updateTable(self, devices)
Definition: gateway.py:179
def getTable(self)
Definition: gateway.py:240
def __init__(self)
Definition: gateway.py:60
def waitTCPClient(self)
Definition: gateway.py:332
def discoveryAndCreateTable(self)
Definition: gateway.py:154
def getConnector(self, address, typeConnector)
Definition: gateway.py:124
def verifyDevices(self)
Definition: gateway.py:150
def createTCPServer(self, IP, port)
Definition: gateway.py:319
def verifyTable(self)
Definition: gateway.py:140