Sip&Puff4Butiá - Haciendo la computación más accesible
Contenido
- 1 Preámbulo
- 2 Sensores Sip and Puff
- 3 Sip and Puff y el robot Butiá
- 4 Sip&Puff4Butiá
- 5 Desarrollo y código
- 6 Trabajo a futuro
- 7 Reflexiones finales
- 8 Enlaces
- 9 Autor
- 10 Tutores
- 11 Descargo de responsabilidad
Preámbulo
Pocas personas podrían afirmar que la informática no ha tenido impacto alguno sobre el mundo en general. Actualmente el mundo gira alrededor de ella, ya sea en medios portátiles como teléfonos celulares o fija en caso de las computadoras personales; muchos profesionales han migrado sus rutinas de trabajo a medios electrónicos, y los estudiantes dependen de ella para sus clases y evaluaciones en este tiempo de pandemia.
Esto puede tener ventajas y desventajas según las situaciones particulares de cada persona. Sin embargo, quienes indefectiblemente salen desfavorecidas son aquellas que no tienen acceso a estos medios, independientemente de la razón que lo cause. Dejando de lado las razones económicas, una de ellas es el no poder utilizar estos dispositivos por limitaciones físicas. Por ejemplo, una persona con un grave caso de parálisis no tiene forma de utilizar una computadora, pues no puede mover el mouse ni ingresar texto mediante el teclado.
A lo largo de los años se han desarrollado distintas soluciones para este tipo de situaciones, como puede ser reconocimiento de voz, uso de aparatos auxiliares para aquellas personas que tienen movilidad parcial, entre otros. En el caso de los individuos con problemas de movilidad importantes, como por ejemplo cuadriplegia, puede que en algunos casos estas alternativas no les sean suficientes, ya que puede ser difícil para ellos utilizarlas por limitaciones físicas.
Sin embargo, hay una acción que es casi universalmente posible de hacer; y esto es, soplar y aspirar con la boca.
Sensores Sip and Puff
Se han desarrollado sensores que permiten detectar estas dos señales y realizar diferentes acciones en base a ellas. Esto permite a sus usuarios realizar actividades que antes les eran imposibles, y consecuentemente mejorar su calidad de vida. A modo de ejemplo, en el año 2020 la ciudadana inglesa Natasha Lambert, de 23 años de edad y con cuadriplegia, cruzó el océano Atlántico en un viaje de más de 5.000 kilómetros en un velero controlado por ella mediante un sensor de este tipo.
Además de ser generalmente más accesibles, este tipo de sensores tienen otras ventajas con respecto a otras alternativas. A modo de ejemplo, por su modo de ingresar datos es menos propenso a falsas activaciones, y en casos como el control del velero mencionado antes, donde la precisión es clave, cualquier orden enviada 'en falso' puede tener consecuencias muy serias. Además, es más confiable en situaciones como grandes ruidos, es económico y puede ser instalado y mantenido por cualquier persona con conocimientos básicos de electricidad y/o electrónica.
Persona utilizando un dispositivo Sip and Puff. Fuente de imagen: Ver
Sip and Puff y el robot Butiá
En el año 2014 un grupo de estudiantes junto a sus tutores implementaron un sensor de este tipo controlado mediante la placa USB4Butiá (véase aquí), en donde además de hacer el sensor propiamente dicho lograron controlar el mouse con él mediante, y crearon una nueva paleta para TurtleBots con la que se pueden controlar diferentes aspectos del cursor, como por ejemplo moverlo, obtener su posición actual, hacer click, etc. Además, se dejó un espacio de trabajo a futuro con ideas que se tuvieron pero no se llevaron a cabo en esa ocasión en particular. Esto incluye, por ejemplo, el poder controlar el teclado.
Sensor Sip&Puff
Sip&Puff4Butiá
Sip&Puff4Butiá surge como una idea de avanzar en lo que ya se había desarrollado, agregándole características que la implementación anterior no poseía, como por ejemplo el control de teclado. Además, el enfoque fue más a una aplicación de uso doméstico y directo, sin necesidad obligatoria de programación ni configuración para poder utilizarla. De esta forma, los usuarios solamente deben descargar y ejecutarla, y ya podrán realizar las tareas que ella permite a través de una interfaz amigable e intuitiva.
Ella está programada en su totalidad con Python, tanto el backend como la interfaz gráfica. De esta forma, es utilizable fácilmente, pero al ser de código y hardware abiertos cualquier persona con el conocimiento técnico necesario puede modificarla o incluso mejorarla para agregar funcionalidades.
Ella posee dos módulos principales, uno para control de mouse y otro para control de teclado, además de una sección de ajustes para personalizar ciertos aspectos del programa. Se cambia de módulo con cierta acción especial dentro de cada uno, que serán explicadas en sus respectivas secciones.
La interfaz principal consiste en el título del programa, un subtítulo que informa el módulo actual y cambia según cuál se esté ejecutando y dos columnas, la primera con un elemento y la segunda con tres.
En el caso de la primera, contiene en su totalidad el cuadro de texto de instrucciones, ubicado en la parte izquierda del programa. El propósito de él es brindarle información e instrucciones de cómo usar el módulo actual, de forma de tener una documentación resumida y accesible directamente en el programa. De esta manera, los usuarios que lo usen por primera vez no tendrán que recurrir a ningún sitio externo, ya sea físico o virtual, para aprender a usarlo. El texto es específico para cada módulo, y al cambiar entre ellos el texto cambia también.
En la segunda, el primero es un cuadro informativo para uso del módulo teclado, que incluye un indicador de tiempo, la secuencia Morse que se ingresa hasta el momento y, una vez interpretado por el programa, a qué caracter o comando especial equivale. Mientras se utiliza el módulo mouse estos espacios no son utilizados, dejando solamente el texto estático y la barra vacía.
Debajo de él se encuentra la sección de ajustes, donde se pueden modificar ciertos aspectos de los módulos para adaptarlos más a las necesidades particulares de cada usuario, con botones para guardar los ajustes o restablecerlos a sus valores predeterminados.
El tercer elemento es la barra de botones, interactuables solamente en el módulo Mouse, que permiten cambiar al módulo Teclado, abrir una tabla de equivalencias de código Morse a caracteres o salir del programa.
Cada una de estas partes será explicada más a profundidad en las secciones siguientes, y más específicamente en las que corresponden a los módulos o secciones que las utilizan.
Es importante destacar que si al abrir el programa detecta que el sensor de soplido o aspirado no están conectados no dejará abrirlo, informando al usuario cuál no se encuentra, para luego cerrarse automáticamente.
Importante: Para que este programa funcione correctamente, el sensor de botón correspondiente al aspirado debe ser conectado al puerto 4 de la placa USB4Butiá, y el correspondiente al soplido al puerto 5.
Módulo Mouse
El módulo de mouse funciona haciendo dos barridos separados, uno en el eje de las X y el otro en el de las Y. Para ello, el programa pasa por tres etapas, cambiando entre ellas mediante un soplido.
En la primera etapa, el cursor se mantiene quieto. Esta es la etapa inicial, y es en la que se encuentra el programa cuando se abre. Al soplar y pasar a la segunda, el cursor comienza a moverse hacia la derecha a una velocidad fijada por el usuario; al llegar al borde de la pantalla, vuelve al otro extremo manteniendo la posición vertical. Esto continúa hasta que se sopla nuevamente y se pasa a la tercera etapa, donde el cursor deja de moverse hacia los lados y comienza a hacerlo hacia abajo. Al soplar nuevamente, se pasa a la etapa inicial, y el cursor se mantendrá estático hasta que reciba nuevamente órdenes de moverse.
Sin embargo,esto es solo para posicionarlo en un lugar específico de la pantalla. Para hacer click, se debe aspirar. Es importante destacar que esto se puede hacer en cualquier momento, sin importar en qué etapa se esté; sin embargo, al hacer esto se vuelve a la etapa inicial, por lo que para reanudar el movimiento se debe soplar nuevamente acordemente a lo que se desee.
La idea de movimiento del mouse por etapas fue parcialmente inspirada por el barrido hecho en el proyecto anterior de Sip&Puff con la placa USB4Butiá. Sin embargo, esta implementación tiene algunos cambios como el click únicamente mediante aspirado, y no obligatoriamente al volver a la etapa inicial.
Para cambiar al módulo de teclado se tienen dos opciones: la primera es hacer click en el botón "Cambiar a teclado" presente en la interfaz, y la segunda es aspirar de forma sostenida por una cantidad de segundos determinada por el usuario, que por defecto son 3. Esto, junto a la velocidad de movimiento del cursor, son modificables en la sección de Ajustes del programa.
Mientras se está en el modo Mouse es posible utilizar los diferentes botones disponibles en el programa principal. Al cambiar al modo Teclado se deshabilitan, y al reingresar al modo Mouse se habilitan nuevamente. Cada uno cumple una función determinada. La hilera de arriba corresponde al guardado y restablecimiento de ajustes, que será explicado en la sección de Ajustes. En la hilera de abajo se encuentran tres botones; con "Cambiar a teclado" se cambia al módulo teclado sin necesidad de enviar la orden de cambio mediante aspirado, con "Guía Morse" se abre una imagen con la tabla de equivalencias de secuencia Morse a caracter, y con el botón "Salir" se cierra completamente el programa.
Módulo Teclado
En el módulo teclado, las letras se ingresan mediante código Morse. Un soplido equivale a un guión y una aspirada a un punto. Si el usuario no conoce las equivalencias de secuencia Morse a caracteres, puede pulsar el botón "Guía Morse" dentro de la interfaz en el módulo Mouse o ingresar la secuencia (---.-) para abrir una imagen con estos datos.
Hay un tiempo mínimo que pasa entre señales antes de recibir otra, para dar estabilidad al programa y no contar dos o más veces una misma señal; este tiempo varía según el tipo de indicación. En el caso del soplido, equivalente a un guión, debe pasar medio segundo antes de que se interprete otro envío. Es decir, si el usuario sopla constantemente por un segundo, se registrarán dos guiones. Si este tiempo de espera no estuviera, se registraría una cantidad inmensa de guiones, tantas como señales el equipo sea capaz de recibir en ese tiempo. En el caso del aspirado, equivalente a un punto, el tiempo es de 0.95 segundos. Por lo tanto, en el peor caso hay aproximadamente un segundo entre cada indicación, y a efectos prácticos se toma un promedio de 1 segundo por señal por este peor caso, y porque es prácticamente imposible comenzar a ingresar otra indicación exactamente en el momento en que se comienza a contar el tiempo.
Cada vez que se ingresa una señal se reproduce el sonido correspondiente; es decir, al ingresar un guión se escucha un guión Morse (ver dash.wav), y al ingresar un punto un punto Morse (ver dot.wav). Asimismo, a medida que se van ingresando datos la secuencia se va mostrando en la interfaz, a la derecha del texto "Secuencia Morse ingresada".
Cada cierta cantidad de segundos definida en la sección ajustes, el programa interpreta esa secuencia como un caracter y simula una presionada de la tecla correspondiente, que tiene el mismo efecto que presionarla en el teclado físico. Cuando esto ocurre se escuchan tres puntos seguidos (ver tripleDot.wav), para notificar al usuario de que ya puede comenzar a formar su nueva secuencia Morse. A medida que este tiempo avanza, la barra indicadora de progreso "Tiempo" de la parte superior derecha del programa se va llenando. Que esté llena indica que se acabó el tiempo de ingresar señales y que el programa interpretará lo ya ingresado; de la misma forma, la barra vacía indica que es el principio del ingreso de datos.
Una vez que se agota este tiempo, la secuencia se reinicia y el programa continúa recibiendo señales para luego interpretarlas, repitiendo esta secuencia hasta enviar el comando de cambio de módulo o salir.
En esta interpretación, el caracter al que correspondió lo ingresado se muestra en la interfaz, a la derecha del texto "y equivale a: ". De esta forma, ante cualquier duda sobre el caracter que el usuario indicó, puede observar la interfaz y ver a qué correspondía. Si la secuencia es vacía o no corresponde a ningún caracter, se muestra la palabra "Nada". De esta forma el usuario es consciente de que se equivocó al formar la secuencia, y no se queda preguntando por qué no funciona la tecla que ingresó.
Sin embargo, el error es humano; entonces, no se espera que el usuario tenga una precisión del 100% a la hora de enviar las señales. Por lo tanto, al percatarse de un error se puede cancelar el caracter ingresando siete o más señales Morse. Siendo que no hay ningún caracter o comando especial que requiera más de este número de señales, no puede significar otra cosa que la cancelación. Cuando el programa se encuentra con esta situación, no simula ningún presionado de tecla y continúa su ciclo.
Este número ayuda especialmente en las teclas con secuencias largas, donde es fácil equivocarse y un error significa ingresar algo completamente diferente a lo deseado. Por ejemplo, si el usuario está ingresando un punto y coma (-.-.-.) pero en vez de eso ingresa un guión en vez de punto al final (-.-.--), accidentalmente habrá tipeado un signo de exclamación que puede cambiar significativamente la intención de lo que estaba escribiendo, a modo de ejemplo. Al percatarse del error, puede enviar un punto, convirtiendo la secuencia a (-.-.--.), efectivamente cancelando su ingreso. Luego puede intentar enviarlo nuevamente con la tranquilidad de no haber ingresado un caracter que no era el deseado.
Debido a la forma de simulación de presión de teclas, hay algunas cuyo ingreso se hace de una manera diferente; en este caso, son las definidas como caracteres especiales en dicts.py. Más específicamente, son las vocales con tilde, la eñe y las comillas dobles. Para ingresarlas, se copian al portapapeles y luego se simula la combinación de teclas Ctrl-V, de esta forma pegándolas. En situaciones normales no habría problema con este método; sin embargo, a modo de ejemplo en programas como la mayoría de las terminales Linux se pega con Ctrl-Shift-V y no Ctrl-V. En estos casos este método no funcionaría, y este programa no es capaz de ingresarlos directamente. Una alternativa más larga pero válida es hacer la combinación de puntos y guiones correspondiente a uno de estos caracteres especiales, y luego con el módulo Mouse presionar sobre el botón "Pegar" del programa donde se quiere ingresar, siempre y cuando cuente con esta opción.
Una consecuencia de esto es la pérdida del contenido del portapapeles a menos que se respalde en otra ubicación. Aún así, si el equipo se usa exclusivamente con SipAndPuff4Butiá, siendo que actualmente no hay forma de copiar y pegar, no debería hacer ninguna diferencia. De todas formas es un hecho a tener en cuenta en el caso de que se use en conjunto con otras herramientas, que haya otra persona ayudando al control de la computadora, o cualquier otro caso similar.
Además de las letras, números y símbolos convencionales, existen teclas especiales como la barra espaciadora, la tecla de borrar, Escape, Enter, Tab y Caps Lock. De estas cabe destacar que todas simulan una presión de la tecla correspondiente menos Caps Lock, siendo esta una configuración interna del programa; igualmente, su comportamiento es exactamente el mismo, y a efectos prácticos no debería haber diferencia alguna con tocar la tecla del teclado físico. Al entrar a el módulo teclado, el programa enciende el bloqueo de mayúsculas de forma predeterminada; actualmente esto no es configurable sin editar el código fuente.
Para cambiar al módulo Mouse se puede ingresar la secuencia EOM (end of message), y para cerrar el programa en su totalidad se debe enviar la secuencia de salida, correspondiente a seis guiones.
Los botones interactivos de la interfaz están desactivados hasta cambiar nuevamente al Mouse. Esto es porque en teoría no se podría utilizar el teclado y el mouse a la vez con la implementación actual, además de que las acciones que pueden resultar útiles para el módulo teclado ya están implementadas como secuencias especiales. Esto es el caso del cambio a módulo mouse mediante EOM, la apertura de la guía Morse y el cerrado del programa principal.
Notar que el código Morse utilizado sigue mayormente los estándares internacionales; sin embargo, se le hicieron modificaciones y agregados que no están normalmente en él. Por lo tanto, se sugiere consultar la Guía Morse incluida antes de utilizar este módulo.
Sección Ajustes
Uno de los aspectos más importantes en este tipo de programas es la personalización; no todos los usuarios tienen las mismas necesidades, y lo que para algunos hace el programa inutilizable para otros hace posible su uso. Para lograr eso, se incorporó una sección de ajustes en el programa principal, donde se pueden personalizar diversos aspectos de los módulos.
Todos los ajustes actuales son numéricos, y cada uno tiene un rango de valores válidos. Si se ingresa un número fuera de ellos o ni siquiera se ingresa un número en una primera instancia, al guardarlos el programa ignorará ese cambio y solo almacenará los ajustes que cumplan las condiciones.
En su primera ejecución en el equipo el programa carga valores predeterminados que generalmente hacen posible y accesible su uso; si el usuario desea cambiarlos es completamente libre de hacerlo.
Para guardar los ajustes personalizados, se debe presionar el botón "Guardar", luego de lo cual el programa informa que los cambios han sido realizados con éxito. Sin embargo, se debe tener en cuenta que estos cambios solamente surtirán efecto al reiniciar el programa.
Si en algún momento se ingresa una combinación específica de ajustes que hacen imposible su uso normal, siempre se pueden reiniciar a los valores predeterminados, pulsando el botón "Restaurar valores por defecto". Luego de ello, los valores se reinician y el programa informa que sólo tendrán efecto luego de reiniciar el programa. Los valores predeterminados están detallados más abajo, en la explicación de cada uno de ellos.
Ajustes de Teclado
En el caso del teclado, la configuración disponible es el tiempo que da el programa para ingresar una secuencia Morse. Un usuario más experimentado podría ingresar secuencias en menos de 10 segundos, pero alguien que está dando sus primeros pasos con el uso quizás demore más, por ejemplo mientras se fija en la guía a qué secuencia corresponde el caracter que desea ingresar.
Por lo tanto, el rango de este ajuste va de los 5 segundos hasta los 60, siendo el predeterminado 10. La elección del mínimo de 5 segundos no es aleatoria ni insignificante, siendo que corresponde a una posible situación problemática: en el evento de que el tiempo entre letras sea accidentalmente ingresado demasiado bajo, cinco segundos es el mínimo de tiempo que se necesita para volver a cambiarlo a la normalidad.
Frente a esta situación, se puede enviar EOM para cambiar al módulo mouse (siendo que son 4 señales, el tiempo promedio para ingresarlo es de 4 segundos), luego mover el mouse hacia el cuadro de ajustes y seleccionar el campo de teclado, volver al módulo teclado, ingresar un número más alto y guardar los ajustes. Siendo que todos los números están formados por cinco señales Morse, el tiempo promedio de ingreso es de cinco segundos. Si el mínimo del rango fuera cuatro segundos, no se podría ingresar ningún número y sin ayuda externa el programa quedaría inutilizable.
Si se recuerda de otra sección anterior, el tiempo que pasa entre que se recibe un punto y se vuelve a recibir otro es de exactamente 0.95 segundos. No es 1 exacto para dar estabilidad, pues si se ingresa la señal exactamente cuando se cumplen los cinco segundos puede tanto que se interprete como tanto que no, ya que la diferencia sería en términos de milisegundos y demasiado precisa. Por lo tanto, se le restan 50 milisegundos para intentar evitar esta situación.
Ajustes de Mouse
En cuanto a los ajustes del mouse respectan, se incluyeron dos diferentes: la velocidad de movimiento y el tiempo que se debe aspirar de forma continua para cambiar al teclado.
En el caso del primero, determina la velocidad en píxeles por décima de segundo de movimiento del mouse, con un mínimo de 1 y un máximo de 150, siendo el predeterminado 10. Esta unidad de medida se debe a que el cursor se mueve efectivamente cada 1/10 segundo.
Esto responde nuevamente al hecho que distintos usuarios pueden tener distintas necesidades, además de hardware y resoluciones de pantalla distintas. Un usuario con una resolución de 640x480 deberá elegir una velocidad menor que otro con 3840x2160, pues tiene 6 veces menos píxeles horizontalmente. Al usuario del segundo monitor la velocidad elegida por el primero podría resultarle abrumadoramente lenta.
Por otro lado, el segundo ajuste es el tiempo necesario de aspirado constante para enviar la señal de cambio de módulo al programa. Tiene un mínimo de 1 segundo y un máximo de 10, siendo el predeterminado 3.
Programa en funcionamiento
El siguiente video muestra el programa en acción, incluyendo los dos módulos con sus características y los cambios entre ellos. El objetivo en este video fue entrar al EVA del Taller de Robótica Educativa con el Robot Butiá (TRERB) y leer el programa de la materia.
Es importante destacar que está al triple de la velocidad normal, para reducir considerablemente la duración del mismo.
Desarrollo y código
Consideraciones previas
Este proyecto fue programado íntegramente en Python 2.7. Las librerías utilizadas fueron sys, time, json, subprocess, pyperclip, playsound y PySimpleGUI27. Las explicaciones siguientes asumen que el lector ya tiene cierto conocimiento técnico básico sobre el lenguaje, por lo que algunas cosas simples pueden darse por sentadas.
El programa está dividido en siete archivos diferentes, de los cuales son todos necesarios para su correcto funcionamiento. Ellos son los tres archivos de audio del teclado, la imagen de guía Morse, los ajustes predeterminados, el programa principal que se ejecuta y el archivo de Python que contiene los diccionarios de Morse, caracteres especiales y texto de instrucciones utilizados en él.
En el programa principal están implementados todos los módulos y, a menos que se especifique lo contrario, en las secciones siguientes todo lo hablado se refiere a él.
Programa principal
En el programa principal se inicializa la interfaz grafica mediante la función Window
de PySimpleGUI27, y se ejecutan en bucle infinito, uno tras otro, los subprogramas de mouse y teclado, comenzando por el de mouse. Por lo tanto cuando uno de ellos termina se pasa al siguiente, y esto continúa hasta que se elija la opción de Salir en cualquiera de ellos, mediante lo cual se cerrará definitivamente el programa mediante la función quit()
.
Interfaz gráfica
Layouts
La interfaz gráfica de este programa fue creada utilizando la librería PySimpleGUI27, que es la versión específica de PySimpleGUI para Python 2.7. En esta librería los elementos están dispuestos en layouts, que se pueden ubicar y combinar entre sí para formar los elementos gráficos.
Dentro de ellos se ubican los elementos, dentro de los cuales hay varios tipos, como pueden ser cuadros de texto, botones, barras de progreso, entre otros.
En este caso, la interfaz consiste en dos layouts principales, uno para la parte izquierda y otro para la derecha. En la parte izquierda el único elemento es el cuadro de texto de instrucciones, de tipo Multiline para que sea de múltiples líneas; en la derecha, están las disposiciones de la parte de feedback del módulo teclado y la de los ajustes, que se combinan en una sola mediante frames, lo que permite su disposición en bloques, y la barra de botones inferiores.
Las dos disposiciones de izquierda y derecha son dispuestas en forma de columnas, y se colocan los dos textos, uno estático y otro variante, encima de ellas.
Es importante destacar que cada elemento que se desee que cambie durante la ejecución del programa debe tener asignada una clave única. Con ella, se puede acceder a sus elementos y modificarlos en la medida que se desee. Por lo tanto, todos los elementos excepto el texto estático tienen asignada una.
Para facilitar este cambio creé una función auxiliar llamada cambiarTextoGUI, que recibe como parámetros la clave del elemento, la ventana creada al inicializar la interfaz y el texto a cambiar; una vez llamada cambia el texto y refresca la ventana, de forma que el cambio se ve reflejado inmediatamente en la interfaz.
Para ilustrar, un ejemplo de estas disposiciones es la siguiente, correspondiente a la sección de ajustes:
feedbackText = [ [sg.Text('Tiempo:', 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)], [sg.Text('y equivale a: ',font=('Helvetica',10)), sg.Text('',key='_TRANSLATION_',font=('Helvetica',20), size=(200,None), enable_events=True)], ]
En este caso, sg corresponde a la librería PySimpleGUI27, que es importada "as sg" para resumir su nombre. Aquí se muestra que esta disposición tiene texto estático, indicado por aquellos sg.Text sin una clave asociada, texto variante con claves asociadas, y una barra de progreso cambiante también.
Por defecto los elementos se disponen horizontalmente, y para hacerlo verticalmente se deben poner en forma de columnas, mediante el elemento sg.Column. Un ejemplo de esto son las columnas de la ventana principal del programa, que poseen la siguiente layout:
combinedLayout = [ [sg.Text('Sip&Puff4Butia', size=(wSizeX, 1), font=("Helvetica", 25))], [sg.Text('', size=(wSizeX, 1), justification='center', font=("Helvetica", 15), key='__ACTUAL__')], [sg.Column(textHelp), sg.Column(doubleLayout)], ]
Se pone el título en la parte superior, un subtítulo cambiante debajo para indicar el módulo en ejecución y dos layouts dispuestas verticalmente, cada una ocupando un 50% del ancho de la ventana. Para ello, nuevamente, se utiliza la instrucción sg.Column(layout), que en este caso corresponde al cuadro de texto de instrucciones y a la disposición combinada de feedback de teclado y sección de ajustes.
Interacción con botones
Cada vez que un botón, tanto de la sección de ajustes como de la barra inferior, es presionado en el módulo mouse, se genera un evento, obtenido mediante la instrucción event, values = window.Read()
.
En este caso, los values son los valores de los cuadros de texto, y events el evento actual ocurrido en el programa. Normalmente events es vacío, pero al presionar uno de los botones esta variable se cambiará por el texto del botón propiamente dicho. Por lo tanto, en el módulo mouse se obtienen constantemente estos valores, y cuando el evento cambia, y por tanto cambia esta variable, se realiza la acción correspondiente. Siendo que es una string, basta con compararlo con el operador '=='.
Sin embargo, apenas se entra a las instrucciones de cada caso lo primero que se hace es dejar la variable events como una secuencia vacía. Esto es porque, en caso contrario, se ejecutarían las órdenes del botón indefinidamente, pues el condicional if event == (evento)
siempre sería cierto.
Popups
Para abrir un popup con un mensaje informativo, se utiliza la función sg.PopupAutoClose
. Es importante que sea de cerrado automático, para que el usuario no tenga que presionar ningún botón para cerrarlo, pues ello podría ocasionar complicaciones innecesarias.
Un ejemplo de popup es el de guardado de ajustes. Al presionar el botón Guardar, aparecerá el siguiente mensaje:
El código correspondiente a él es:
sg.PopupAutoClose('Ajustes guardados. Reinicie el programa para que tengan efecto.', keep_on_top=True, title='Aviso')
Se define el texto (primer parámetro), que aparezca encima de todas las ventanas (keep_on_top) y el título (title). Lo mismo ocurre con las demás ventanas emergentes del programa, cada una con sus características correspondientes.
Ajustes
Persistencia
Una de las incógnitas a la hora del desarrollo era cómo hacer para que los ajustes persistieran entre ejecuciones y no volvieran siempre a los predeterminados. La solución a ello fue guardarlos en un archivo externo con formato JSON, en el mismo directorio del programa, llamado settings.json. De esta forma, los ajustes son persistentes y no se pierden al cerrar el programa, y si llega a ser necesario se pueden editar directamente en el archivo mismo.
Al principio del programa, la primera instrucción que no es importar librerías es cargar los ajustes. Para ello, se cargan del archivo JSON y se pasan a un diccionario global, de forma que todas las funciones puedan llamarlo. Esto se hace mediante
#Abro el diccionario de ajustes en settings.json with open('settings.json') as setDict: settings = json.load(setDict)
Utilizando la librería json, se interpreta el formato y se convierte a un diccionario, donde los elementos son fácilmente accesibles mediante settings.get('clave')
, siendo 'clave' el nombre del ajuste.
Guardado
Si se lee nuevamente la sección de interacción con botones, se notará que explico cómo se obtienen los valores de los cuadros de texto con la instrucción event, values = window.Read()
. En ste caso, están almacenados en la variable values. Cabe destacar que los valores contenidos, por defecto tienen formato JSON.
Para guardar los ajustes mediante el botón Guardar, se transforman los valores de esta variable a un diccionario con la instrucción setString = json.dumps('valores')
. Notar que es dumps y no dump, esto es porque es porque la variable de valores es una string, y de ahí viene la 's' del final. Luego este diccionario se carga con la instrucción settings = json.loads(setString)
, donde los valores se pueden acceder mediante ajuste = settings[clave]
, siendo ajuste el nombre de la variable donde se almacenará temporalmente el ajuste al que le corresponde la clave "clave".
En este caso, hay tres variables para los tres ajustes: keyTimeout, sipTimeout y mouseSpeed. La primera corresponde al tiempo entre letra y letra del módulo teclado, la segunda al tiempo de aspirado para cambiar al módulo teclado en el módulo mouse, y la tercera la velocidad del cursor en el módulo mouse también.
Para guardar los ajustes se define un procedimiento llamado guardarAjustes(newSettings), que recibe como parámetro los values obtenidos mediante window.Read().
Ellos son obtenidos mediante la conversión de JSON a diccionario mencionada anteriormente, y luego mediante las líneas
#Obtengo los ajustes ya existentes keyTimeout = settings['__KTIMEOUT__'] sipTimeout = settings['__STIMEOUT__'] mouseSpeed = settings['__MSPEED__']
Se puede ver como se guarda cada uno en su variable correspondiente, obtenido a través de su clave definida cuando se crearon las layouts.
Validación de tipo y rango
Los ajustes son de tipo entero y están en un rango específico. Por lo tanto, si el usuario ingresa, por ejemplo, caracteres en vez de números, o enteros fuera del rango, ese ajuste no debería guardarse.
Esto se hace mediante una validación de tipo y de rango. Cada ajuste tiene una variable booleana que indica si ese ajuste cumple con sus requisitos o no, y a la hora del guardado al archivo settings.json ella determinará si ese ajuste será modificado o no. Para ilustrar, el fragmento de código correspondiente al chequeo de la variable keyTimeout es el siguiente:
#Chequeo si puedo pasar la string a un entero try: int(keyTimeout) #Si se ingresa algo que no es un entero, dejo el valor original. except ValueError: saveKT = False #Si no, entonces paso la string a un int else: keyTimeout = int(keyTimeout) #Chequeo de rango if keyTimeout >= 5 and keyTimeout <= 60: saveKT = True else: saveKT = False
Para validar el tipo, se prueba si se puede convertir lo ingresado al tipo entero mediante la orden int(keyTimeout)
. Si esto no es posible, se generará un error de tipo ValueError. Esto se chequea con la orden except, que al no cumplirse cambiará la variable de guardado del ajuste a false.
En cambio, si esta variable es efectivamente un entero, se procede a convertir la variable a un entero y a realizar la verificación de rango. Esta consiste en evaluar si este número está comprendido en su rango válido de valores, que es el que aparece en la GUI. Por ejemplo, en el caso del tiempo entre letra y letra va de 5 segundos hasta 60, como fue explicado en su sección. Entonces, si lo ingresado está entre o es uno de estos valores, la variable de guardado de ese ajuste se vuelve verdadera. En caso contrario, se vuelve falsa.
Esto se repite para los otros tres ajustes, cada uno con sus características particulares.
Guardado final y modificación de settings.json
Una vez que los ajustes son verificados y sus valideces marcadas, se procede al guardado persistente en el archivo settings.json.
Esto se hace mediante el siguiente código:
#Diccionario vacío para los valores nuevos resSettings = {} #Si todos los valores cumplen con los requisitos, entonces los guardo if saveKT: resSettings['keyTimeout'] = keyTimeout if saveMS: resSettings['mouseSpeed'] = mouseSpeed if saveST: resSettings['sipTimeout'] = sipTimeout #Almaceno los cambios en settings.json with open('settings.json','w') as setDict: json.dump(resSettings,setDict)
Se crea un diccionario vacío para guardar los ajustes a modificar; luego se verifica la variable de guardado correspondiente a cada uno, y si es verdadera se agrega su clave y valor nuevo correspondiente a él.
Una vez que este diccionario fue creado, se abre el archivo settings.json mediante la instrucción open con la opción 'w' (de write, es decir, "escribir"), y se reemplazan mediante json.dump, con los parámetros del diccionario resultante y el archivo abierto anteriormente.
Algo a destacar de esto es que la instrucción json.dump no sobreescribe completamente el archivo JSON de ajustes, sino que lo actualiza con los valores del diccionario nuevo. Entonces, si no hay ningún ajuste válido o solo algunos lo son, los que no se modifican quedarán con sus valores originales.
Además, si alguna clave no se encuentra presente en el archivo, ya sea por una eliminación accidental de settings.json en el transcurso de la ejecución del programa u otra razón, al escribirlos json.dump creará las claves y les asignará el valor. Es decir, que no funciona solamente para actualizar sino también para crear en caso de eliminación.
Restauración a valores predeterminados
La restauración de los valores predeterminados se realiza cuando se presiona el botón correspondiente en la interfaz durante el módulo Mouse. Allí, se cambian los valores de los cuadros de texto de ingreso de datos mediante la función cambiarTextoGUI, explicada en secciones anteriores:
#Cambio el texto de los cuadros de texto cambiarTextoGUI('__MSPEED__',window,'10') cambiarTextoGUI('__KTIMEOUT__',window,'10') cambiarTextoGUI('__STIMEOUT__',window,'3')
Luego se leen los valores nuevos mediante window.Read(), y se llama a la función de guardar ajustes con estos valores:
#Leo los nuevos valores event, values = window.Read(timeout=10) #Guardo los ajustes con los valores por defecto guardarAjustes(values)
A efectos prácticos, lo que se hace es simular que el usuario ingresara estos valores y después los guardara normalmente. De esta forma, no se precisa hacer otra función adicional para modificar settings.json, sino que basta con cambiar los números correspondientes en los cuadros de texto y luego usar la función de guardado ya implementada. De esta forma se necesita menos código, y se aprovecha más eficientemente el que ya está.
Los valores por defecto son 10 segundos entre letra y letra, 10 píxeles por décima de segundo de velocidad del mouse y 3 segundos de aspirado para cambiar de módulo en el módulo Mouse. Ellos pueden ser ajustados según cada caso particular, pero actualmente esto solo es posible hacerlo editando el código fuente.
Módulo mouse
Algunas consideraciones previas a la ejecución
Lo primero que se hace al cargar el módulo mouse es cambiar el texto de instrucciones y programa actual, además de vaciar la barra de progreso y secuencias Morse de la interfaz, para que no queden residualmente de la ejecución del módulo teclado. Esto se hace en todos los casos mediante la función cambiarTextoGUI.
Para el movimiento automático del mouse se utiliza la librería pyautogui, de funcionamiento similar a la librería xevents de la FING. Mediante ella antes que nada se obtiene el tamaño total de la pantalla para detectar cuáles son los bordes, y la posición actual del cursor.
En la primera ejecución del programa se mueve el cursor a la mitad de la pantalla; es decir, cada uno de los tamaños horizontales y verticales divididos entre 2. Sin embargo, sería poco práctico que se mueva a la mitad en cada cambio de módulo, por lo que luego de este movimiento inicial, pensado como una suerte de inicialización, no se vuelve a modificar la posición el cursor sin indicación previa del usuario.
Las primeras dos acciones se realizan mediante las funciones size()
y position()
de la librería pyautogui, y para mover el cursor se usa la función moveTo(X,Y)
, siendo X e Y las coordenadas en píxeles horizontales y verticales de la pantalla respectivamente.
La ejecución de este módulo continúa mientras no se reciba una orden de cambio o salir. Mientras la salida se maneja mediante la función quit()
de Python, la de salida del módulo se controla mediante la variable booleana salgo
. Se inicializa en false, y en cada bucle dentro del módulo se controla que lo siga siendo. Una vez que se vuelve verdadera, nunca se entra a ningún bucle y el subprograma finalizará su ejecución.
Etapas
Cada etapa está marcada por la variable etapa
, que se modifica al recibir orden de cambio. Entonces, en los bucles de cada etapa además de verificar que no se haya decidido cambiar de módulo se chequea que la etapa no haya cambiado también.
Etapa 0 (inicial) y conteo de tiempo de aspirado
Durante la etapa 0, el cursor se mantiene quieto. Entonces, se monitorean constantemente los valores de soplido, aspirado y eventos de la interfaz en caso que se presione un botón. Una vez que ocurre uno de estos eventos, se sale de este bucle de obtención de datos y se ejecuta la acción correspondiente.
En el caso del cambio de etapa, se cambia el valor de la variable etapa
por 1, y entonces se pasa a la siguiente.
En el caso de los botones, pasa algo diferente según cuál sea el presionado:
Al presionar Guardar, se llama a la función auxiliar guardarAjustes
, que guarda los ajustes según se describió en la sección correspondiente. Lo mismo pasa con el botón Restaurar valores por defecto. En el caso de Cambiar a teclado, se cambia la variable de cambio de módulo, por lo que finaliza el subprograma y se cambia al módulo teclado. Con Guía Morse se abre la imagen de guía con el visor determinado del sistema mediante la instrucción subprocess.call(['xdg-open', 'guide.jpg'])
, y con Salir se sale completamente del programa mediante la orden de Python quit()
.
Al recibir un aspirado se hace click mediante pyautogui.click()
, y se cuenta el tiempo por el que se mantiene el aspirado.
Si se recibe un soplido, se cambia la variable de etapa y se cambia a la siguiente.
Conteo de tiempo
Al recibir un aspirado, lo primero que se hace es obtener el tiempo actual de ese momento mediante la instrucción time.time()
. Se repiten las instrucciones de hacer click y refrescar los valores de los sensores hasta que se deje de aspirar o pase el tiempo indicado en los ajustes. Una vez que una de estas dos condiciones se rompe, se guarda el tiempo en que ello pasó para poder calcular el tiempo transcurrido. Si es mayor al máximo definido, entonces se cambia la variable de cambio de módulo y se notifica al usuario para que deje de aspirar.
En caso de que este umbral no se superara, no pasaría nada aparte de el o los clicks.
Etapa 1 y debouncing
La posición del cursor está guardada en dos variables, una para la posición horizontal y otra para la vertical. En esta etapa, el cursor se mueve constantemente a la derecha aumentando la variable de posición horizontal por el valor de velocidad definido en los ajustes. Mientras no se reciba ninguna señal, se realiza este movimiento a la vez que se chequea que la posición en X no haya superado el largo en píxeles menos 1. Si esto ocurre, se cambia la posición horizontal a 0 pero se mantiene la vertical, así volviendo al extremo contrario del monitor.
El menos 1 como límite está porque utilizando esta librería este es el mayor valor que alcanza la variable de posición; si se pusiera el tamaño sin modificar, nunca llegaría a ese valor y quedaría estancado en el borde por siempre. Este mismo problema tuvieron los implementadores del programa anterior de Sip&Puff con Butiá, y también fue encontrado a la hora de desarrollar este programa.
Igualmente que en la etapa anterior, al recibir un aspirado se simula un click y con un soplido se cambia de etapa a través del cambio de la variable etapa
.
Etapa 2
La etapa 2 tiene mayormente el mismo funcionamiento y comportamiento que la etapa 1, siendo la única diferencia que la variable que se cambia es la posición vertical y no horizontal. Ella, al ser aumentada, moverá el cursor hacia abajo, volviendo al borde superior cuando se detecte que se llegó al borde inferior, indicado por el alto en píxeles de la pantalla menos 1.
Módulo teclado
Previo a la ejecución
En el módulo teclado, como ya fue explicado en su sección correspondiente, los caracteres se ingresan mediante código Morse. Un soplido equivale a un guión y un aspirado a un punto. A medida que se envían estas señales se va construyendo la secuencia Morse, que al terminar el tiempo definido en los ajustes se convertirá a un caracter o instrucción especial mediante el uso del diccionario morse contenido en dicts.py. Esta secuencia se guarda en una cadena de texto llamada resSecuencia
.
Igual que en el módulo mouse, lo primero que se hace al cambiar de módulo es actualizar el texto estático, para que los contenidos sean justamente los de este módulo. Además, se obtiene el ajuste de timeout entre letra y letra y se lo almacena en una variable llamada tiempoChar
.
De la misma manera, la orden de cambio de módulo es dada por la variable booleana salgo
, verificada en todos los bucles para no entrar a ellos si es verdadera. El bucle principal del módulo se ejecuta de forma constante, siempre y cuando ella sea falsa.
Ingreso de caracteres y medición de tiempo
Como fue mencionado antes, el tiempo almacenado en los ajustes se guarda en la variable tiempoChar
. Entonces, en el bucle principal, mediante el uso de la librería time
se obtiene el tiempo inicial y se calcula el final con la variable antes mencionada.
Mientras no se haya alcanzado el tiempo final se monitorean los sensores de botón de soplido y aspirado hasta que uno de ellos obtenga el valor 1.
Al recibir un aspirado, se agrega un punto a la secuencia, se reproduce el sonido de punto Morse, se cambia la secuencia visual mostrada en la interfaz y se esperan 0.95 segundos para volver a recibir otra señal. Esto es implementado con el siguiente código:
if aspiro == 1: #Agrego a la secuencia Morse resSecuencia = (resSecuencia + '.') #Feedback sonoro playsound('dot.wav') #Actualizo el feedback cambiarTextoGUI('_FEEDBACK_',window,resSecuencia) #Delay de estabilidad (evita que se registre la aspirada más de una vez) time.sleep(0.95)
Para reproducir el sonido se utiliza la librería playsound, que mediante la instrucción playsound(archivo) lo abre y reproduce, luego de lo cual el programa continúa su ejecución. Siendo que dura décimas de segundo, esta demora no afecta en lo más mínimo.
En el caso del punto Morse, equivalente al aspirado, el comportamiento es el mismo, salvo que se agrega un guión a la secuencia, se reproduce el archivo dash.wav y se esperan 0.5 segundos.
Barra de progreso
La barra de progreso es un elemento de tipo ProgressBar de PySimpleGUI27. Es definida con un valor máximo, que en este caso es el tiempo entre letra y letra definido entre los ajustes. Se actualiza mediante la instrucción window.Element(clave).UpdateBar(valor)
; a medida que aumenta el valor, más llena estará la barra.
Entonces, mientras no se haya acabado el tiempo se va llenando la barra de progreso mediante las siguientes líneas:
window.Element('__PROGRESS__').UpdateBar(contador) contador = contador + 1
Al final del tiempo quedará llena. La variable contador
se inicializa en 0
al principio del bucle de conteo de tiempo, por lo que la barra se vaciará automáticamente al comenzar a contarlo nuevamente.
Simulación de caracteres y comandos especiales
Una vez que se termina el tiempo, se evalúa si la longitud de la secuencia es menor a 7, mediante la orden len(resSecuencia)
, quien devuelve el número de caracteres en ella. Si es mayor, simplemente se cambia el texto de equivalencia en la interfaz por la palabra Cancelado y se vuelve al bucle de ingreso de señales Morse.
Si no fue cancelado, entonces se traduce la secuencia a caracteres con el diccionario Morse contenido en dicts.py. Los diccionarios en Python son equivalencias "clave->valor", siendo en este caso la clave la secuencia Morse y el valor cada caracter o acción correspondiente. Entonces, se puede obtener el valor a partir de la clave con la función get(clave)
. En este caso, se guarda en la variable resCaracter
el valor con la instrucción resCaracter = dicts.morse.get(resSecuencia)
.
Si la secuencia no equivale a nada, esa variable tomará el valor especial None.
Luego se vacía la secuencia ingresada en la interfaz y se muestra el caracter o acción correspondiente al que equivale.
Mayúsculas
El bloqueo de mayúsculas está almacenado en la variable mayus
, que se inicializa en 1 al entrar al módulo y se puede cambiar a 0 ingresando la secuencia de cambio (---.). Un 1 equivale a activado y un 0 a desactivado.
En el diccionario todas las letras están en mayúsculas. Por lo tanto, luego de verificar que la secuencia ingresada corresponde a un caracter o acción válido, si el bloqueo de mayúsculas está desactivado este caracter se pasará a minúsculas mediante la función lower()
. Cabe destacar que aunque los símbolos no tienen esta propiedad, si se llama la función sobre ellos no pasará nada, por lo que es completamente válido hacer esto en su caso.
Ingreso de caracteres normales
Los caracteres normales son todos aquellos que no son comandos especiales del programa (como EOM, MAYUS, GUÍA o SALIR) y tampoco pertenecen al conjunto de los caracteres especiales (Ñ, ", Á, É, Í, Ó, Ú). El programa verifica si resCaracter
pertenece a alguno de ellos. Si no lo está, entonces simula su presión en el teclado físico mediante press(resCaracter)
, acción perteneciente a la librería pyautogui.
Es importante remarcar que esta acción recibe ciertos comandos especiales pertenecientes a teclas de función, como pueden ser Enter, Esc, Borrar, etc. Ellos también pueden pasarse como parámetro a la función press, quien simulará la presión de estas teclas y no ingresará la secuencia de sus caracteres; por ejemplo, press('return')
presiona la tecla Enter y no escribe literalmente la palabra "return". La lista de posibles teclas que se pueden ingresar mediante esta función se pueden ver en la siguiente página. Se podrían agregar teclas adicionales al módulo teclado asignando una secuencia Morse no utilizada a otra de las teclas listadas en ella.
Ingreso de caracteres especiales
Los caracteres especiales no disponibles para ingresar mediante la función press
son, como fue mencionado anteriormente, Ñ, ", Á, É, Í, Ó y Ú
. Ellos están incluidos en el diccionario cEsp
(de caracteres Especiales) incluido en el archivo dicts.py. Cuando el programa identifica que resCaracter
pertenece a él, los ingresa de una forma alternativa: copia el caracter al portapapeles con pyperclip.copy
y lo pega simulando Ctrl-V mediante pyautogui.hotkey("ctrl","v")
.
Esto se hace mediante:
pyperclip.copy(resCaracter) pyautogui.hotkey("ctrl","v")
Sin embargo, no es una solución perfecta, pues se borra el contenido del portapapeles y solo sirve para aquellos programas donde se puede pegar con esta combinación. Aún así, para la mayoría de los casos funciona y no debería haber problema.
Acciones especiales del programa
Hay valores correspondientes a secuencias Morse que equivalen a ciertas acciones internas del programa. Ellas son, y como fue mencionado anteriormente, EOM, MAYUS, GUIA y SALIR. El programa revisa también si resCaracter
pertenece a alguna de esas opciones, y si esto se cumple se ejecutará la acción correspondiente a cada una.
Además, según si el bloqueo de mayúsculas está activado o no, estas órdenes se recibirán escritas en mayúsculas o minúsculas. Esto no hace diferencia en ninguno de los casos menos en el comando de bloqueo de mayúsculas. Si se recibe 'mayus', entonces él está desactivado y se activa. De la misma manera, haber recibido 'MAYUS' significa que está encendido y se debe desactivar. Según el caso, al recibir la orden se asigna el valor '0' o '1' a la variable mayus
según si se desactiva o activa respectivamente.
En el caso de la apertura de la guía Morse, se abre la imagen que la contiene con la instrucción subprocess.call(['xdg-open', 'guide.jpg'])
, igual que en el módulo mouse. Lo mismo ocurre con la orden de salir, donde se cierra el programa con la función de Python quit()
.
En el caso de EOM, se cambia la variable de cambio de módulo salgo
a True, lo que hará que no se ingrese a ningún bucle y se cambie de subprograma.
Trabajo a futuro
Este programa, aunque cumple su cometido y es completamente funcional, tiene aspectos aún pendientes de agregar que de así hacerlo, lo volverían mucho más completo y mejoraría su uso para los usuarios.
Algunos de estos aspectos son:
- Cerrar o minimizar ventanas con secuencias Morse
- Añadir combinaciones de teclas (ej: Ctrl-C, Shift-1, etc.)
- Más ajustes, por ejemplo desactivar el bloqueo de mayúsculas por defecto o desactivar/cambiar el volumen del feedback sonoro
- Deshabilitar botones visualmente al entrar al modo teclado, para indicar que no tienen funcionalidad hasta cambiar de módulo
- Cambiar aspirado por soplido sostenido para pasar del módulo mouse a teclado, para evitar hacer clicks innecesarios
- Agregar click derecho
- Copiar y pegar según indicaciones del usuario
- Permitir ingresar caracteres especiales sin tener que recurrir al copiado y pegado
- Poner más símbolos y teclas especiales en el módulo teclado
- Permitir recargar ajustes al guardarlos sin tener que reiniciar el programa
- Añadir valores por defecto personalizados dentro del programa; por ejemplo, agregando un botón en la sección de ajustes llamado "Marcar como valores por defecto", y que al restaurar los valores por defecto se carguen ellos en vez de los estáticos definidos en el programa actual. Una posibilidad es almacenarlos en settings.json, así como los ajustes actuales.
- Combinar todo en un solo ejecutable, para así facilitar su uso. Por ejemplo, mediante el uso de
PyInstaller
.
Cada uno de estos aspectos tiene su complejidad asociada, y hay algunos más simples de implementar que otros. Sin embargo, todos aportan su parte y harían el uso de este programa más enriquecedor y conveniente.
Reflexiones finales
Se ha creado un programa con el que se aspira a que usuarios incapaces de manejar un equipo informático de forma convencional por limitaciones físicas puedan comenzar a hacerlo. De esta forma, y como fue mencionado al principio de esta entrada, se busca universalizar el acceso a herramientas computacionales mediante la accesibilidad, para así abrir las puertas de ellas a cada vez más personas, dado su creciente uso en la vida cotidiana.
Este programa no es perfecto, y siempre tendrá cosas a corregir o mejorar. Sin embargo, lo que ya está funciona; y él también busca ser una chispa iniciadora a más proyectos de este tipo, como lo fue el de Sip&Puff con Butiá original en su momento para este proyecto.
Por lo tanto, cualquier persona que desee mejorarlo, agregarle módulos o modificarlo es más que bienvenido, y siendo de hardware y software abierto esto es completamente posible y realizable. El código disponible en GitLab tiene multitud de comentarios que, agregados con esta entrada, permiten una comprensión mucho mayor de lo que ocurre internamente en el programa y ayuda a hacer cualquier tipo de cambio.
Cualquier consulta, duda, sugerencia o aporte se pueden comunicar con el autor original de este programa mediante el correo electrónico disponible en la sección Autor.
Enlaces
- Código fuente: https://gitlab.fing.edu.uy/santiago.freire/SipAndPuff4Butia
- Proyecto anterior de Sip and Puff con Butiá: https://www.fing.edu.uy/inco/proyectos/butia/mediawiki/index.php/Accesibilidad
Autor
- Santiago Freire López - santiago.freire@fing.edu.uy
Tutores
- Daniel Larrosa
- Guillermo Trinidad
- Gonzalo Tejera
Descargo de responsabilidad
Este programa es de estricto uso educativo. Prohibido su uso en sistemas o situaciones donde esté en juego la salud o integridad física de una persona. Consulte con su médico.
Este contenido está distribuido bajo licencia GNU Free Documentation License. Para el programa y código fuente véase el archivo LICENSE disponible en el repositorio GitLab.