class Dosificadora: def __init__(self): #Crear el objeto de la clase dosificadora ##Convenciones: axxxx: a significa atributo #Con-> Concentrado #Min-> Mineral #Lev-> Levadura #Puertos de control para las valvulas self.avTolva = 2 self.avMineral = 16 self.avLevadura = 27 #Puertos de asignacion de las celdas de carga self.alsensorC1 = (11, 9) #Formato tupla: (dt,sck) self.alsensorC2 = (22, 10) self.alsensorC3 = (24, 23) self.alsensorC4 = (12, 6) self.alsensorML = (19, 13) #Puertos control de motores self.amCon = (7, 8) #Formato tupla: (Encendido, velocidad) self.amMin = (20, 21) #Formato tupla: (velocidad, sentido) self.amLev = (25, 26) #Sensibilidades celdas de carga self.asMin = 1030.3320 self.asLev = 2563.3821 self.asC1 = 1 self.asC2 = 1 self.asC3 = 1 self.asC4 = 1 self.asConc = 50.380 #Valores de Tara para cada celda de carga self.asZeroMin = 0 self.asZeroLev = 0 self.asZeroA1 = 0 self.asZeroA2 = 0 self.asZeroB1 = 0 self.asZeroB2 = 0 #Masas objetivo self.aConObj = 1 self.aMinObj = 1 self.aLevObj = 1 #Parametros del filtro de media movil self.peso_k_1 = [0, 0, 0] #Formato de lista [Con,Min,Lev] self.peso_k_2 = [0, 0, 0] #Formato lista [Con,Min,Lev] #Otros atributos self.asText = "________________" #Separador de Textos self.minCon = 39 #Menor ciclo de PWM permitido para el concentrado self.maxCon = 99 #Mayor ciclo de PWM permitido para el concentrado self.razon = [ 800, 50, 10 ] #Mayor tasa de cambio permitida por el filtro tamizador #Formato lista [Con,Min,Lev] self.aConCrucero = 70 #Velocidad crucero motor Con self.aConMin = 60 #Minima velocidad para mover el motor def __del__(self): #Metodo destructor de objeto nombre = self.__class__.__name__ print nombre, "Destruido" def inicializarPuertos(self): #Encargado de iniciar el estado de los puertos de RPi. print("\n________________\nIniciando puertos\n________________\n") #Configurar puertos #Valvulas GPIO.setup(self.avTolva, GPIO.OUT) GPIO.setup(self.avMineral, GPIO.OUT) GPIO.setup(self.avLevadura, GPIO.OUT) #Motores #Concentrado GPIO.setup(self.amCon[0], GPIO.OUT) GPIO.setup(self.amCon[1], GPIO.OUT) #Mineral GPIO.setup(self.amMin[0], GPIO.OUT) GPIO.setup(self.amLev[0], GPIO.OUT) #Levadura GPIO.setup(self.amMin[1], GPIO.OUT) GPIO.setup(self.amLev[1], GPIO.OUT) #Colocar todos los puertos en BAJO "LOW". GPIO.output(self.avTolva, 0) GPIO.output(self.avMineral, 0) GPIO.output(self.avLevadura, 0) GPIO.output(self.amCon[0], 0) GPIO.output(self.amCon[1], 0) GPIO.output(self.amMin[0], 0) GPIO.output(self.amMin[1], 0) GPIO.output(self.amLev[0], 0) GPIO.output(self.amLev[1], 0) def inicializarMotores(self): #Iniciar el estado de los motores #Frecuencia de PWM self.amMinPWM = GPIO.PWM(self.amMin[0], 300) #Formato tupla: (velocidad, sentido) self.amLevPWM = GPIO.PWM(self.amLev[0], 300) #Formato tupla: (velocidad, sentido) self.amConPWM = GPIO.PWM(self.amCon[1], 250) ##Iniciar PWM en valor 0 self.amMinPWM.start(0) self.amLevPWM.start(0) self.amConPWM.start(0) def inicializarCeldas(self): #Inciar celdas de carga print( "\n________________\nIniciando celdas de carga\n________________\n" ) #Formato tupla: self.alsensorA = (dt,sck) #Celda de carga Concentrado C1 self.ahxC1 = Scale(self.alsensorC1[0], self.alsensorC1[1], 1, 80) #Celda de carga Concentrado C2 self.ahxC2 = Scale(self.alsensorC2[0], self.alsensorC2[1], 1, 80) #Celda de carga Concentrado C3 self.ahxC3 = Scale(self.alsensorC3[0], self.alsensorC3[1], 1, 80) #Celda de carga Concentrado C4 self.ahxC4 = Scale(self.alsensorC4[0], self.alsensorC4[1], 1, 80) #Celda de carga Levadura Mineral self.ahxML = Scale(self.alsensorML[0], selfalsensorML[1], 1, 80) #Inicializa, resetea y tara A print( "\n________________\nConfigurando Amplificador C1\n________________\n" ) #Resetear amplificador C1 self.ahxA.reset() self.ahxA.set_gain(gain=64) #Configurar ganancia para el canal A self.ahxA.select_channel(channel='A') self.ahxA.set_reference_unit(1) #Resetear calibracion amplificador C1 self.ahxA.select_channel(channel='B') self.ahxA.set_offset(0) self.ahxA.select_channel(channel='A') print('\tConfigurado\n') # -------------------------------------------------------------# ##Configurar amplificador C2 #Inicializa, resetea y tara amplificador C2 print( "\n________________\nConfigurando Amplificador C2\n________________\n" ) print("\tReseteando...") self.ahxA.reset() #Resetear amplificador C2 self.ahxB.set_gain(gain=64) #Configurar ganancia para el canal A self.ahxB.select_channel(channel='A') self.ahxB.set_reference_unit(1) #Resetear calibracion amplificador C2 self.asZeroB1 = self.ahxA.tare(1) #Tarar celda de carga self.ahxB.select_channel(channel='B') self.asZeroB2 = self.ahxA.tare(1) self.ahxB.set_offset(0) self.ahxB.select_channel(channel='A') print('\tConfigurado\n') ##Configurar amplificador Min Lev #Inicializa, resetea y tara Min Lev print( "\n________________\nConfigurando Amplificador Min Lev\n________________\n" ) print("\tReseteando...") self.ahxML.reset() #Resetear amplificador print('\tConfigurado') self.ahxML.set_gain(gain=64) #Configurar ganancia para el canal A self.ahxML.select_channel(channel='A') self.ahxML.set_reference_unit(1) #Calibrar celda A self.asZeroMin = self.ahxML.tare(1) #Tarar celda de carga self.ahxML.select_channel(channel='B') self.asZeroLev = self.ahxML.tare(1) self.ahxML.set_offset(0) self.ahxML.select_channel(channel='A') def encenderMotores(self, motor): #Metodo que activa los motores #Entrada: self-> Objeto propio de python # motor-> Selector del motor: # Con: Concentrado, Min: mineral Lev: levadura if (motor == 'Con'): if self.aConObj != 0: #Encendido motor Con velocidad = self.aConCrucero self.amConPWM.ChangeDutyCycle(velocidad) GPIO.output(self.amCon[0], 1) else: print "Masa es 0, concentrado no encendido" return if (motor == 'Min'): if self.aMinObj != 0: self.amMinPWM.ChangeFrequency(750) self.amMinPWM.ChangeDutyCycle(50) else: print "Masa igual a 0, mineral no encendido" return if (motor == 'Lev'): if self.aLevObj != 0: self.amLevPWM.ChangeFrequency(750) self.amLevPWM.ChangeDutyCycle(50) else: print "Masa igual a 0, levadura no encendido" return else: print("Motor no encontrado") def desacelerarMotores(self, motor): #Metodo que desacelera los motores if (motor == 'Con'): velocidad = self.aConMin self.amConPWM.ChangeDutyCycle(velocidad) return if (motor == 'Min'): self.amMinPWM.ChangeFrequency(200) self.amMinPWM.ChangeDutyCycle(50) return if (motor == 'Lev'): self.amLevPWM.ChangeFrequency(200) self.amLevPWM.ChangeDutyCycle(50) return else: print "Motor no encontrado" return def apagarMotores(self, motor, condicion): #Detener motores #Entradas: motor: Seleccion del motor deseado # Con -> Concentrado # Min -> Mineral # Lev -> Levadura # Condicion: Indica si el motor no fue apagado en la iteracion anterior if (motor == 'Con'): GPIO.output(self.amCon[0], 0) self.amConPWM.stop() if condicion: print("Concentrado apagado") return if (motor == 'Min'): self.amMinPWM.ChangeFrequency(50) self.amMinPWM.ChangeDutyCycle(0) if condicion: print("Mineral apagado") return if (motor == 'Lev'): self.amLevPWM.ChangeFrequency(50) self.amLevPWM.ChangeDutyCycle(0) if condicion: print("Levadura apagado") return else: print("Motor no encontrado") return def abrirCerrarValvulas(self, valvula, condicion): #Metodo de abrir y cerrar valvulas #Entradas: valvula: # Tolv -> Puerta de la tolva Romana # Min -> Compuerta del mineral # Lev -> Compuerta levadura # condicion: # 0 -> Valvula cerrada # 1 -> Valvula abierta if (valvula == 'Tolv'): GPIO.output(self.avTolva, condicion) return if (valvula == 'Min'): GPIO.output(self.avMineral, condicion) return if (valvula == 'Lev'): GPIO.output(self.avLevadura, condicion) return else: print("Valvula incorrecta") def cambiarSensibilidad(self, celda, sensibilidad): #Metodo para cambiar la sensibilidad de la celda de carga: (depuracion) #Formato de celda: 'Min','Lev','A','B' #Entradas: celda: A1, A2, B1, B2, Min, Lev print("Cambiando sensibilidad") if (celda == 'A1'): self.asA1 = sensibilidad self.axA.select_channel(channel='A') self.axA.set_scale_ratio(sensibilidad) return if (celda == 'A2'): self.asA2 = sensibilidad self.axA.select_channel(channel='B') self.axA.set_scale_ratio(sensibilidad) return if (celda == 'B1'): self.asB1 = sensibilidad self.axB.select_channel(channel='A') self.axB.set_scale_ratio(sensibilidad) return if (celda == 'B2'): self.asB2 = sensibilidad self.axB.select_channel(channel='B') self.axB.set_scale_ratio(sensibilidad) return if (celda == 'Min'): self.asMin = sensibilidad self.axML.select_channel(channel='A') self.axML.set_scale_ratio(sensibilidad) return if (celda == 'Lev'): self.asLev = sensibilidad self.axML.select_channel(channel='A') self.axML.set_scale_ratio(sensibilidad) return else: print("Celda no encontrada") def leerMineral(self, lecturas): #Leer el peso del mineral en gramos. #Entrada: lecturas -> Cantidad de veces que el sensor se lee antes de retornar #Mineral puerto A del sensor self.ahxML.select_channel(channel='A') masaMin = -( (self.ahxML.get_value(lecturas)) - self.asZeroMin) / self.asMin return masaMin def leerLevadura(self, lecturas): #Leer el peso del mineral en gramos. #Entrada: lecturas -> Cantidad de veces que el sensor se lee antes de retornar self.ahxML.select_channel(channel='B') masaLev = (self.ahxML.get_value(lecturas) - self.asZeroLev) / self.asLev return masaLev def leerConcentrado(self, lecturas): #Leer el peso del concentrado en gramos. #Entrada: lecturas -> Cantidad de veces que el sensor se lee antes de retornar self.ahxA.select_channel(channel='A') Conc1 = (self.ahxA.get_value(lecturas) - self.asZeroA1) self.ahxA.select_channel(channel='B') Conc2 = (self.ahxA.get_value(lecturas) - self.asZeroA2) self.ahxB.select_channel(channel='A') Conc3 = (self.ahxB.get_value(lecturas) - self.asZeroB1) self.ahxB.select_channel(channel='B') Conc4 = (self.ahxB.get_value(lecturas) - self.asZeroB1) Conc = (Conc1 + Conc2 + Conc3 + Conc4) / 2 * self.asConc #Nota: De momento se estan leyendo solo las celdas de los puertos A del concentrado # las celdas B presetan problemas de retardos en las lecturas return Conc def cerrarSteppers(self): #Metodo para apagar puertos de velocidad de los motores self.amMinPWM.stop() self.amLevPWM.stop() self.amConPWM.stop() def leer4Concentrado(self): #Metodo para leer por separado cada celda de carga del concentrado (depuracion) self.ahxA.select_channel(channel='A') Conc1 = (self.ahxA.get_value(1) - self.asZeroA1) self.ahxA.select_channel(channel='B') Conc2 = (self.ahxA.get_value(1) - self.asZeroA2) self.ahxB.select_channel(channel='A') Conc3 = (self.ahxB.get_value(1) - self.asZeroB1) self.ahxB.select_channel(channel='B') Conc4 = (self.ahxB.get_value(1) - self.asZeroB1) print("%d\t%d\t%d\t%d" % (Conc1, Conc2, Conc3, Conc4)) def leer4ConcentradoRaw(self, lecturas): #Metodo para leer cada celda del concentrado sin restar tara (depuracion) self.ahxA.select_channel(channel='A') Conc1 = (self.ahxA.get_value(lecturas)) self.ahxA.select_channel(channel='B') Conc2 = (self.ahxA.get_value(lecturas)) self.ahxB.select_channel(channel='A') Conc3 = (self.ahxB.get_value(lecturas)) self.ahxB.select_channel(channel='B') Conc4 = (self.ahxB.get_value(lecturas)) print("%d\t%d\t%d\t%d" % (Conc1, Conc2, Conc3, Conc4)) return def tararConcentrado(self, lecturas=30): #Metodo para tarar los amplificadores del concentrado self.ahxA.select_channel(channel='A') self.asZeroA1 = self.ahxA.get_value(lecturas) self.ahxA.select_channel(channel='B') self.asZeroA2 = self.ahxA.get_value(lecturas) self.ahxB.select_channel(channel='A') self.asZeroB1 = self.ahxB.get_value(lecturas) self.ahxB.select_channel(channel='B') self.asZeroB2 = self.ahxB.get_value(lecturas) def tararMineral(self, lecturas=30): #print self.ahxML.GAIN #Metodo para tarar mineral self.ahxML.select_channel(channel='A') self.asZeroMin = self.ahxML.get_value(lecturas) print("\tTara del mineral %d" % (self.asZeroMin)) def tararLevadura(self, lecturas=30): #Metodo para tarar levdura self.ahxML.select_channel(channel='B') self.asZeroMin = self.ahxML.get_value(lecturas) print("\tTara de la levadura %d" % (self.asZeroMin)) def filtradorTamizador(self, dato, alimento): #Metodo para filtrar y tamizar los valores de las celdas de carga #Se aplica un filtro de media movil con tres periodos, #luego se eliminan las lecturas que presenten cambios abruptos respecto de los valores predecesores. if alimento == 'Con': #Tamizar if ((abs(dato - self.peso_k_1[0])) > self.razon[0]): dato = self.peso_k_1[0] print "Tamizado" #Filtrar concentrado = (dato + self.peso_k_1[0] + self.peso_k_2[0]) / 3 self.peso_k_2[0] = self.peso_k_1[0] self.peso_k_1[0] = concentrado return concentrado if alimento == 'Min': #Tamizar if ((abs(dato - self.peso_k_1[1])) > self.razon[1]): dato = self.peso_k_1[1] print "Tamizado" #Filtrar mineral = (dato + self.peso_k_1[1] + self.peso_k_2[1]) / 3 self.peso_k_2[1] = self.peso_k_1[1] self.peso_k_1[1] = mineral return mineral if alimento == 'Lev': #Tamizar if ((abs(dato - self.peso_k_1[2])) > self.razon[2]): dato = self.peso_k_1[2] print "Tamizado" #Filtrar levadura = (dato + self.peso_k_1[2] + self.peso_k_2[2]) / 3 self.peso_k_2[2] = self.peso_k_1[2] self.peso_k_1[2] = levadura return levadura else: print("Alimento no encontrado") def inRangeCoerce(self, dato, minimo=0.0, maximo=100.0): #Metodo que limita los valores de una variable if dato > maximo: return maximo if dato < minimo: return minimo else: return dato def normalizarVelocidadConcentrado(self, dato): #Metodo para normalizar los valores del concentrado #Debido a la electronica, el valor de PWM permitido es entre 39 y 99. #Fuera de esos valores comienza a presentarse comportamiento erratico. dato = self.inRangeCoerce(dato, 0, 100) dato = (self.maxCon - self.minCon) / 100 * dato + self.minCon return dato #Metodos para resumir bloques de la secuencia def tararCeldas(self): #Metodo para tarar todas las cedas de carga. Permite no hacerlo desde el main self.leerMineral(30) print("________________\nTarando Concentrado\n________________\n") self.tararConcentrado(30) print("Zero A1 ", self.asZeroA1) print("Zero A2 ", self.asZeroA2) print("Zero B1 ", self.asZeroB1) print("Zero B2 ", self.asZeroB2) print("________________\nTarando Mineral\n________________\n") self.tararMineral(30) print("________________\nTarando Levadura\n________________\n") self.tararLevadura(30) self.tararMineral(30) def resetearCeldas(self): print("Reseteando celdas de carga concentrado") #Celdas del concentrado self.hxC1.turnOff() time.sleep(0.5) self.hxC1.turnOn() time.sleep(0.5) self.hxC1.turnOff() time.sleep(0.5) self.hxC1.turnOn() time.sleep(0.5) self.hxC2.turnOff() time.sleep(0.5) self.hxC2.turnOn() time.sleep(0.5) self.hxC3.turnOff() time.sleep(0.5) self.hxC3.turnOn() time.sleep(0.5) self.hxC4.turnOff() time.sleep(0.5) self.hxC4.turnOn() time.sleep(0.5) print("Reseteando celdas de carga Mineral y Levadura") self.hxML.turnOff() time.sleep(0.5) self.hxML.turnOn() time.sleep(0.5)
class Trial(): """ A class specifying a Trial and methods related to a trial. when a trial is created, it only needs to be drawn and checked for input. """ def __init__(self, win, colours, sequence, mouse, output_stream, clock, location_sequence, manager, override_text=None): """ Initializes a Trial object. Params: win: A window object the form should be drawn onto colours: A tuple of colour strings. (names, hex, or rgb) sequence: A sequence string containing only 0's and 1's mouse: A mouse object for checking against mouse input output_stream: A dictionary object containing lists for writing data into clock: A clock object to get the current time """ self.manager = manager self.clock = clock self.win = win self.mouse = mouse self.output = output_stream self.colours = colours self.sequence = sequence self.location_sequence = location_sequence self.place_boxes() info_text = Constants.TRIAL_BASE_INFO if override_text is not None: info_text = override_text else: if self.manager.failed_last == True: info_text += Constants.FAILED_TRIAL else: info_text += Constants.COMPLETED_TRIAL if len(sequence) != Constants.MATRIX[0] * Constants.MATRIX[1]: info_text += Constants.TIMED_TRIAL_INFO banner = Constants.BANNER_TIMED else: info_text += Constants.NOT_TIMED_INFO banner = Constants.BANNER_NOT_TIMED self.manager.scene = InfoScene(self.win, self, self.mouse, info_text) self.create_stimuli(banner) def to_trial(self): self.manager.scene = self def place_boxes(self): """ Places a centered grid of box stimuli and returns a list of the created grid. Params: None Returns: A python list containing all the boxes that was created for the grid """ boxes = [] y_offset = ( Constants.WINDOW_SIZE[1] / 2 ) - 2 * Constants.SQUARE_SIZE # The starting offset of the first square. (Upper-left) y_spacing = abs( y_offset * 2 / (Constants.MATRIX[1] - 1) ) #Has the value for the spacing between the squares. The formula calculates even distribution on all monitors #Places the squares in two-dimensional array structure for _ in range(Constants.MATRIX[1]): x_offset = (-Constants.WINDOW_SIZE[0] / 2) + 2 * Constants.SQUARE_SIZE x_spacing = abs(x_offset * 2 / (Constants.MATRIX[0] - 1)) for _ in range(Constants.MATRIX[0]): box = visual.Rect( self.win, width=Constants.SQUARE_SIZE, height=Constants.SQUARE_SIZE, pos=[ x_offset + Constants.CENTER_OFFSET[0], y_offset + Constants.CENTER_OFFSET[1] ], # Sets the position to the offset values in addition to adding a global offset to each box to control grid location fillColor="#BEBEBE") boxes.append(box) x_offset += x_spacing y_offset -= y_spacing self.grid = boxes """ Creates the confidence scales """ def create_stimuli(self, banner): """ Creates all stimuli that is to be drawn by the Trial class and stores them as attributes and into lists for easy drawing. Params: None Returns: None """ banner_text = visual.TextStim(self.win, text=banner, pos=[0, 500], height=40, wrapWidth=Constants.WINDOW_SIZE[0]) next_box_text = visual.TextStim(self.win, text="Show next box", pos=[0, -300]) choice_text0 = visual.TextStim( self.win, text='Press the circle if you believe it is the dominant colour:', pos=[-600, -300]) choice_text1 = visual.TextStim( self.win, text='Press the circle if you believe it is the dominant colour:', pos=[600, -300]) self.button0 = visual.Circle(self.win, radius=50, pos=[-600, -400], fillColor=self.colours[0]) self.button1 = visual.Circle(self.win, radius=50, pos=[600, -400], fillColor=self.colours[1]) self.continue_box = visual.Rect(self.win, pos=[0, -300], width=Constants.SQUARE_SIZE, height=Constants.SQUARE_SIZE / 2, fillColor="black") rating_text0 = visual.TextStim(self.win, text=f"More \n{self.colours[2]}", pos=[-800, -400], color=self.colours[0]) rating_text1 = visual.TextStim(self.win, text=f"More \n{self.colours[3]}", pos=[800, -400], color=self.colours[1]) self.rating_scale = Scale(self.win, self.colours) self.rating_stims = [rating_text0, rating_text1, banner_text ] + self.grid self.drawables = [self.continue_box, next_box_text, banner_text ] + self.grid self.choice_stims = [ self.button0, self.button1, choice_text0, choice_text1 ] self.boxes_revealed = 0 def check_input(self): """ Checks input for all components of the trial on screen except for the rating scale. It also writes to the output if a trial is failed. Params: None Returns: bool: indicating wether the Trial has ended. A true value indicates the trial has ended """ if (self.mouse.isPressedIn(self.button0, buttons=[0]) and self.button0 in self.drawables): [ self.output["Decision"].append(-1) for _ in range(len(self.output["Box_Num"]) - 1) ] self.output["Decision"].append(self.colours[2]) self.manager.completed_trial(False) return elif (self.mouse.isPressedIn(self.button1, buttons=[0]) and self.button0 in self.drawables): [ self.output["Decision"].append(-1) for _ in range(len(self.output["Box_Num"]) - 1) ] self.output["Decision"].append(self.colours[3]) self.manager.completed_trial(False) return elif (len(self.sequence) == 0 and self.mouse.isPressedIn(self.continue_box, buttons=[0])): self.output["Box_Num"].append(self.boxes_revealed + 1) self.output["Reaction_time"].append(-1) self.output["Probability_Estimates"].append(-1) [ self.output["Decision"].append(-1) for _ in self.output["Box_Num"] ] self.manager.completed_trial(True) return if 'escape' in event.getKeys(): core.quit() if self.mouse.isPressedIn(self.continue_box, buttons=[0]): self.next_box() box_seen = self.clock.getTime() self.get_rating() rating_given = self.clock.getTime() self.output["Reaction_time"].append(rating_given - box_seen) def next_box(self): """ Opens the next box. Reads the sequence data and gives the appropriate box its colour and writes data to the output. Params: None Returns: None """ if (self.boxes_revealed == 0): self.drawables += self.choice_stims location = self.location_sequence[0] box = self.grid[location - 1] if (self.sequence[0] == '0'): box.fillColor = self.colours[0] elif (self.sequence[0] == '1'): box.fillColor = self.colours[1] else: raise SyntaxError( "The given input does not match program specification for a sequence. Allowed values: 0 and 1" ) self.boxes_revealed += 1 self.output["Box_Num"].append(self.boxes_revealed) self.sequence = self.sequence[1:] self.location_sequence = self.location_sequence[1:] def get_rating(self): """ Draws the rating scale and waits for input. Once the input is given, it is written to the output and the method returns. Params: None Returns: None """ event.clearEvents() while (self.rating_scale.noResponse() == True): if 'escape' in event.getKeys(): core.quit() self.rating_scale.draw() [x.draw() for x in self.rating_stims] self.win.flip() self.output["Probability_Estimates"].append( self.rating_scale.getRating()) self.rating_scale.reset() event.clearEvents( ) #Event queue is cleared to prevent keypresses from rating period to be carried forward def save(self): return self.output def draw(self): """ Draws the Trial to a window. Params: None Returns: None """ for drawable in self.drawables: drawable.draw()