diff --git a/dash.wav b/dash.wav new file mode 100644 index 0000000000000000000000000000000000000000..64fb48cf2a489496fa9311cc665a4db313a77c73 Binary files /dev/null and b/dash.wav differ diff --git a/dicts.py b/dicts.py index 8d2aa09a8cd843360faae01a3953bbd911f3058e..b99a9ca56254af1be4eb318d883572f819cca64c 100644 --- a/dicts.py +++ b/dicts.py @@ -95,37 +95,39 @@ morse = { #Comandos especiales del programa '.-.-': 'EOM', '---.': 'MAYUS', + '------' : 'SALIR', } texto = { #tiempo entre letra y letra en módulo teclado - 'keyboardBig' : """Módulo teclado. Aquà los caracteres se ingresan mediante código Morse, es decir, puntos y guiones. Un soplido equivale a un guión y un aspiro a un punto. + 'keyboardBig' : """Instrucciones para módulo Teclado: -Para ver la tabla de equivalencias secuencia Morse a caracteres, presione el botón "Guia Morse" ubicado en la parte inferior derecha del programa. +Aquà los caracteres se ingresan mediante código Morse, es decir, puntos y guiones. Un soplido equivale a un guión y un aspiro a un punto. Para ver la tabla de equivalencias secuencia Morse a caracteres, presione el botón "Guia Morse" ubicado en la parte inferior derecha del programa. -Cada %s segundos el programa traduce la secuencia ingresada a un caracter y lo ingresa. Si se equivocó y no quiere ingresarlo, puede introducir siete o más señales Morse para cancelarlo. Si no desea esperar los segundos para que su secuencia sea interpretada, puede pulsar el botón de cambio para saltear la espera. - -En futuras versiones este botón será reemplazado por otro elemento accesible. +Cada %s segundos el programa traduce la secuencia ingresada a un caracter y lo ingresa, indicado sonoramente por tres puntos Morse. El indicador de progreso en la parte superior derecha indica el tiempo que le queda para ingresar su caracter. Si se equivocó y no quiere ingresarlo, puede introducir siete o más señales Morse para cancelarlo. Por defecto está activado el bloqueo de mayúsculas. Para desactivarlo, ingrese ---. (guión guión guión punto); si desea activarlo de vuelta, ingrese la misma secuencia nuevamente. -Para volver al módulo Mouse, ingrese la secuencia EOM .-.- (punto guión punto guión) +Para volver al módulo Mouse, ingrese la secuencia EOM .-.- (punto guión punto guión). + +Para salir del programa, ingrese ------ (6 guiones). -Nota: Los botones Guardar, GuÃa Morse y Salir no tienen efecto en el modo teclado. Para utilizarlos, cambie al modo mouse.""" %keyTimeout, +Nota: Los botones Guardar, Cambiar a teclado, GuÃa Morse y Salir no tienen efecto en el modo teclado. Para utilizarlos, cambie al modo mouse.""" %keyTimeout, #el % está para sustituir ese valor por el contenido en los ajustes, al estilo printf de C - 'mouseBig' : """Módulo Mouse. + 'mouseBig' : """Instrucciones para módulo Mouse: -Sople para cambiar de etapa. Aspire para hacer click. -Aspire de forma seguida por %s segundos para salir. +Sople para cambiar de etapa. Aspire para hacer click. Aspire de forma seguida por %s segundos o presione "Cambiar a teclado" para utilizar el teclado. Presione 'Salir' para cerrar el programa. -Este módulo está dividido en tres etapas: +Este módulo está dividido en tres etapas. En todos los casos hacer click mediante aspirado hace que se regrese a la etapa 0. Etapa 0: Etapa inicial. El cursor se mantiene quieto. Etapa 1: El cursor se mueve hacia la derecha, y se puede detener pasando de etapa o haciendo click mediante aspirado. Etapa 2: El cursor se mueve hacia abajo, y se puede detener pasando de etapa o haciendo click mediante aspirado. -En todos los casos hacer click mediante aspirado hace que se regrese a la etapa 0.""" %sipTimeout, +Siempre que el cursor llegue a algún borde, seguirá su camino por el contrario. Por ejemplo, si el cursor se está moviendo hacia la derecha y llega al borde derecho, se moverá al borde izquierdo y continuará moviéndose a la derecha, manteniendo su posición vertical. + +Notar que el cuadro superior al de ajustes es de uso del módulo teclado, y no tiene ningún propósito al utilizar el mouse.""" %sipTimeout, } diff --git a/dot.wav b/dot.wav new file mode 100644 index 0000000000000000000000000000000000000000..132f8d184259456baac1be44f5c7f789dba9772c Binary files /dev/null and b/dot.wav differ diff --git a/principal.py b/principal.py index e3f60ee9d75e98891e0b7e153a0d8b649bd1e730..0e65bb1b6048882f9c43bb0f0a17d693b78694e7 100755 --- a/principal.py +++ b/principal.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- #Implementación de teclado y mouse virtual controlado por un sensor Sip&Puff conectado a la placa USB4Butiá. -#Parte del proyecto Sip&Puff4Butiá. +#Parte del proyecto Sip&Puff4Butiá. Entrada de Wiki Butiá disponible en https://sipandpuff4butia.santiagofreire.com/. #Desarrollado por Santiago Freire <santiago.freire@fing.edu.uy> import sys @@ -30,15 +30,20 @@ robot = usb4butia.USB4Butia() #Para poder mover el cursor a los bordes de la pantalla pyautogui.FAILSAFE = False + #Chequeo inicial if robot.getButton(5) == -1: if robot.getButton(4) == -1: - print "Soplido y Aspirado no conectados." + sg.PopupAutoClose('Soplido y Aspirado no conectados. Saliendo.', keep_on_top=True) quit() else: - print "Soplido no conectado" + sg.PopupAutoClose('Soplido no conectado. Saliendo.', keep_on_top=True) quit() +elif robot.getButton(4) == -1: + sg.PopupAutoClose('Aspirado no conectado. Saliendo.', keep_on_top=True) + +###Función para guardar ajustes en settings.json def guardarAjustes(newSettings): #Los nuevos ajustes los recibo en formato JSON @@ -70,7 +75,9 @@ def guardarAjustes(newSettings): #Chequeo de rango #El chequeo de rango se hace para evitar que se ingresen valores erróneos que darÃan errores en el programa o harÃan imposible su uso. En el caso de que se considere necesario, se podrÃan cambiar estos lÃmites en este mismo archivo. - if keyTimeout >= 3 and keyTimeout <= 60: + #Es 5 porque es el mÃnimo de tiempo en que se puede ingresar un número. Por ejemplo, si el usuario accidentalmente ingresa un valor muy bajo en los ajustes del keyTimeout, puede hacer EOM (4 segundos mÃnimo), mover el mouse hasta el cuadro de ajustes y poner un número más alto (ej: 9, ----. , 5 segundos mÃnimo) + #Como cada guión o punto tiene una separación de poco menos de un segundo entre sÃ, es que esto es de esta forma. + if keyTimeout >= 5 and keyTimeout <= 60: saveKT = True else: saveKT = False @@ -118,14 +125,15 @@ def guardarAjustes(newSettings): with open('settings.json','w') as setDict: json.dump(resSettings,setDict) +###Módulo teclado def keyboard(window,salgo): cambiarTextoGUI('_OUTPUT_',window,dicts.texto.get('keyboardBig')) + cambiarTextoGUI('__ACTUAL__',window,'Programa actual: Teclado') #Guardo el módulo que se está ejecutando actualmente actual = 'keyboard' - print "Bloq Mayus activado. Para desactivar, ingrese ---." mayus = 1 #Tiempo entre secuencias Morse, obtenido del diccionario de ajustes @@ -135,18 +143,26 @@ def keyboard(window,salgo): while resCaracter != 'EOM' and resCaracter != 'eom' and salgo == False: - t_end = time.time() + tiempoChar + #Feedback sonoro de arranque de tiempo de interpretación + playsound('tripleDot.wav') + + t_inicial = time.time() + t_end = t_inicial + tiempoChar + + #Para contar los segundos transcurridos y mostrarlos en el progress bar + contador = 0 #string resultado resSecuencia = "" - final = robot.getButton(2) - #Armo la secuencia morse durante los tiempoChar segundos - while time.time() < t_end and final != 1: + while time.time() < t_end: soplido = robot.getButton(5) aspiro = robot.getButton(4) - final = robot.getButton(2) + + if time.time() >= t_inicial + contador: + window.Element('__PROGRESS__').UpdateBar(contador) + contador = contador + 1 #El soplido es un guión if soplido == 1: @@ -155,7 +171,7 @@ def keyboard(window,salgo): resSecuencia = (resSecuencia + '-') #Feedback sonoro - # playsound('dash.wav') + playsound('dash.wav') #Actualizo el feedback cambiarTextoGUI('_FEEDBACK_',window,resSecuencia) @@ -171,7 +187,7 @@ def keyboard(window,salgo): resSecuencia = (resSecuencia + '.') #Feedback sonoro - # playsound('dot.wav') + playsound('dot.wav') #Actualizo el feedback cambiarTextoGUI('_FEEDBACK_',window,resSecuencia) @@ -185,7 +201,6 @@ def keyboard(window,salgo): #Paso de código Morse a caracter utilizando el diccionario morse importado anteriormente incluido en el archivo de diccionarios resCaracter = dicts.morse.get(resSecuencia) - print resCaracter #Reseteo el feedback de la secuencia Morse cambiarTextoGUI('_FEEDBACK_',window,'') @@ -199,7 +214,7 @@ def keyboard(window,salgo): cambiarTextoGUI('_TRANSLATION_',window,resCaracter) #Si no recibà ningún caracter especial, presiono la tecla. - if resCaracter != None and resCaracter != 'EOM' and resCaracter != 'eom' and resCaracter != 'MAYUS' and resCaracter != 'mayus': + if resCaracter not in {None,'EOM','eom','MAYUS','mayus','SALIR','salir'}: #Si los caracteres están en los que puede insertar pyautogui, presiono la tecla. if resCaracter not in dicts.cEsp: @@ -214,36 +229,47 @@ def keyboard(window,salgo): #Si recibo una secuencia vacÃa o que no corresponde a ningún caracter elif resCaracter == None: cambiarTextoGUI('_TRANSLATION_',window,'Nada') + #Implementación de Bloq Mayus elif resCaracter == 'MAYUS': + sg.PopupAutoClose('Bloq Mayus desactivado.', keep_on_top=True) mayus = 0 - + resCaracter = None + elif resCaracter == 'mayus': + sg.PopupAutoClose('Bloq Mayus activado.', keep_on_top=True) mayus = 1 - - #Para dar estabilidad - if final == 1: - time.sleep(0.5) + resCaracter = None + + #Para salir del programa + elif resCaracter in {'SALIR','salir'}: + sg.PopupAutoClose('Saliendo del programa.', keep_on_top=True) + quit() #Para cancelar, en caso de error al ingresar el código y para no simular ningún caracter, ingresar una secuencia de 7 o más caracteres. else: cambiarTextoGUI('_TRANSLATION_',window,'Cancelado') + #Cuando se ingresa un EOM, vuelvo al módulo de mouse saliendo del subprograma. sg.PopupAutoClose('Cambiando a mouse.', keep_on_top=True) + + #Reseteo el indicador de progreso, para dejarlo en 0, siendo que no se usa en el módulo Mouse. + window.Element('__PROGRESS__').UpdateBar(0) + + #Hago verdadera mi variable para salir salgo = True +###Módulo mouse def mouse(window,salgo): -# print "entré." - - #El error en X está entre estos dos prints, pues con el debug vemos que no pasa de acá + #Actualizo las instrucciones y el programa actual cambiarTextoGUI('_OUTPUT_',window,dicts.texto.get('mouseBig')) + cambiarTextoGUI('__ACTUAL__',window,'Programa actual: Mouse') #Elimino el feedback de la parte de teclado cambiarTextoGUI('_FEEDBACK_',window,'') cambiarTextoGUI('_TRANSLATION_',window,'') -# print "cambié texto en gui." #Guardo el módulo que está ejecutándose actualmente actual = 'mouse' @@ -256,54 +282,48 @@ def mouse(window,salgo): #Tamaño total de la pantalla sizeX,sizeY = pyautogui.size() - #Muevo el cursor al centro de la pantalla - pyautogui.moveTo(sizeX/2,sizeY/2) - - #Inicializo las posiciones actuales del cursor - actX = sizeX/2 - actY = sizeY/2 - - #Para cambiar al módulo de teclado, presiono el botón. - cambio = robot.getButton(2) + #Inicializo las posiciones actuales del cursor en donde está al momento de llamada la función + actX, actY = pyautogui.position() #La etapa inicial es 0. etapa = 0 event, values = window.Read(timeout=10) -# print "volvà a leer events and values." #Mientras no reciba la orden de cambiar de módulo - while cambio != 1 and salgo == False: + while salgo == False: soplido = robot.getButton(5) aspiro = robot.getButton(4) - cambio = robot.getButton(2) - #Mantengo el cursor quieto a menos que reciba orden de moverlo o hacer click - while etapa == 0 and cambio == 0 and salgo == False: + #Mantengo el cursor quieto a menos que reciba orden de moverlo, hacer click o salir + while etapa == 0 and salgo == False: -# print "entré al while." - #Será acá el error del X Window System? #Si no recibo nada fuera de lo normal, no hago nada. - while aspiro == 0 and soplido == 0 and cambio == 0 and salgo == False and event != 'Guardar' and event != 'Ajustes' and event != 'Salir' and event != 'Guia Morse': + while aspiro == 0 and soplido == 0 and salgo == False and event not in {'Guardar','Ajustes','Salir','Guia Morse','Cambiar a teclado'}: event, values = window.Read(timeout=10) -# print "leà otra vez events and values." soplido = robot.getButton(5) aspiro = robot.getButton(4) - cambio = robot.getButton(2) + #Guardo los ajustes ingresados con la función guardarAjustes if event == 'Guardar': event = '' - #Tiene formato JSON guardarAjustes(values) sg.PopupAutoClose('Ajustes guardados. Reinicie el programa para que tengan efecto.', keep_on_top=True) + #Cambio al módulo teclado + if event == 'Cambiar a teclado': + event = '' + sg.PopupAutoClose('Cambiando a teclado.', keep_on_top=True) + salgo = True + + #Abro la guÃa almacenada en el archivo guide.jpg con el visor de imágenes predeterminado if event == 'Guia Morse': event = '' - #Abrir la guÃa almacenada en el archivo guide.jpg con el visor de imágenes predeterminado subprocess.call(['xdg-open', 'guide.jpg']) + #Salfo del programa if event == 'Salir': event = '' quit() @@ -330,11 +350,13 @@ def mouse(window,salgo): #tFinal-tInicial vendrÃa a ser una suerte de deltaT. if tFinal-tInicial >= constTimeout: - #Cambio de módulo - + #Hago un click extra para deseleccionar texto + pyautogui.click() + #Para no ingresar caracteres accidentalmente por seguir aspirando sg.PopupAutoClose('Puede dejar de aspirar.', keep_on_top=True) - # keyboard(textWindow) + + #Cambio de módulo salgo = True #Paso a la siguiente etapa @@ -348,12 +370,11 @@ def mouse(window,salgo): #Etapa 1: me muevo hacia la derecha #Si hago un click vuelvo a la etapa 0 - while etapa == 1 and cambio == 0: + while etapa == 1: - while aspiro == 0 and soplido == 0 and cambio == 0: + while aspiro == 0 and soplido == 0 : soplido = robot.getButton(5) aspiro = robot.getButton(4) - cambio = robot.getButton(2) #Si llegué al borde derecho de la pantalla, voy al izquierdo manteniendo mi posición en Y if actX >= sizeX-1: @@ -382,11 +403,10 @@ def mouse(window,salgo): #En la etapa 2, muevo el cursor hacia abajo mientras no reciba una orden de hacer click o de pasar de etapa. #Si hago click, vuelvo a la etapa 0. - while etapa == 2 and cambio == 0: - while aspiro == 0 and soplido == 0 and cambio == 0: + while etapa == 2: + while aspiro == 0 and soplido == 0: soplido = robot.getButton(5) aspiro = robot.getButton(4) - cambio = robot.getButton(2) #Si llegué al borde inferior de la pantalla voy al superior, manteniendo mi posición en X if actY >= sizeY-1: @@ -410,10 +430,7 @@ def mouse(window,salgo): time.sleep(0.5) soplido = robot.getButton(5) - #Cambio de módulo, de mouse a teclado. -# keyboard(textWindow) - -#Cambio el texto del elemento identificado por 'key' en la ventana 'window' por 'text' +#Función para cambiar el texto del elemento identificado por 'key' en la ventana 'window' por 'text' def cambiarTextoGUI(key,window,text): window.Element(key).Update(text) window.Refresh() @@ -427,62 +444,68 @@ sipTimeout = settings.get('sipTimeout') #Layout del GUI principal +#Tamaño de la ventana del programa en pÃxeles +wSizeX = 1200 +wSizeY = 510 + #Layout de la parte izquierda textHelp = [ - [sg.Multiline('', key='_OUTPUT_', font=('Helvetica',10),size=(None,20),enable_events=True) ], + [sg.Multiline('', key='_OUTPUT_', font=('Helvetica',10),size=(None,21),enable_events=True) ], ] #Layout de la parte derecha feedbackText = [ - #en esta lÃnea irÃa el progress bar para el keyTimeout - [sg.Text('This is a placeholder for the progress bar.')], - #Los espacios en blanco luego se cambian por puntos o guiones. Si los saco deja de funcionar. + [sg.Text('Tiempo:', justification='center', font=('Helvetica',10,'italic'))], + [sg.ProgressBar(keyTimeout,size=(200,15),key='__PROGRESS__')], [sg.Text('Secuencia Morse ingresada:', font=('Helvetica',10)), sg.Text(' ', key='_FEEDBACK_', font=('Helvetica',20), enable_events=True)], - #Tengo que ajustar el tamaño de este texto (con size=(a,b)) para que las palabras largas como backspace no aparezcan cortadas - [sg.Text('y equivale a: ',font=('Helvetica',10)), sg.Text(' ',key='_TRANSLATION_',font=('Helvetica',20), enable_events=True)], + [sg.Text('y equivale a: ',font=('Helvetica',10)), sg.Text('BACKSPACE',key='_TRANSLATION_',font=('Helvetica',20), size=(200,None), enable_events=True)], ] settingsLayout = [ - [sg.Text('Teclado', font=('Helvetica',10,'bold underline'))], - [sg.Text('Tiempo entre letra y letra (3-60)', size =(45, 1)), sg.InputText(keyTimeout, size=(20,1), key='__KTIMEOUT__')], - [sg.Text('Mouse', font=('Helvetica',10,'bold underline'))], + + [sg.Text('Ajustes', font=('Helvetica',10,'bold underline'))], + [sg.Text('Teclado:', font=('Helvetica',10,'underline'))], + [sg.Text('Tiempo entre letra y letra (5-60 segundos)', size =(45, 1)), sg.InputText(keyTimeout, size=(20,1), key='__KTIMEOUT__')], + [sg.Text('Mouse:', font=('Helvetica',10,'underline'))], [sg.Text('Velocidad del Mouse (1-250)', size =(45, 1)), sg.InputText(mouseSpeed, size=(20,1), key='__MSPEED__')], - [sg.Text('Tiempo de aspirado para cambiar a teclado (1-10)', size =(45, 1)), sg.InputText(sipTimeout, size=(20,1), key='__STIMEOUT__')], + [sg.Text('Tiempo de aspirado para cambiar a teclado (1-10 seg.)', size =(45, 1)), sg.InputText(sipTimeout, size=(20,1), key='__STIMEOUT__')], [sg.Button('Guardar', enable_events=True)], -# [sg.Text('Nota: Solo ingresar numeros en los rangos validos. Si se ingresa otra cosa, el cambio en ese campo no surtira efecto.', justification='center', font=('Helvetica',10,'italic'))], -# [sg.Text('Si se ingresa otra cosa, el cambio en ese campo no surtira efecto.', justification='center', font=('Helvetica',10,'italic'))], ] doubleLayout = [ [sg.Frame('',feedbackText, element_justification='c')], [sg.Frame('',settingsLayout, element_justification='c')], - [sg.Button('Guia Morse', enable_events=True), sg.Button('Salir', enable_events=True)], + [sg.Button('Cambiar a teclado', enable_events=True),sg.Button('Guia Morse', enable_events=True), sg.Button('Salir', enable_events=True)], ] #Layout combinada combinedLayout = [ - [sg.Text('Sip&Puff4Butia', size=(47, 1), justification='center', font=("Helvetica", 25))], - [sg.Column(textHelp, element_justification='c'), sg.Column(doubleLayout, element_justification='c')], + [sg.Text('Sip&Puff4Butia', size=(wSizeX, 1), justification='center', font=("Helvetica", 25))], + [sg.Text('', size=(wSizeX, 1), justification='center', font=("Helvetica", 15), key='__ACTUAL__')], + [sg.Column(textHelp, element_justification='center'), sg.Column(doubleLayout, element_justification='c')], ] - #Inicializo la GUI -textWindow = sg.Window('Sip&Puff4Butia', combinedLayout) - +textWindow = sg.Window('Sip&Puff4Butia', combinedLayout, size=(wSizeX,wSizeY)) event, values = textWindow.Read(timeout=10) -#Ejecuto una función otra tras otra -#Quizás pueda poner un solo salgo fuera del while, pues serÃa una variable global y no importarÃa qué pasase con ella dentro de los subprogramas. +#Tamaño total de la pantalla +sizeX,sizeY = pyautogui.size() + +#Muevo el cursor al centro de la pantalla +pyautogui.moveTo(sizeX/2,sizeY/2) + +#Variable para salir de los programas. Al volverse True dentro de ellos, se termina el subprograma y se cambia de módulo. +#Siendo que es global, fuera de ellos siempre será False, y al volver a llamarse los subprogramas dentro del while recibirán de parámetro esta variable como False. +salgo = False + +#Ejecuto una función otra tras otra, hasta que se llame quit() dentro de uno de los subprogramas. while True: - salgo = False mouse(textWindow,salgo) - salgo = False keyboard(textWindow,salgo) -#Cierro la ventana al finalizar -textWindow.Close() - +###Fin. diff --git a/settings.json b/settings.json index 7a098eecc413f6a1477e049f7bf28f1a95641b23..a558aaba0f05396e6ec342ffe37617805604f852 100644 --- a/settings.json +++ b/settings.json @@ -1 +1 @@ -{"mouseSpeed": 10, "sipTimeout": 3, "keyTimeout": 7} \ No newline at end of file +{"mouseSpeed": 10, "sipTimeout": 3, "keyTimeout": 10} \ No newline at end of file diff --git a/tripleDot.wav b/tripleDot.wav new file mode 100644 index 0000000000000000000000000000000000000000..a4473f2a65d5a8cf8ce68c14c25ff5215aba30f8 Binary files /dev/null and b/tripleDot.wav differ