def callback(self, ch, method, properties, body): Mesh.consummed += 1 if Mesh.consummed % 100 == 0: #display mesh status each 100 models received Mesh.required_amount = SchedulerState.get_amount() Mesh.print_mesh_info() # uncommment to reduce the amount of frames send during the esp declaration phase # if Mesh.comp < Mesh.required_amount : # print_flush("Not enough pixels on the mesh network to display the model") # return if Websock.should_get_deco(): Listen.deco = json.loads(Websock.get_deco()) b = body.decode('ascii') self.model.set_from_json(b) tmp = Websock.get_esp_state() if tmp != None and eval(tmp) != self.previous_state: Mesh.change_esp_state = True print_flush("tmp != None, tmp = {}".format(tmp)) self.previous_state = eval(tmp) else: Mesh.change_esp_state = False if Mesh.change_esp_state: # A procedure has started (ie AMA.py is launched) and both the mesh network and the serveur have to be ready self.procedures_manager() elif (Mesh.ama == 1): # A procedure is running self.ama_care() elif (Mesh.addressed): # Production mod : all pixels are addressed Mesh.sequence = (Mesh.sequence + 1) % 65536 array = slef.msg.color(self.model._model, Mesh.sequence, Mesh.pixels, Listen.unk) self.mesh_conn.send(array) else: # Temporisation required between the launching of AMA.py and the frist model matching the procedure arrives print_flush("{} : It is not the time to send colors".format( Mesh.consummed))
class Mesh(Thread): socket = None #socket bind to mesh network through AP mac_root = '' #esp32 root mac address sequence = 0 # sequence number of the COLOR frame pixels = SchedulerState.get_pixels_dic() #pixels addressed and connected required_amount = SchedulerState.get_amount( ) #number of pixel required by the administrator consummed = 0 #model consummed on RabbitMQ comp = 0 # pixel amount #Manage adressing procedures addressed = None #Tels if the pixels are addressed or not ama = 0 #fluctuates between 0 and 3 : 0 => NEVER_addressed; 1 => AMA_INIT; 2 => AMA_COLOR; 3 => HAR change_esp_state = False #Order from ama.py to shift ESP in other state # Initialisation of Mesh instance requires seting up several connections to be in operating mode # A TCP connection for communication with the mesh network # A RabbitMQ connection for models recepetion # Reddis connection is managed by Websock class and static methods def __init__(self, conn, addr): Thread.__init__(self) print_flush("Pixels :", Mesh.pixels) #Manage adressing procedures Mesh.addressed = not (Mesh.pixels == {}) self.ama_check = 0 self.previous_state = 1 self.model = Model(SchedulerState.get_rows(), SchedulerState.get_cols()) #Communication with mesh network config self.msg = Frame() self.mesh_conn = conn self.mesh_addr = addr self.l = Listen(conn) self.l.start() self.stopped = False #Communication with RabbitMQ config self.channel = None self.connection = None credentials = pika.PlainCredentials( os.environ['RABBITMQ_DEFAULT_USER'], os.environ['RABBITMQ_DEFAULT_PASS']) self.params = pika.ConnectionParameters(host='rabbit', credentials=credentials, heartbeat=0) #Determine if the current model has a pattern matching with the expected one. # This function is used only in AMA or HAR procedure. It is called in ama_care function which handle models during those procedures. #The pattern to match is set in the instance attribut ama_check. # If ama_check is set to 0, Mesh class is looking for models having : 1 red pixel, x green pixel (0 <= x < required_amount ) and special pixels (set at -1) # else Mesh is looking for models having : x green pixels and the rest is colored in black def ama_model(self): i = self.model.get_height() - 1 if (self.ama_check == 0): green = red = 0 while (i >= 0): j = self.model.get_width() - 1 while (j >= 0): tmp = self.model.get_pixel(i, j) # tmp = (R, G, B) if ((tmp[0] + tmp[1] + tmp[2]) == -3): # special "color" -1 return True elif (tmp[0] == 0 and tmp[1] == 1 and tmp[2] == 0): # green green += 1 elif (tmp[0] == 1 and tmp[1] == 0 and tmp[2] == 0): # red red += 1 else: return False j -= 1 i -= 1 return (green == Mesh.required_amount - 1 and red == 1) elif (self.ama_check == 1): while (i >= 0): j = self.model.get_width() - 1 while (j >= 0): tmp = self.model.get_pixel(i, j) if (((tmp[0] + tmp[1] + tmp[2]) != 0) and (tmp[0] != 1 and tmp[1] + tmp[2] != 0) and (tmp[1] != 1 and tmp[0] + tmp[2] != 0)): return False j -= 1 i -= 1 return True #Is called to manage the addressing procedures. # Get from Reddis the newly update pixel dictionnary and unknown dictionnary # this is a prerequise to build the COLOR frame and ensure that addressing procedures are well executed. # The type of model expected is also get from Reddis as it is set by the F-app AMA.py # The model received from RabbitMQ is check by ama_model. It necessary because RabbitMQ is refreshed at a frequency of 6Hz, # which introduce a delay between the reception of the expected model and the first model matching. def ama_care(self): #Get the new pixel addressed positions tmp = Websock.get_pixels() if tmp != None and tmp != {}: Mesh.pixels = json.loads(tmp) tmp = json.loads(Websock.get_pos_unk()) if tmp != None: Listen.unk = tmp #Get the model format to check tmp = Websock.get_ama_model() if tmp != None: self.ama_check = eval(tmp)['ama'] if self.ama_model(): # send a COLOR frame only if the model match the expected model Mesh.sequence = (Mesh.sequence + 1) % 65536 array = self.msg.color(self.model._model, Mesh.sequence, Mesh.pixels, Listen.unk, self.ama_check) self.mesh_conn.send(array) # This function is the manager of the different procedures implemented. It puts the server and the mesh network in the # right configuration for the required procedure by putting esp in the right state and manage the dictionnary def procedures_manager(self): Mesh.ama += 1 if Mesh.ama == 1: #AMA procedure starts print_flush("START AMA") Mesh.addressed = False Mesh.print_mesh_info() array = self.msg.ama(c.AMA_INIT) self.mesh_conn.send(array) elif Mesh.ama == 2: # Ends adressing procedures Mesh.addressed = True Mesh.print_mesh_info() array = self.msg.ama(c.AMA_COLOR) self.mesh_conn.send(array) print_flush("END addressing procedure") else: # HAR procedure starts print_flush("START HAR") Mesh.ama = 1 Mesh.addressed = False Mesh.print_mesh_info() array = self.msg.har(Mesh.mac_root, c.STATE_CONF) self.mesh_conn.send(array) print_flush(Listen.unk.keys(), Listen.deco) # The pixel in deco are one by one being forgotten and their index is attributed to one of the unknown for mac in Listen.unk.keys(): if len(Listen.deco) > 0: pixel_deco = Listen.deco.popitem() print_flush("Adding new element") print_flush(pixel_deco) print_flush("Inserted unknwon card at {0}".format( pixel_deco[1][1])) Listen.unk[mac] = ((-1, -1), pixel_deco[1][1]) array = self.msg.install_from_mac(mac, pixel_deco[1][1]) self.mesh_conn.send(array) Websock.send_pos_unk(Listen.unk) Mesh.print_mesh_info() array = slef.msg.ama(c.AMA_INIT) self.mesh_conn.send(array) #Invoque whenever a model is received from RabbitMQ, the callback function is the core. #Due to the absence of Reddis notification the callnack funciton is used to check if something have changed # - The deconnected pixel dictionnary has to be get from Reddis if a HAR procedure is at stake, because the unknown pixel have taken # their indexes in the routing table. # - The esp_state is the boolean that determines if a pocedure has started def callback(self, ch, method, properties, body): Mesh.consummed += 1 if Mesh.consummed % 100 == 0: #display mesh status each 100 models received Mesh.required_amount = SchedulerState.get_amount() Mesh.print_mesh_info() # uncommment to reduce the amount of frames send during the esp declaration phase # if Mesh.comp < Mesh.required_amount : # print_flush("Not enough pixels on the mesh network to display the model") # return if Websock.should_get_deco(): Listen.deco = json.loads(Websock.get_deco()) b = body.decode('ascii') self.model.set_from_json(b) tmp = Websock.get_esp_state() if tmp != None and eval(tmp) != self.previous_state: Mesh.change_esp_state = True print_flush("tmp != None, tmp = {}".format(tmp)) self.previous_state = eval(tmp) else: Mesh.change_esp_state = False if Mesh.change_esp_state: # A procedure has started (ie AMA.py is launched) and both the mesh network and the serveur have to be ready self.procedures_manager() elif (Mesh.ama == 1): # A procedure is running self.ama_care() elif (Mesh.addressed): # Production mod : all pixels are addressed Mesh.sequence = (Mesh.sequence + 1) % 65536 array = slef.msg.color(self.model._model, Mesh.sequence, Mesh.pixels, Listen.unk) self.mesh_conn.send(array) else: # Temporisation required between the launching of AMA.py and the frist model matching the procedure arrives print_flush("{} : It is not the time to send colors".format( Mesh.consummed)) #prints information relative to the mesh current state (server point of view) @staticmethod def print_mesh_info(): print_flush(" ========== Mesh ==========") print_flush("-------- Is mesh initialized :") print_flush(Mesh.addressed) print_flush("-------- Color frame sent : ") print_flush(Mesh.consummed) print_flush("-------- Pixels amount declared ?") print_flush(Mesh.comp) print_flush("-------- Pixels amount required ?") print_flush(Mesh.required_amount) print_flush("-------- Pixels?") print_flush(Mesh.pixels) print_flush("-------- Pixels deconnected?") print_flush(Listen.deco) print_flush("-------- Pixels unknown?") print_flush(Listen.unk) #starts the connection to RabbitMQ def run(self): try: self.connection = pika.BlockingConnection(self.params) self.channel = self.connection.channel() self.channel.exchange_declare(exchange='pixels', exchange_type='fanout') result = self.channel.queue_declare(exclusive=True, arguments={"x-max-length": 1}) queue_name = result.method.queue self.channel.queue_bind(exchange='pixels', queue=queue_name) self.channel.basic_consume(self.callback, queue=queue_name, no_ack=True) Mesh.print_mesh_info() print_flush( 'Waiting for pixel data on queue "{}".'.format(queue_name)) self.channel.start_consuming() except Exception as e: if self.channel is not None: self.channel.close() if self.connection is not None: self.connection.close() raise e #Close nicely the connections with other services before killing the thread def close_socket(self): print_flush("exiting thread, closing connection") if self.mesh_conn is not None: self.mesh_conn.close() if self.channel is not None: self.channel.close() if self.connection is not None: self.connection.close() print_flush("Closed connection, exiting thread...") self.stopped = True