Mostrando las entradas para la consulta hilo ordenadas por fecha. Ordenar por relevancia Mostrar todas las entradas
Mostrando las entradas para la consulta hilo ordenadas por fecha. Ordenar por relevancia Mostrar todas las entradas

viernes, 27 de marzo de 2020

MONITOREO DE MULTIPLES HILOS EN PYTHON 3


En este programa se ejemplifica cómo monitorear varios hilos y mostrar el progreso de cada uno en la interfase gráfica. Para saber mas sobre hilos, procesos y demás. puedes leer el post del link.


El programa utiliza unas barras de progreso (gauge) para representar cada hilo en la interfase gráfica. Cada hilo tiene un proceso, el cual consiste en contar de 0 a 100, con un retraso aleatorio de tiempo de entre 5 y 10 mili segundos en cada iteración para simular la variación de tiempos de proceso. Puedes  cambiar el numero de procesos en la variable num_procesos.  Corre este programa una vez y observa como las barras de progreso se llenan casi al mismo tiempo.

semáforo desactivado

En el código mostrado mas abajo hay 2 funciones comentadas. Estas funciones son threadLock.acquire() y threadLock.release(). Estas funciones funcionan (valga la redunancia) como un semáforo que pausa un hilo determinado mientras otro continua. Esto es útil para evitar corromper datos cuando 2 o mas hilos intentan acceder a la misma información para lectura o escritura al mismo tiempo. Des-comenta estas 2 funciones como experimento para observar como las barras de progreso se pausan entre si de modo que ya no se llenan de de forma uniforme.

Semáforo activado

En el código fuente te dejo comentarios comprender lo que sucede. Nesecitas la librería wxpython para correr este programa. Este programa fué probado en Python 3.7.


import wx
from time            import sleep
from random          import randint
from threading       import Thread
from threading       import Lock
from threading       import current_thread
from wx.lib.newevent import NewEvent
 
threadLock = Lock() # descomenta threadLock.acquire() y threadLock.release()
                    # (mas abajo) como experimento para ver que sucede.
MandaGaugeDatos, EVT_ACTUAL_GAUG = NewEvent() # evento que se usara para monitorear hilos y actualizar gauges
num_procesos = 5   # numero de hlos (y por ende, de gauges) pon aqui los que quieras

class TestThread(Thread): # Esto es un hilo.
    def __init__(self,parent,threadID):
        Thread.__init__(self)     # llamar al constructor de la clase
        self.threadID = threadID  # variable para identificar el hilo
        self.parent   = parent    # padre de el hilo (se usa para matar hilo cunando se sierre el programa)

    def run(self): # EL PROCESO
        #--------------------------------------------------
        #   El roceso consiste en contar de 1 a 100.
        #   se generan retardos aleatorios para simular tiempos variantes
        #--------------------------------------------------
        for i in range(1,100): # Cuenta de 1 a 100%
            
            #threadLock.acquire() # DESCOMENTA ESTO A VER QUE PASA.

            slp = randint(5, 10)/1000  # generar tiempo aleatorio entre 5 y 10ms
            sleep(slp)                 # realizar el retraso aleatorio
            #-------------------------------------------------------------
            # Enviar datos resultantes de cada iteracion del hilo a la GUI
            #  wx.postevent(parent, function) #generar evento
            #  MandaGaugeDatos(datos nombrados que quieras regresar)
            #--------------------------------------------------
            wx.PostEvent(self.parent,
                         MandaGaugeDatos(dato   = i,
                                         gauge  = self.threadID,
                                         thread = current_thread()))
            
            #threadLock.release() # DESCOMENTA ESTO A VER QUE PASA.
     
class MainFrame(wx.Frame):
    def __init__(self, parent, title =""):
        super().__init__(parent, title = title , size = (500,250))
        self.panel  = wx.Panel(self)
        
        self.gauges = []  # Arreglo de Gauges
        threads     = []  # arreglo de hilos
        #---------------------------------------------
        #                WIDGETS
        #---------------------------------------------
        #       llenar el arreglo de gauges
        #--------------------------------------------
        for gauge in range(num_procesos):
            self.gauges.append(wx.Gauge(self.panel, size=(300, 20)))
        #---------------------------------------------
        #                 BINDS
        #---------------------------------------------
        # Conectar señal del evento emitida con la funcion correspondiente
        #--------------------------------------------
        self.Bind(EVT_ACTUAL_GAUG, self.on_actualizar)
        #---------------------------------------------
        #                 LAYOUT
        #---------------------------------------------
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add(self.panel,1, wx.CENTER|wx.ALL|wx.EXPAND)
        panel_sizer = wx.BoxSizer(wx.VERTICAL)
        self.panel.SetSizer(panel_sizer)
        for gauge in self.gauges:
            panel_sizer.Add(gauge , 0, wx.CENTER|wx.ALL|wx.EXPAND)
        self.SetSizer(main_sizer)
        #---------------------------------------------
        #  llenar arreglo de hilos e  Iniciarlos todos
        #---------------------------------------------
        for x in range(num_procesos):
            threads.append(TestThread(self, x))
            threads[x].daemon = True
            threads[x].start()
            
    def on_actualizar(self, evt):
        #Actualizar el gauge correspondiente en la GUI
        self.gauges[evt.gauge].SetValue(evt.dato)
 
if __name__ == '__main__':
    app = wx.App(0)
    frame = MainFrame(None)
    app.SetTopWindow(frame)
    frame.Show()
    app.MainLoop()

jueves, 19 de marzo de 2020

MEDICION MULTIPLE DE TIEMPOS EN PYTHON 3



Imagina que tienes varias estaciones donde se procesa algún tipo de  pieza y quieres medir cuanto tiempo permanece cada pieza en cada estación. Supongamos también que cada estación tiene su tiempo de ciclo (unas inician antes otras despues, otras al mismo tiempo).

En el siguiente programa muestro como medir una cantidad determinada de tiempos con un solo temporizador y un solo hilo (thread). El programa calcula minutos, segundos y centésimas de segundo de cada estacion. La interfase de usuario es dinámica, es decir, se puede determinar el numero de registros de tiempo que se deseen (yo puse 20 pero lo puedes cambiar la variable numtimers al numero que quieras), el programa genera los elementos nesesarios. Este programa apenas consume CPU (se podría usar en un Raspberry Pi).El programa inicia el conteo al presionar un botón correspondiente, cuando se presiona de nuevo detiene el conteo de tiempo y lo guarda en un archivo de texto.

Programa en windows

El programa tiene comentarios en la mayoría de las líneas de código para que se pueda entender lo que sucede.

AVISO: Cada temporizador crea un archivo de texto, si pones 50, se crean 50 archivos de texto en el lugar donde esta el script. Se recomienda correrlo dentro de su propio folder.

Este programa fue probado con python 3.7. También nesecitas wxpython para correr este programa.

import wx
from   time                 import sleep
from   threading            import Thread
from   wx.lib.scrolledpanel import ScrolledPanel

class TimeReg():   #clase para registrar conteo de tiempo y estado del conteo.
    def __init__(self):
        self.tiempo = [0,0,0]  #registro de minutos, segundos y decasegundos
        self.activado = False  #iniciar desactivado

    def update(self):
        self.tiempo[2] += 1         # incremento cada centisegundo (1/100)

        if (self.tiempo[2] >= 100): # Han pasado 100 centisegundos (1 segundo)?
            self.tiempo[2] =  0     # resetear cuenta de centisegundos.
            self.tiempo[1] += 1     # incrementar segundero
                
        if (self.tiempo[1] >= 60):  # Han pasado 60 segundos (1 minuto)?
            self.tiempo[0] += 1     # incrementar minutero
            self.tiempo[1] =  0     # resetear segundero

    def reset(self):
        self.tiempo = [0, 0, 0]     # 0 minutos, 0 segundos, 0 centisegundos
        

class MyForm(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Timers")#llamar a la funcion constructor de Frame
        #---------------------------------------------------
        #                 VARIABLES
        #---------------------------------------------------
        self.numtimers       = 20        # cuantos timers quieres 20, 50, 200?
        self.CronometrArr    = []        # Arreglo de registro de tiempos
        self.TimeRegePattern = '{0:02d}:{1:02d}:{2:02d}'       # Darle formato al tiempo
        self.thread          = Thread(target = self.WrkrThred) # poner funcion WrkrThred en un hilo
        self.thread.daemon   = True      # Poner hilo en modo daemon, si el programa termina, tambien el hilo
        self.exitFlag        = False     # Bandera para parar el ciclo del hilo
        self.LblTimeRegeArr  = []        # Arreglo de etiquetas de tiempos
        self.BtnTimeRegrArr  = []        # Arreglo de botones.
        FntLblTimeRegrs      = wx.Font(16, wx.MODERN, wx.NORMAL, wx.BOLD)  #fuente a usar
        self.TiempoStr       = ""

        for i in range(self.numtimers):
            self.CronometrArr.append(TimeReg()) #Arreglo de objetos de registro de tiempos

        self.createFiles() #crear los archivos de texto nesesarios
        #---------------------------------------------------
        #                 WIDGETS
        #---------------------------------------------------
        PnlTimeRegpos = ScrolledPanel(self) #Panel de lecturas de tiempos
        
        for i in range(self.numtimers):
            # crear etiquetas y llenar arreglo
            self.LblTimeRegeArr.append(wx.StaticText(PnlTimeRegpos,
                                                -1,
                                                style = wx.ALIGN_CENTER|wx.ST_ELLIPSIZE_MIDDLE))
            self.LblTimeRegeArr[i].SetFont(FntLblTimeRegrs)     #asignar fuente
            self.LblTimeRegeArr[i].SetBackgroundColour('black') #asignar color de fondo
            self.LblTimeRegeArr[i].SetForegroundColour('white') #asignar color de letra
            self.LblTimeRegeArr[i].SetLabel("00:00:00")         #texto inicial

            # crear botones y llenar arreglo de botones
            self.BtnTimeRegrArr.append(wx.ToggleButton(PnlTimeRegpos ,
                                                  -1,
                                                  str(i),
                                                  size=(50,40)))
        #--------------------------------------------------
        #                 BINDS
        #--------------------------------------------------
        self.Bind(wx.EVT_CLOSE, self.onClosed) # trabajo de limpieza al salir de la aplicacion
        
        for i in range(self.numtimers):
            # poner bind a todos los botones a self.onTggleBtn
            self.BtnTimeRegrArr[i].Bind(wx.EVT_TOGGLEBUTTON,self.onTggleBtn)
 
        #---------------------------------------------------
        #                 MAIN LAYOUT
        #--------------------------------------------------
        MainSizer = wx.BoxSizer(wx.VERTICAL)
        #--------- Panel de lecturas ------
        TimeRegposSizrArr = [] #Arreglo de sizers
        PnlTimeRegposSizr = wx.BoxSizer(wx.VERTICAL)
        
        for i in range(self.numtimers):
            TimeRegposSizrArr.append(wx.BoxSizer(wx.HORIZONTAL))
            TimeRegposSizrArr[i].Add(self.BtnTimeRegrArr[i],0,wx.EXPAND|wx.ALL,2)
            TimeRegposSizrArr[i].Add(self.LblTimeRegeArr[i],1,wx.EXPAND|wx.ALL,2)
            PnlTimeRegposSizr.Add(TimeRegposSizrArr[i],1,wx.EXPAND|wx.ALL,0)
        PnlTimeRegpos.SetSizer(PnlTimeRegposSizr)
        PnlTimeRegpos.SetupScrolling() #calcular espacio para scroll bar
        #--------------------------------------------------
        MainSizer.Add(PnlTimeRegpos, 1,wx.EXPAND|wx.ALL,5)
        self.SetSizer(MainSizer)
        #---------------------------------------------------
        #            INICIAR HILOS AL ULTIMO
        #---------------------------------------------------         
        self.thread.start()       # Iniciar hilo
        
    def WrkrThred(self):
        while True: #loop "infinito"
            if self.exitFlag == False: # programa sigue corriendo?
                sleep(0.01)            # reterdo de 1 centi sedundo
                for i in range(self.numtimers):                    #iterar en los registros
                    if self.CronometrArr[i].activado == True:      #si esta activado
                        self.CronometrArr[i].update()              #actualizar registro i
                        self.TiempoStr = self.TimeRegePattern.format(self.CronometrArr[i].tiempo[0],
                                                                     self.CronometrArr[i].tiempo[1],
                                                                     self.CronometrArr[i].tiempo[2]) #Dar formato a los tiempos
                        wx.CallAfter(self.LblTimeRegeArr[i].SetLabel,self.TiempoStr)                 #Actualizar Texto correspondiente
                        #wx.callafter es un modo seguro de actualizar GUI desde un hilo.
            else:
                break #Romper el ciclo infinito

    def onTggleBtn(self, evt):
        BTN      = evt.GetEventObject()  #obtener el objeto boton que emite la señal
        BtnID    = BTN.GetLabelText()    #obtener el texto del boton
        BtnIdInt = int(BtnID)            #convertir el texto del boton en entero.
        if BTN.GetValue():
            self.CronometrArr[BtnIdInt].reset()          #reestablecer conteo
            self.CronometrArr[BtnIdInt].activado = True  #iniciar conteo
        else:
            self.savetimefile(BtnID)                     #Guardar conteo
            self.CronometrArr[BtnIdInt].activado = False #detener conteo

    def savetimefile(self,timer):
        f = open(timer+".txt", "a+")
        f.write(self.TiempoStr+"\n")
        f.close()
        
    def createFiles(self):
        for x in range(self.numtimers):
            open(str(x)+".txt","w+").close()

    def onClosed(self, evt):
        self.exitFlag = True #Salir del ciclo while del hilo
        self.Destroy()       #Destruir TODO

if __name__ == "__main__":
    app = wx.App()
    frame = MyForm().Show()
    app.MainLoop()

sábado, 1 de junio de 2019

SINTETIZADOR DE VOZ EN ESPAÑOL PARA PYTHON 3 (OFFLINE)





En este post describiré cómo instalar pyttsx3 con espeak  y pondré unos programas ejemplo, donde el primero es un ejemplo del uso básico, y el segundo es una pequeña aplicación que dice la hora o alguna frace pequeña cada vez que se presiona un botón.

pytssx3 es una librería de interfase entre python 3 y los motores de texto a voz tales como SAPI5 (windows), espeak (Linux o windows) y NSSS (Mac). Ahunque existen otras librerías de conversión de texto a voz como gTTS (Google Text to Speech) con la cual se obtiene una voz mucho más realista, solo se pueden realizar 50 sintetizaciones de voz por día (no good) y requiere de una conexión permanente a internet (no good). Otra diferencia es que gTTS solo puede generar archivos de sonido que uno debe reproducir para escuchar, mientras que en pytssx3 se puede generar la señal audible (hacer hablar) y opcionalmente guardar como archivo de sonido.

Se recomienda usar la versión de python mas reciente. A continuación pondré los pasos a seguir para instalar lo necesario.

1. Instalar espeak.

En FEDORA, correr como root (super usuario) el siguiente comando en una terminal:

      dnf install espeak

con esto se instalan espeak y todas las voces con sus variantes de las voces disponibles.

En UBUNTU o RASPBIAN (Raspberry pi 3) correr como root (super usuario) el siguiente comando en una terminal:
     
      sudo apt install espeak

con esto se instalan espeak y todas las voces con sus variantes de las voces disponibles..

En Windows se debe instalar con el archivo ejecutable, lo puede descargar de la siguiente dirección:

 http://espeak.sourceforge.net/download.html

En el instalador se requiere especificar que voces se quieren instalar. Por ejemplo si quiero instalar el idioma español latino agrego spanish-latin-am. Además existen varios modificadores de voz, como por ejemplo +f5 (variante femenia de en este idioma). Si quisiera instalar una la voz con esa variante,  podría poner spanish-latin-am+f5 en el instalador, como se muestra en la imagen de abajo.

Tambien esta el español de españa que es spanish. Los modificadores aplicables a cualquier voz son los siguientes:
corak, f1, f2, f3, f4, f5, m1, m2, m3, m4, m6, m7, whisper, whisperf, klatt, klatt2, klatt3, klatt4.



Ejemplo: instalasr español latino con modificador +f5  y español latino normal

Si quieres instalar todas las voces en español con todas las variantes, copia y pega esto: 

spanish-latin-am+corak spanish-latin-am+whisper spanish-latin-am+whisperf spanish-latin-am+f1 spanish-latin-am+f2 spanish-latin-am+f3 spanish-latin-am+f4 spanish-latin-am+f5 spanish-latin-am+m1 spanish-latin-am+m2 spanish-latin-am+m3 spanish-latin-am+m4 spanish-latin-am+m5 spanish-latin-am+m6 spanish-latin-am+m7 spanish-latin-am+klatt spanish-latin-am+klatt2 spanish-latin-am+klatt3 panish-latin-am+klatt4 spanish+corak spanish+whisper spanish+whisperf spanish+f1 spanish+f2 spanish+f3 spanish+f4 spanish+f5 spanish+m1 spanish+m2 spanish+m3 spanish+m4 spanish+m5 spanish+m6 spanish+m7 spanish+klatt spanish+klatt2 spanish+klatt3 spanish+klatt4

2.- Instalar librerías de Python 3:

En FEDORA o UBUNTU o RASPBIAN o Windows poner en una terminal o linea de comandos:

       pip3 install pyttsx3

Posiblemente nesecites correrlo como root (super usuario).

En WINDOWS podrías necesitar instalar también pypiwin32 si arroja un error al importar la librería en python, pero es mejor ir a lo seguro e instalarla desde el inicio.

      pip3 install  pypiwin32

EJEMPLO 1 (Uso básico):

En el programa que se muestra más abajo, se ejemplifica el uso de pyttsx3. En este ejemplo se inicia por averiguar cuáes voces que están disponibles. Por ejemplo: en linux la voz en español latino es "spanish-latin-am", en windows es algo parecido a "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\eSpeak_2" (es posible que tengas que cambiarlo en el programa de abajo, debido a que el nombre depende de cuantos idiomas y variantes hayas instalado). Después se elige la voz en español, en seguida se ajusta la velocidad y volumen de la voz elegida y por último pronuncia la célebre frase "Hola mundo".


#!/usr/bin/env python3
#-*- coding: utf-8 -*-
#------------------------------------------------------
#  Desarrollado por Carlos Alberto Rodriguez Martinez
#  http://mecatroncharly.blogspot.com/p/blog-page.html
#
#  Este es un programa que dice "Hola mundo" con voz audible
#------------------------------------------------------

from   platform import system
import pyttsx3

ThisOS = system() # averiguar Sistema operativo actual
engine = pyttsx3.init()    # inicializar motor de voz
#---------------------------------------
#     Encoontrar voces isponibles
#---------------------------------------
voices = engine.getProperty('voices')
male_language_count   = 0
female_language_count = 0
none_language_count   = 0
for voice in voices:
    print("------------------------")
    print(" - ID: %s"        % voice.id)
    print(" - Nombre: %s"    % voice.name)
    print(" - Lenguajes: %s" % voice.languages)
    print(" - Genero: %s"    % voice.gender)
    print(" - Edad: %s"      % voice.age)
    if voice.gender == 'male':
        male_language_count += 1
    if voice.gender == 'female':
        female_language_count +=1
    if voice.gender == 'none':
        none_language_count += 1
print ('Voces Masculinas : %d' % male_language_count)
print ('Voces femeninas  : %d' % female_language_count)
print ('Voces neutras    : %d' % none_language_count)
#--------------------------------------
#      CONFIGURAR VOZ E IDIOMA
#--------------------------------------
if ThisOS == 'Linux':
    engine.setProperty('voice', 'spanish-latin-am+f5') #ASIGNAR Español latino
    # agregar +f5 para simular voz femenina (1-5)
if ThisOS == 'Windows':
    engine.setProperty('voice','HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\eSpeak_4')
engine.setProperty('rate'  ,140)   # Aumentar velocidad 40%
engine.setProperty('volume',1.0)   # Poner el volumen al 100%: Minimo:0, Maximo: 1
#--------------------------------------
#               HABLAR
#--------------------------------------
engine.say("Hola mundo!")          # Hablar
engine.runAndWait()                # Esperar a que terine de Hablar
engine.stop()                      # Detener motor tts


EJEMPLO 2

Este programa es un poco mas complicado pero mas cool,  la aplicación dice la hora actual o dice cualquier otra cosa que se le indique. Se utiliza una cola FIFO (librería "queue") donde se ponen en cola las fraces (que podría ser la hora o la frase que se indique) cada vez que se presiona el botón correspondiente. La librería "threading" se utiliza para crear un hilo separado del hilo principal. Este hilo se encarga de revisar constantemente si hay fraces en cola, si es asi, llama a la funcion que dice las fraces que haya en cola, dependiendo del numero de veces que se haya presionado el botón. El programa también tiene su control de velocidad, de volumen y selección de voces en español.

Interfase gráfica en windows.
La interfase en Linux cambia un poco debido a que se pueden combinar los mods con las voces.

Interfase gráfica en Linux.



Para correr este ejemplo nesesitas tener instaladas las librerías: "wxpython" y "queue". Este programa fué probado en windows 7 y Linux (Fedora 30) con python 3.6 y 3.7.

Te dejo comentarios en el código fuente para ayudatre a entender que es lo que sucede.


#!/usr/bin/env python3
#-*- coding: utf-8 -*-
#------------------------------------------------------
#  Desarrollado por Carlos Alberto rodriguez Martinez
#  http://mecatroncharly.blogspot.com/p/blog-page.html
#
#  Este es un programa que dice la hora con voz audible
#------------------------------------------------------

import wx
import pyttsx3
from   platform  import system
from   datetime  import datetime
from   queue     import Queue
from   time      import sleep
from   threading import Thread

class SayTimeApp(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self,None,-1,"Di la hora")
        self.ThisOS = system()        # averiguar Sistema operativo actual.
        self.createGUI()              # Crear los elementos de la GUI
        self.Lista = Queue()          # Una cola FIFO para que no se traslapen las funciones
        self.thread = Thread(target = self.WrkrThred) # poner funcion WrkrThred en un hilo
        self.thread.daemon = True     # Poner hilo en modo daemon, si el programa termina, tambien el hilo
        self.thread.start()           # Iniciar hilo
        self.VoiceNames = []          # Para guardar los nombres de las voces
        self.voicesIDs  = []          # para guardar los IDs de las voces (winndows)
        self.getVoiceList()           # Averiguar voces disponibles y llenar combobox
  
    def createGUI(self):
        MainPanel = wx.Panel(self) # contenedor de Frames
                               #es util para separar el frame principal de las barras de herramientas y
                               #la barra de estado
        BtnDiHora = wx.Button(MainPanel,
                           label = "Decir la hora",
                           pos   = (125, 10),
                           size  = (300, 50))
        #----------------------------------------------------------------
        BtnDiTexto = wx.Button(MainPanel,
                           label = "Decir esto",
                           pos   = wx.DefaultPosition,
                           size  = wx.DefaultSize)
        self.TxTDecir = wx.TextCtrl(MainPanel,
                                    wx.ID_ANY,
                                    wx.EmptyString,
                                    wx.DefaultPosition,
                                    wx.DefaultSize,
                                    0)
        DecirAlgoSizr = wx.BoxSizer(wx.HORIZONTAL)
        DecirAlgoSizr.Add(BtnDiTexto,  0, wx.ALL, 5)
        DecirAlgoSizr.Add(self.TxTDecir, 1, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #----------------------------------------------------------------
        self.ComboVoices = wx.ComboBox(MainPanel,
                                       choices = [],
                                       style   = wx.CB_READONLY|wx.ALIGN_CENTER)
        StticBoxVoices  = wx.StaticBox(MainPanel, -1, 'Voz')
        StticBVoicsSizr = wx.StaticBoxSizer(StticBoxVoices, wx.VERTICAL)
        StticBVoicsSizr.Add(self.ComboVoices, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #----------------------------------------------------------------
        self.SpeedSlider = wx.Slider(MainPanel,
                                     value    = 140,
                                     minValue = 50,
                                     maxValue = 300,
                                     style    = wx.SL_HORIZONTAL|wx.SL_LABELS)
        StticBoxSpeed    = wx.StaticBox(MainPanel, -1, '% Velocidad')
        StticBxSpeedSizr = wx.StaticBoxSizer(StticBoxSpeed, wx.VERTICAL)
        StticBxSpeedSizr.Add(self.SpeedSlider, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #----------------------------------------------------------------
        self.VolumSlider = wx.Slider(MainPanel,
                                     value    = 100,
                                     minValue = 0,
                                     maxValue = 100,
                                     style    = wx.SL_HORIZONTAL|wx.SL_LABELS)
        StticBoxVolume    = wx.StaticBox(MainPanel, -1, '% Volumen')
        StticBxVolumeSizr = wx.StaticBoxSizer(StticBoxVolume, wx.VERTICAL)
        StticBxVolumeSizr.Add(self.VolumSlider, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #----------------------------------------------------------------
        if self.ThisOS == 'Linux':   #en linux
            mods = ['f5','f4','f3','f2','f1','klatt4','klatt3','klatt2','klatt',
                    'm7','m6','m5','m4','m3','m2','m1','whisper','whispef','croak',]
            self.ComboMods = wx.ComboBox(MainPanel,
                                       choices = mods,
                                       style   = wx.CB_READONLY|wx.ALIGN_CENTER)
            StticBoxMods  = wx.StaticBox(MainPanel, -1, 'Mods')
            StticBoxModsSizr = wx.StaticBoxSizer(StticBoxMods, wx.VERTICAL)
            StticBoxModsSizr.Add(self.ComboMods, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        #------------------------------------------------------------
        #                    BINDS
        #------------------------------------------------------------
        BtnDiHora .Bind(wx.EVT_BUTTON, self.DiHora)
        BtnDiTexto.Bind(wx.EVT_BUTTON, self.DiTexto)
        #------------------------------------------------------------
        #                    Main layout
        #------------------------------------------------------------
        MainSizer = wx.BoxSizer(wx.VERTICAL)
        MainSizer.Add(BtnDiHora,       0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        MainSizer.Add(DecirAlgoSizr,   0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        MainSizer.Add(StticBVoicsSizr, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)

        if self.ThisOS == 'Linux':   #en linux
            MainSizer.Add(StticBoxModsSizr,   0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
            
        MainSizer.Add(StticBxSpeedSizr,  0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        MainSizer.Add(StticBxVolumeSizr, 0, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        MainPanel.SetSizer(MainSizer)
        
        MainPanel.Layout()
        MainPanel.Fit()
        self.Layout()
        self.Fit()

    def DiTexto(self,evt):
        Texto = self.TxTDecir.GetValue()
        if Texto != "": #S no hay algo, no hacer nada
            self.Lista.put(Texto)

    def DiHora(self, evt):
        # obtener frace 
        self.Lista.put(self.getcurrTime()) # poner esta frace en cola.
        
    def getcurrTime(self): 
        currentTime   = datetime.now()             #Que hora es ahora
        currminuto    = currentTime.strftime('%M') #obtener minuto como string
        hora   = currentTime.strftime('%I')        #obtener hora como string
        if hora[0] == '0':
            hora = hora[0 : 0 : ] + hora[0 + 1 : :]   #si hay cero a la izquierda, quitarlo
        if currminuto[0] == '0':
            currminuto = currminuto[0 : 0 : ] + currminuto[0 + 1 : :] #si hay cero a la izquierda, quitarlo
        if currminuto == '0':
            currminuto = ''

        texto = ""
        if hora == '1':
            texto = "Es la una "+ str(currminuto)
        else:
            texto = "Son las "+str(hora)+" "+ str(currminuto)
            
        return texto

    def WrkrThred(self): #Esta es la funcion del ilo (thread)
        while True:
            sleep(0.1)
            if self.Lista.empty() == False:           # Mientras que la lista no este vacia
                self.SaySomething(self.Lista.get())   # Decir todo lo que este en cola
            
    def SetupVoice (self):  # configurar voz
        if self.ThisOS == 'Linux':   #en linux
            self.engine.setProperty('voice', self.ComboVoices.GetStringSelection()+"+"+self.ComboMods.GetValue())

        if self.ThisOS == 'Windows': #en windows
            index = self.ComboVoices.GetSelection()                # obtener el indice de seleccion del combobox
            self.engine.setProperty('voice',self.voicesIDs[index]) # obtener el nombe ID de la voz y asignarla

        self.engine.setProperty('rate'  ,self.SpeedSlider.GetValue())     # Asignar % velocidad 
        self.engine.setProperty('volume',self.VolumSlider.GetValue()/100) # Asignar volumen: Minimo:0, Maximo: 1 (por eso /100)
        
    def SaySomething(self, something):
        self.engine = pyttsx3.init()   # inicializar motor de voz.
        self.SetupVoice()              # inicializar voz
        self.engine.say(something)     # Indicar que es lo que se va a decir
        self.engine.runAndWait()       # Esperar a que terine de Hablar
        self.engine.stop()             # Detener motor

    def getVoiceList(self):
        engine = pyttsx3.init()   # inicializar un motor de voz localmente en esta funcion
        voices = engine.getProperty('voices')
        engine.stop()             # Parar el motor

        for voice in voices:
            if voice.name.lower().find("spanish") !=-1: # Restringir las voces al espanol
                self.VoiceNames.append(voice.name)
                self.voicesIDs .append(voice.id)
        self.ComboVoices.SetItems(self.VoiceNames)
        self.ComboVoices.SetSelection(0)

if __name__ == '__main__':
    app = wx.App()
    frame = SayTimeApp()
    frame.Show()
    app.MainLoop()