martes, 17 de septiembre de 2024

Python Sockets, manipular datos recibidos por cliente

Ahora que ya tengo en marchar Terminal DCC, una de las cosas que más me ha llamado la atención es la plasticidad de la biblioteca "sockets".

¿Que son los sockets?

De una manera simple, son los puntos de conexión entre procesos de diferentes sistemas a través de una red.

Con sockets no solo podemos transmitir texto entre cliente y servidor, también números, tanto int como float, binarios como imágenes o vídeo en resumen podemos enviar cualquier información que pueda ser representada como una secuencia de bytes.

Los usos más comunes que se le da a los sockets son como:

  • Servidor web
  • Transferencia de archivos
  • Clientes de chat
  • Juegos en red

En este ejemplo recibiremos los datos desde un servidor en formato bytes y los pasaremos a una codificación "UTF-8" para poder manejarlo como un String desde la misma u otra clase diferente.

Conceptos básicos:

  • Socket: Es el punto de conexión entre dos procesos. Se caracteriza por una dirección (IP) y un puerto.
  • Dirección IP: Es una etiqueta numérica única que identifica un dispositivo en una red.
  • Puerto: Es un número que identifica un servicio específico en un dispositivo.
  • TCP/IP: Es el protocolo de comunicación más utilizado en Internet. TCP (Transmission Control Protocol) garantiza la entrega ordenada y fiable de datos, mientras que IP (Internet Protocol) se encarga del enrutamiento de paquetes de datos. 
  •  

Consideraciones importantes:

  • Serialización: Para enviar objetos de Python, debes serializarlos en una secuencia de bytes. Pickle es un módulo estándar de Python que permite la serialización de objetos, pero ten en cuenta que los objetos serializados con pickle pueden no ser compatibles entre diferentes versiones de Python.
  • Protocolo: El protocolo de comunicación que utilices (TCP, UDP, etc.) influirá en la forma en que se envían y reciben los datos.
  • Codificación: Es crucial elegir la codificación correcta para los datos de texto. La codificación UTF-8 es muy común, pero existen otras opciones.

Empecemos con el servidor:

Tanto el servidor como el cliente son clases. Para llamar a la clase "serverSocket" tenemos que pasarle los parámetros "IP" y "puerto"


## Inicia el programa principal
if __name__ == '__main__':
	# Inicia el socket con los parametros IP y puerto
	# Si no especificamos una dirección IP y usamos un 
	# String vacío, socket usará la IP de la maquina.
	server = serverSocket("", 1314)

	# Inicia el servidor
	server.iniciar()
    

Al iniciar el servidor con "python3 class_server_test.py", aparecerá el siguiente mensaje


[START] Server starting at 192.168.0.25
[LISTENING] Waiting for connections... 

Con esto ya tenemos el servidor a la escucha esperando conexiones desde los clientes

Cliente conectando con el servidor:

Del otro lado tenemos al cliente al que llamaremos "clienteSocket" desde el archivo "class_cliente_test.py" y al que tenemos que pasar los parámetros que usará para establecer la conexión con el servidor y son la IP del servidor, el puerto que esta a la escucha en el servidor y el número de cliente en caso de que conectemos mas de un cliente.


## Inicia el programa cliente
if __name__ == '__main__':
	# Establece conexión con el servidor
	cliente = clienteSocket("192.168.0.25", 1314, 1)

	# Inicia la conexión con el servidor
	estado = cliente.iniciar()
	
	# Imprime en pantalla el estado del servidor
	print(estado)

	# Si el estado contiene la palabra "CONNECTED"
	if "CONNECTED" in estado:

		# Envía un mensaje al servidor comunicando que estamos conectados
		cliente.clientSendData("Cliente conectado...")
		
		# Inicia un bucle con la respuesta del resta del servidor
		while True:

			# imprime los datos desde el cliente
			print(cliente.get_client_data())

			cliente.clientSendData(cliente.get_client_data())
			
			# Espera 2 segundos para volver a leer los datos
			time.sleep(2)

Si todo esta correcto en la pantalla del cliente se imprime el mensaje:


	[CONNECTED] 192.168.0.25:1314
    
	[OK] datos recibidos
    

Ahora ya tenemos el sistema conectado. Pero nosotros los datos que necesitamos no se van a quedar en el la clase cliente, queremos mostrarlos en otra pantalla como una interfaz con Tkinter o pyTermTk.

Los datos los obtenemos desde la función "cliente.get_client_data()" Es necesario tenerlo en bucle "while" o un "hilo" para que este constantemente a la escucha de los nuevos datos


## Iniciar el cliente desde otra clase:
cliente = clienteSocket("192.168.0.25", 1314, 1)
cliente.iniciar()

# Obtener datos del cliente desde otra clase:
datas = cliente.get_client_data()

Y de está manera tenemos los datos de una conexión socket disponible para conectar cualquier dispositivo a nuestra red. 

 

Conexión Cliente - Servidor


Con algunas modificaciones, estos ejemplos pueden servir para conectar dispositivos tan dispares como:

  • Ordenadores: Sobremesa, portátil, servidor...
  • Raspberry Pi: En todas sus versiones
  • Microcontroladores: Dispositivos como ESP8266 o ESP32

Programa de ejemplo de este articulo en GitHub

Espero que este ejemplo sea de ayuda y mejore las conexiones entre vuestros dispositivos, tanto tradicionales como IoT.

 Saludos!!

miércoles, 11 de septiembre de 2024

Terminal DCC - Controla trenes DCC la terminal

Muy Buenas a todos!!! Hoy traigo un programa creado con el ya comentado creador de interfaces de terminal pyTermTk. He vuelto a juntar las dos cosas que más me gustan, la informática y los trenes (si, muy a lo Sheldon).

Ya hacía tiempo que le iba dando vueltas a un controlador desde el terminal que no fuera estar metiendo instrucciones a mano desde una terminal. Entonces hace unos meses descubrí pyTermTk y empece a trabajar con el.

Tengo que decir que el diseñador de interfaces ttkDesigner ayudó mucho en el proceso, algunas veces me ha dado algún quebradero de cabeza, pero más por inexperiencia con la librería

También se han incluido los archivos json de las interfaces generados por ttkDesigner en la carpeta del repositorio

Terminal DCC esta diseñado para leer una base de datos generada con Rocrail y conectarnos a una central DCC de tipo hazlo tu mismo (DIY).

Para seleccionar el archivo desde un file picker que se abrirá al pulsar sobre el botón de menú "open". Desde aquí seleccionaremos el archivo .xml donde estén almacenados los datos del plan Rocrail.

 

Cuenta con dos pantallas principales llamadas Controller y List. En la primera de ellas, Controller, podemos manejar las locomotoras. Por otra parte List nos mostrará un resumen de los elementos almacenados en el archivo plan.xml


A la ventana configuración accedemos desde el menú superior "File", en "Config". Una vez que se abre la pantalla, encuentran los parámetros IP, que será la IP de la central DCC y el puerto que por defecto es el "2560" para realizar la conexión con wi-fi y sockets.

También se pueden configurar las pantallas Controller y List para que aparezcan al iniciar Terminal DCC.

Con estos datos ya podremos realizar la conexión pulsando sobre "Connection On/Off". Si todos los datos son correctos recibiremos un mensaje de confirmación:


--> [CONNECTED] to DCC station 192.168.1.5:2560

Una vez conectados podemos interactuar con la central introduciendo comandos con el widget de texto en la parte inferior. 

Con el botón "DCC On/Off" encendemos o apagamos la central DCC. Con la cental encendida ya podemos  manejar locomotoras desde el Controller con sus funciones básicas de avance, retroceso, parada. Las funciones de las locomotoras aún no están implementadas.

 


Instalación: 

Para instalar Terminal DCC solo necesitamos tener acceso a un terminal linux.

Una vez estamos en el terminal creamos un entorno virtual donde instalaremos las librerías y accedemos a el:


python3 -m venv ~/venv/TerminalDCC

source ~/venv/TerminalDCC/bin/activate

Nos situamos en la carpeta donde vamos a descargar Terminal DCC y lo podemos descargar directamente desde GitHub con la "git":


git clone https://github.com/Peyutron/TerminalDCC

Instalamos las librerías del archivo "requirements.txt" con "pip":


pip install -r requirements.txt

Ya solo queda ejecutar el programa:


python3 -m TerminalDCC_main
o
python3 TerminalDCC_main.py

 Bien pues esto es todo, las funciones son básicas y seguramente haya otro programa que no sea Rocrail, pero por el momento me ha servido para practicar Python con sus librerías, listas, diccionarios y sockets entre otros. La intención es actualizar la entrada cuando vaya haciendo cambios en el programa.

 

Si te gusta la programación y el contenido que realizo puedes aportar comentando tanto aquí como en el canal de Youtube.


Terminal DCC en GitHub

Y aquí tienes algunos ejemplos sobre TermTk y ttkDesigner:

pyTermTk y ttkDesigner: Button y Lablel

pyTermTk y ttkDesigner: List, ListSelect y Lablel

pyTermTk y ttkDesigner: Slider y Label

Librería PyTermTk crea interfaces en terminal

Librería PyTermTk en GitHub

Sígueme para más artículos sobre TermTk y otros temas de programación, Saludos!!!

miércoles, 4 de septiembre de 2024

pyTermTk y ttkDesigner: Abrir y guardar archivo EditText con File Picker

Seguimos viendo ejemplos de como crear interfaces con ttkDesigner. Hemos visto botones y labels, listas o sliders. Los siguiente widgets que vamos a ver es el widget FilePicker y Text Edit.

¿Que es un File Picker?

Es una herramienta visual que nos permite seleccionar un archivo específico desde tu sistema de archivos. Es como una ventana emergente que te muestra la estructura de tus carpetas y archivos, y te permite navegar entre directorios hasta encontrar la carpeta o archivo que deseas. Con el se pueden abrir archivos, guardar los datos o la selección múltiple de archivo.

¿Que es un Text Edit?

El widwet Text Edit nos permite crear áreas de texto multilinea es una especie de caja donde el usuario puede escribir o pegar texto de varias líneas, como si fuera un pequeño editor de texto dentro de la aplicación. Con Text Edit podemos mostrar información, capturar texto y editarlo. Sus características principales pueden ser la capacidad de ser multilínea, incluir una barra de scroll y con un poquito de programación se pueden aplicar diferentes formatos al texto como negrita, cursiva, subrayado, etc.

Como ya hemos hablado anteriormente sobre como crear una interfaz, vamos crear una con los siguientes elementos:

  • Un widget tipo File Picker al que llamaremos "Tfpicker_open"
  • Un widget tipo File Picker al que llamaremos "Tfpicker_save"
  • Un widget tipo Label al que llamaremos "lb1"
  • Un widget tipo TextEdit al que llamaremos "textedit"

  • ttkDesigner

Para este ejemplo necesitamos crear un archivo llamado "texto.txt", puede contener el texto que queramos o incluso estar vacío.
Este archivo lo situaremos en la misma carpeta del proyecto. No es obligatorio que el archivo este en esa ubicación pero para este ejemplo sera suficiente.

En el programa nos vamos a encontrar con la definición de los widgets y su conexión con las funciones:


# Conectamos el Filepicker con el evento "abrirArchivo"
self.getWidgetByName("Tfpicker_open").filePicked.connect(self.abrirArchivo)

# Texto que se mostrará en el widget Filepicker "Tfpicker_save"
self.getWidgetByName("Tfpicker_save").setText("Save file as...")

# Conectamos el Filepicker con el evento "guardarArchivo"
self.getWidgetByName("Tfpicker_save").filePicked.connect(self.guardarArchivo)

# TTkLabel
self.lb01 = self.getWidgetByName("lb1")
self.lb01.setText("No hay datos")

# TTkLabel
self.text_edit = self.getWidgetByName("textedit")
self.text_edit.setText("No hay datos")

Y este sera el resulatado, Se puede modificar el texto que muestra File Picker con ".setText" y poner el mensaje que queramos.

TermTk con File Picker y Text Edit

Ahora conectamos esos objetos a sus funciones slots. El primero llamara a la función "abrirArchivo".


# Rutina generica para abrir/leer un archivo
    pyTTkSlot(str)
    def abrirArchivo(self, fileName):
        
        with open(fileName) as fp:
            # Pone el la ruta y el nombre en el widget "lb1"
            self.lb01.setText(f"Open: {fileName}")
            
            # Pone el contenido del archivo en el "textedit"
            self.text_edit.setText(fp.read())
            

Ventana emergente File Picker

Una vez que seleccionemos el archivo "texto.txt" (o vuestro archivo preferido). El texto se mostrará en el Text Edit como se muestra a continuación:

Archivo "texto.txt" mostrado en Text Edit

 

La otra función que llamara el programa se llama "guardarArchivo".


    # Rutina generica para guardar el contenido del widget "textedit"
    pyTTkSlot(str)
    def guardarArchivo(self, fileName):

        # Guarda el contenido del widget "textedit" a un 
        # archivo con el nombre asignado
        with open(fileName, 'w') as fp:
            self.lb01.setText(f"saved: {fileName}")
            fp.write(self.text_edit.toPlainText())
            

Este es el ejemplo completo. Con el podemos abrir un archivo de texto, editarlo y guardarlo. Además La ruta del archivo que abramos se muestra en el Label situado en la parte superior de la ventana.


from TermTk import TTkUtil, TTkUiLoader, TTk, TTkContainer, pyTTkSlot


class ClassFilePicker(TTkContainer):
    def __init__(self):

        
        # Data generated using ttkDesigner
        TTkUiLoader.loadDict(TTkUtil.base64_deflate_2_obj(
    "eJyFlMtP20AQhx3ycEJAtIUGFaTKx9BDBD1UqmirQsqjmISoROVQVdXGXjIrHG9kr3lUQuoxkea4/X87axtEQbSxLO/M7OP7zczmV+l3pWylv2vdRPucR7GQocby69Z6" +
    "a11jUSVCm1DZC1gca5zr98/aMlRMhDzSWBmziI3idEqpy0Zc42yHYici9OWFxmpPxkKZLb/rpltyZziWjsVPnpoDd5VjrSNC50T4CrRr0WKy9rkYgjJmrcMu8+CBZRVM" +
    "nBx5PPPYX0UsBgHXE7R3QkYj3wz7UgZ9MdZoabR7zPdFOEwPtbKHY+WQXclEaayRpHycYCXIRiQIyrAC9jWJ2ONyxFV0lS8nbqVjrHogAj/iRls6Pd0pV24WNqGGpf6F" +
    "CDXUM/kFDvNmtOkuc1hwLXhC79NUCDzLPosTWJrAc2jAsplZdAvm4fACVrCyLSOf0j7Bcl8o0gwNnMsOdHYDNoxNTqxqAqt38OFlRv3WXSRqcGjXjLaa6h7w4Ba2GAw2" +
    "ctYCpShjbZjTF1ybWAuPsZJMfkmptLvSORUEhuW2DCQ1SGn12/qIcrwViGE44qEpa4LFiFJE9S16MjBfm+x4zELtFtAmXz5OMtBFAt2lXbcTpWTYE94Z5eCGeb5/Ok49" +
    "P+SYh7f0N5lecouGvk70xUcz/QZrR7Q4Q4e1CdbawL0z00x6inZqUFtNsdRj1IpYaJHUNhunjY11w+Z8EiyQQ/KTpegaaZzfCgLHxGKn+WqN5m15Hh8rpyN9njZ3ui63" +
    "EtikVnhH73tK84cb7fDxoc6YnfNcZ+O+zpl/66R3bQK7U9ibwj58hoO/4eHwPjR0iejoUTysU2lM6Xd8oW5LUlXk4aknr0Y5p9xw5//b91g/pP8Wp5uMBlTmKS7dMZ1j" +
    "xSJlrrLJ3xfOfOcoDK5o1mwnCZRwzFw9eUjLH7pYkmDdk2HIPVPHmG5x0voDJL2grQ=="), self)



        # Conectamos el Filepicker con el evento "abrirArchivo"
        self.getWidgetByName("Tfpicker_open").filePicked.connect(self.abrirArchivo)
        
        # Texto que se mostrará en el widget Filepicker "Tfpicker_save"
        self.getWidgetByName("Tfpicker_save").setText("Save file as...")
        
        # Conectamos el Filepicker con el evento "guardarArchivo"
        self.getWidgetByName("Tfpicker_save").filePicked.connect(self.guardarArchivo)

        # TTkLabel
        self.lb01 = self.getWidgetByName("lb1")
        self.lb01.setText("No hay datos")

        # TTkLabel
        self.text_edit = self.getWidgetByName("textedit")
        self.text_edit.setText("No hay datos")

    # Rutina generica para abrir/leer un archivo
    pyTTkSlot(str)
    def abrirArchivo(self, fileName):
        
        with open(fileName) as fp:
            # Pone el la ruta y el nombre en el widget "lb1"
            self.lb01.setText(f"Open: {fileName}")
            
            # Pone el contenido del archivo en el "textedit"
            self.text_edit.setText(fp.read())

    
    # Rutina generica para guardar el contenido del widget "textedit"
    pyTTkSlot(str)
    def guardarArchivo(self, fileName):

        # Guarda el contenido del widget "textedit" a un 
        # archivo con el nombre asignado
        with open(fileName, 'w') as fp:
            self.lb01.setText(f"saved: {fileName}")
            fp.write(self.text_edit.toPlainText())


if __name__ == '__main__':
    root=TTk()
    root.layout().addWidget(ClassFilePicker())
    root.mainloop()

Este es otro ejemplo de como TermTk ofrece una forma intuitiva y eficiente de crear interfaces para terminal. El manejo de archivos es algo básico en una interfaz donde queramos cargar alguna configuración, guardar los datos que hayamos creado o modificado facilitando así la interacción del usuario con el sistema de archivos.

Aquí tienes algunos ejemplos sobre TermTk y ttkDesigner:

Programa de ejemplo de este articulo en GitHub

pyTermTk y ttkDesigner: Button y Lablel

pyTermTk y ttkDesigner: List, ListSelect y Lablel

pyTermTk y ttkDesigner: Slider y Label

Librería PyTermTk crea interfaces en terminal

Librería PyTermTk en GitHub

Sígueme para más artículos sobre TermTk y otros temas de programación, Saludos!!!

domingo, 1 de septiembre de 2024

Raspberry Pi 3 Oled 0.96 I2C Adafruit

 Hace un tiempo conecte la pantalla Oled I2C con la librería "lemur", tenéis toda la información en la entrada 'Raspberry Pi 2 con Oled 128x64 I2C'.

Ahora lo quiero aplicar a la Raspberry Pi 3 y añadirlo al ventilador pwm que ya instalamos anteriormente así que aprovecho para darle una actualización a la entrada anteriormente enlazada y añadir algunos pasos más.

La librería que usamos anteriormente está descontinuada ahora la información ahora la información esta en la propia web de Adafruit. Los cambios son mínimos ya que solo instalando la librería y cambiando las lineas de configuración todo vuelve a funcionar como antes. Veamos como instalar las librerías que relacionadas adafruit-circuitpython-ssd1306.


sudo apt-get install python3-pip	

pip3 install adafruit-circuitpython-ssd1306

sudo apt-get install python3-pil

sudo apt-get install python3-numpy

Para usar pantalla Oled 128x64 I2C hay que importar la librería de la siguiente manera:


import board
import adafruit_ssd1306
from PIL import Image, ImageDraw, ImageFont

# Use for I2C.
i2c = board.I2C()  # uses board.SCL and board.SDA

try:
	disp = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C, reset=RST)
    print("\nAdafruit SSD1306 ok")
except Exception as errors:
	print("\nError Adafruit SSD1306:",str(errors), "\nPlease check Raspi-config\n")
	exit()

Una vez que tenemos la pantalla funcionando estos son los datos que vamos a mostrar:, 

  • IP de la red
  • Uso del CPU 
  • Temperatura del procesador
  • Uso de Ram
  • Velocidad del ventilador (PWM)
  • Tiempo que lleva encendido el sistema
  • Sistema operativo
Para conseguir todos estos datos que queremos mostrar en la pantalla usaremos las librerías externas:
  • psutil
  • subprocess
  • sys
Empecemos por la IP

La ip la podemos obtener con subprocess:



    ## Obtiene la ip del equipo
    # @ return String "0.0.0.0"
    def GetIP():
        return str(subprocess.check_output(['hostname', '-I'])).split(' ')[0].replace("b'", "")


Obtenemos la carga de CPU con psutil:


    ## Obtiene la carga de la CPU
    # @ return float
    def GetCPU():
        return psutil.cpu_percent(1)


La temperatura de la CPU con subprocess:


    ## Obtiene la temperatura de la CPU
    # @ return float
    def GetTemp():
        output = subprocess.run(['vcgencmd', 'measure_temp'], capture_output= True)
        temp_str = output.stdout.decode()
        try:
            return float(temp_str.split('=')[1].split('\'')[0])
        except (IndexError, ValueError):
            raise RuntimeError('no se puede obtener temperatura')


La RAM en uso mediante psutil:


    ## Obtiene la cantidad de RAM en uso
    # @ return float
    def GetRAM():
        return psutil.virtual_memory()[2]


El PWM que aplicamos al pin viene nos lo devuelve la clase healt_pwm_class.py:


    ## Obtiene la velocidad del ventilador
    # @ hpc: healt_pwm_class.System_PWM_Fan
    # @ type_ int: 0=Proporcional, 1=Por pasos de temperatura
    # @ return int: valor de pwm aplicado en el pin
    def GetFanPWM(sef, hpc, type_):
    	if type_ == 0:
        	return hpc.Proportional()
	else:
        	return hpc.Stepped()



El sistema operativo lo obtenemos gracias a sys:


    ## Función que devuelve el SO en uso
    # @ return String
    def getOS():
        platforms = {
        'linux1' : 'linux',
        'linux2' : 'linux',
        'darwin' : 'mac',
        'win32' : 'win32'
        }
        if sys.platform not in platforms:
            return sys.platform
        
        return platforms[sys.platform]    



Y por último el tiempo que lleva encendida la maquina con psutil:


    ## Obtiene el tiempo que lleva la maquina encendida
    # @ return String "0d 00h 00m 00s"
    def GetUpTime():
        boot_time_timestamp = psutil.boot_time()
        current_time_timestamp = time.time()
        uptime_seconds = current_time_timestamp - boot_time_timestamp
        uptime_minutes = uptime_seconds // 60
        uptime_hours = uptime_minutes // 60
        uptime_days = uptime_hours // 24
        uptime_str = f"{int(uptime_days)}d {int(uptime_hours % 24)}h {int(uptime_minutes % 60)}m {int(uptime_seconds % 60)}s"
        # print("Uptime: " + uptime_str)
        return uptime_str

Como todas las funciones están en una clase llamada "system_functions_class.py". También podemos llamar a estas funciones de manera individual directamente desde la clase de la siguiente manera:


    # Importamos la clase
    from system_functions_class import *
    
    sfc = System_Functions()
	# Retorna la IP del equipo
    ip = sfc.GetIP()
    
    # Retorna la temperatura de la CPU
    cpu_temp =.sfc.GetTemp()
    
    # Imprime los resultados de ip y cpu_temp
    print("IP: {}, CPU temp: {}".format(ip, cpu_temp))
    

 

¿Como usamos el programa?

Clonamos el repositorio rpi3_healt_system desde GitHub. Vamos a nuestra carpeta de proyectos y escribimos:


https://github.com/Peyutron/python-Warehouse/tree/main/Raspberry_Python/rpi3_healt_system

Con el programa en nuestro equipo, el ejecutable principal se llama "main_rpi_healt.py y lo ejecutamos con


python3 -m main_rpi_healt

Si todo ha ido bien con la instalación en el terminal recibiremos el siguiente mensaje:


Adafruit ssd1306 ok

y en la pantalla se dibujara lo siguiente:

Los datos que se muestran se guardan en un archivo JSON. Esto nos servirá en un futuro para enviar los datos a otros equipos o como consulta de la última vez que se ejecuto el programa. La información se guarda en un archivo llamado healt_datas.json y este es su contenido:


{
    "filename": "healt_datas.json",
    "ip": "192.168.1.39",
    "m0_cpuf": 39,
    "m0_cpul": 0.0,
    "m0_cput": 45.6,
    "m0_os": "linux",
    "m0_raml": 18.5,
    "m0_ut": "0d 4h 48m 55s"
}


Aquí tienes otros enlaces de este blog relacionados con Raspberry Pi:

Programa de ejemplo de este articulo en GitHub

Raspberry pi PWM

Poner Ip fija en Raspberry Pi

Información desde la web oficial de Adafruit


Un saludo a todos!!

miércoles, 28 de agosto de 2024

ManKat con RC Engine Sound ESP32

Rc Engine Sound
Una de las cosas que más realismo le da a un coche R/C de este tipo (camiones) es que puedan configurar de una manera sencilla y que que suene... que suene de verdad...

Si bien hay muchos "emuladores de sonido" para coches y camiones R/C. Aunque uno que alguno modelos sean asequibles para el bolsillo, parece que estemos en un juego de coches de los 80's en un 386...  Los modelos más caros suelen tener un buen sonido, pero suelen rondar los 50€~60€. Y pueden sonar bien, pero no tienen la cantidad de accesorios que incluye el repositorio del que vamos a tratar.

Empecemos!

Pues bien, navegando por GitHub encontré un proyecto llamado Rc_Engine_Sound_ESP32 de TheDIYGuy999, no solo cumple con las expectativas de sonido, Además también incluye todos los controles para luces como intermitencias o luces de freno y varios accesorios como 5ª rueda y el cabestrante.

PAM8403

En el apartado de sonido lo mejor que tiene esta amplificado con el módulo basado en el chipset PAM8403 altavoces de hasta 3W que dispone de dos canales de audio y hace que no tenga que parar un sonido para reproducir otro. 

Y la verdad es que suena muy bien, tanto en potencia como en calidad. Se pueden usar 2 altavoces  pueden usar cualquier potencia siempre que no supere los 3W. 

En mi caso use unos altavoces de algún pequeño equipo para móbiles que tenía por casa. 

Además incluye una gran cantidad de sonidos para motores de muchos vehiculos diferentes. En mi caso uso la motorización del MAN KAT y las luces como intermitencias y frenos. Una cosa que me gusto mucho es que con un motor se puede hacer que simule la vibración del motor. También simula el panel de control con un pequeño Oled de 80*160 al que se le puede cambiar la apariencia.

Cuenta con compatibilidad para señales PWM, PPM, SBUS e IBUS

Una de las cosas con las que me volví loco es que no se puede puede programar con las últimas versiones de placa la ESP32. Estas son las versiones tanto de ide como de placa que he usado y que han funcionado perfectamente:

  • Arduino IDE 1.8.19 y Arduino 2.X En la documentación se especifica que sea superior a Arduino IDE 1.8.7
  • Espressif Systems version 2.0.17

Antes de subir el código podemos cambiar algunos parámetros básicos. Hay archivos de configuración y son los siguientes:

  • src.ino
  • 0_generalSettings.h
  • 1_Veicle.h
  • 2_Remote.h
  • 3_ESC.h
  • 4_Transmission.h
  • 5_Shaker.h
  • 6_Lights.h
  • 7_Servos.h
  • 8_Sound.h
  • 9_Dashboard.h
  • 10_Trailer.h

src.ino

Este es el archivo principal y el que subiremos al ESP32. Todos los demás archivos deriban a este. Una de las cosas que podemos encontrar son los enlaces a las diferentes librerías que no están incluidas en el IDE de Arduino, Aquí teneís una lista con las que vamos a necesitar:

0_GeneralSettings.h

En la pestaña "0_GeneralSettings.h":

Aquí podemos configurar el Wi-Fi en el apartado:


// #define ENABLE_WIRELESS

1_Vehicle.h

El vehiculo que queremos simular lo tenemos en una extensa lista en el archivo "1_Vehicle.h" en mi caso selecciono "ManKat.h".


#include "vehicles/ManKat.h" // MAN KAT V8 Diesel German Bundeswehr military truck

Solo podemos seleccionar un vehículo al mismo tiempo

2_Remote.h

En la pestaña "2_Remote.h" Seleccionamos el transmisor, hay mucha variedad, desde IBUS a PWM, Estos son los que tiene predeterminados:

  • FLYSKY FS I6X
  • FLYSKY FS I6S LOADER
  • FLYSKY FS I6S EXCAVATOR
  • FLYSKY GT5
  • RGT EX86100
  • GRAUPNER MZ 12
  • MICRO RC
  • MICRO RC STICK

// #define RGT_EX86100	// ------- MT-305 remote delivered with RGT EX86100 crawler (use PWM communication setting)

3_ESC.h

También se puede modificar los valores del ESC y como pasaba con el apartado de transmisores, tenemos algunos valores predeterminados en el archivo "3_ESC.h":

  • QUICRUN FUSION
  • QUICRUN 16BL30
  • ESC DIR
  • RZ7886 DRIVER MODE

Otra apartado a tener en cuenta es el la protección de bateria, si no se utiliza hay que comentar la linea 132:


// #define BATTERY_PROTECTION	// This will disable the ESC output, if the battery cutout voltage is reached. 2 fast flashes = battery error!

En el caso de tener activada la protección de batería, cuando el nivel sea bajo, debería sonar un mensaje en el idioma que tengamos seleccionado.


#include "vehicles/sounds/OutOfFuelEnglish.h"
// #include "vehicles/sounds/OutOfFuelGerman.h"
// #include "vehicles/sounds/OutOfFuelFrench.h"
// #include "vehicles/sounds/OutOfFuelDutch.h"
// #include "vehicles/sounds/OutOfFuelSpanish.h"
// #include "vehicles/sounds/OutOfFuelPortuguese.h"
// #include "vehicles/sounds/OutOfFuelJapanese.h"
// #include "vehicles/sounds/OutOfFuelChinese.h"
// #include "vehicles/sounds/OutOfFuelTurkish.h"
// #include "vehicles/sounds/OutOfFuelRussian.h"

4_Transmission.h

En caso de tener una caja de marchas de mas de dos velocidades, podemos activar dentro del archivo 4_Transmission.h

  • Virtual de 3 velocidades
  • Virtual de 16 velocidades secuencial
  • Semiautomatico
  • Doble embrague

En mi caso tengo configurada la de 3 velocidades que es la que viene por predeterminada.


#define VIRTUAL_3_SPEED

5_Shaker.h

Para la simulación del motor use un pequeño motor de un mando USB que tenía por ahí. Este motor también se puede configurar desde la pestaña 5_Shaker.h, según el motor que pongamos tendremos que retocar un poco para que funcione correctamente:


#define GT_POWER_STOCK //------- Select (remove //) one of the shaker configurations below

#ifdef GT_POWER_STOCK

// Shaker parameters (simulating engine vibrations)
const uint8_t shakerStart = 230; // Shaker power while engine start (max. 255, about 100)
const uint8_t shakerIdle = 150; // Shaker power while idling (max. 255, about 49)
const uint8_t shakerFullThrottle = 195; // Shaker power while full throttle (max. 255, about 40)
const uint8_t shakerStop = 230; // Shaker power while engine stop (max. 255, about 60)
#endif

6_Lights.h

Para el apartado de luces, tenemos 6_Lights.h. Toda la configuración va referida a Neopixel. Tiene efectos predefinidos como "El coche Fantástico" o luces de emergencia. Como no voy a usar Neopixel, dejo la linea comentada:


#define NEOPIXEL_ENABLED     // GPIO0 used for WS2812 Neopixel control, if defined! Always enabled, unless you have issues with the library!

7_Servos.h

Los servos también tienen su apartado en "7_Servos.h" Las configuraciones disponibles son:

  • SERVOS DEFAULT
  • SERVOS LANDY_MN_MODEL
  • SERVOS LANDY_DOUBLEEAGLE
  • SERVOS C34
  • SERVOS URAL
  • SERVOS RGT EX86100
  • SERVOS ACTROS
  • SERVOS KING HAULER
  • SERVOS RACING TRUCK
  • SERVOS MECCANO DUMPER
  • SERVOS OPEN RC TRACTOR
Aquí también usaremos la que viene por defecto

#define SERVOS_DEFAULT //------- Select (remove //) one of the remote configurations below

8_Sound.h

En el apartado de sonido, el archivo "8_Sound.h" lo vamos a utilizar para activar o no los intermitentes y sirenas así como ajunstar el volumen que necesitemos.


// #define NO_SIREN // siren sound is not played, if defined
// #define NO_INDICATOR_SOUND // If you don't want the indicator "tick - tack" sound

// Volume adjustment
// const  uint8_t numberOfVolumeSteps = 3; // The mumber of volume steps below
// const uint8_t masterVolumePercentage[] = {100, 66, 44}; // loud, medium, silent (more than 100% may cause distortions)

const uint8_t numberOfVolumeSteps = 4;                     // The mumber of volume steps below
const uint8_t masterVolumePercentage[] = {100, 66, 44, 0}; // loud, medium, silent, no sound (more than 100% may cause distortions)

// Crawler mode
const uint8_t masterVolumeCrawlerThreshold = 44; // If master volume is <= this threshold, crawler mode (without virtual inertia) is active

En este apartado he comnetado #define NO_INDICATORS_SOUND Para activar el sonido de los intermitentes.

9_Dashboard.h

RC dashboard

Como comentabamos al principio, podemos poner una pequeña pantalla de 80*160 y de su configuración se encarga "9_Dashboard.h". Aquí podemos activar el pequeño panel y definir la velocidad máxima o el máximo de revoluciones que se van a mostrar.

El resto de parametros como la cantidad de combustible o, incluso, personalizar los colores del panel, el archivo que tenemos que modificar esta dentro de la carpeta src/Dashboard.h Esta opción viene desactivada por defecto.

10_Trailer.h 

Esta es la configuración para un remolque. Viene activado el primer trailer por defecto y salvo que se este usando, no afecta al funcionamiento normal.

Se pueden configurar hasta 3 trailers diferentes.

Esquema de conexiones entre ESP32 y los demas elementos


Otra de las modificaciones es la bocina, la que viene por defecto no suena muy bien, o por lo menos a mi no termina de convencerme. Esto cambios se pueden realizar en el archivo /vehicles/ManKat.h en el apartado:


// Choose the horn sound (uncomment the one you want) --------
volatile int hornVolumePercentage = 150; // Adjust the horn volume (usually = 100%)

Cambiando:



// #include "sounds/ManKatHorn.h" // MAN KAT V8

por

#include "sounds/westinghouseHorn.h" // American truck horn (the best)

Solo podemos seleccionar una bocina al mismo tiempo

A pesar de que parezca complicado, la verdad es que puedes complicarlo como quieras ya que es completamente modular. Podemos tener solo las luces o solo el sonido.

Otra de las particularidades es que incluye conexión para trailer mediante el chip wifi del mismo ESP. No tengo muy claro como funciona pero imagino que simplificará las conexiones entre entre "cabeza" y "remolque".

Por mi parte a pesar de tener remolque lo tengo con cables y solo tiene una salida para controlar la rampa los intermitentes y la luz de freno.

Instalación:

A la hora de subir el sketch al microcontrolador ESP32 y como he comentado anteriormente, no he tenido problemas a la hora de subir el programa tanto con Arduino IDE 1.8.19 ni con Arduino IDE 2.X.

Pero si tendremos que tener la versión de placa 2.0.17. Si tienes la versión más actual (que es lo lógico) tendremos que cambiar a la versión:

Estos son los parametros que seleccionado a la hora de subir el programa al ESP32

  • Placa: DOIT ESP32 DEVKIT V1
  • Upload Speed: 460800
  • Flash Frequency: 80MHz
  • Core debug level: "ninguno"
  • Erase all before sketch upload: "Enabled"

Cuando la carga del programa haya terminado, podemos comprobar si todo está correctamente en el monitor serial. Nos tiene que aparacer un mensaje como el siguiente:


**************************************************************************************************
TheDIYGuy999 RC engine sound & light controller for ESP32 software version 9.13.0
https://github.com/TheDIYGuy999/Rc_Engine_Sound_ESP32
Please read carefully: https://github.com/TheDIYGuy999/Rc_Engine_Sound_ESP32/blob/master/README.md
XTAL Frequency: 40 MHz, CPU Clock: 240 MHz, APB Bus Clock: 80000000 Hz
Internal RAM size: 354360 Byte, Free: 329236 Byte
WiFi MAC address: F0:08:D1:D8:67:EC
Core 0 reset reason: 9: RTCWDT_SYS_RESET
Core 1 reset reason: 14: EXT_CPU_RESET
**************************************************************************************************

EEPROM initialized.
EEPROM read.
current eeprom_id: 5
change it for default value upload!


ESC calibration data: ----
ESC pulse span: 600 (Used to adjust the top speed: 500 = full ESC power, 1000 = half ESC power etc.)
ESC takeoff punch: 0 (Usually 0. Enlarge it up to about 150, if your motor is too weak around neutral.)
ESC reverse plus: 0 (Usually 0. Enlarge it up to about 220, if your reverse speed is too slow.)
ESC ramp time for crawler mode: 10 (about 10 - 15), less = more direct control = less virtual inertia)
**************************************************************************************************

ENABLE_WIRELESS option disabled, no WiFi configuration or ESP-Now Trailer!
-------------------------------------
Warning, BATTERY_PROTECTION disabled! ESC with low discharge protection required!
-------------------------------------
... PWM communication mode active.
-------------------------------------
Standard ESC mode configured. Connect crawler ESC to ESC header. RZ7886 motor driver not usable!
-------------------------------------
EXPONENTIAL_THROTTLE mode enabled

Transmitter channel offsets (calculated, if channelAutoZero[] = true):
 CH1: 0 µs
 CH3: 0 µs
 


Vídeo exlicando los archivos y la instalación en el ESP32:


 

 

Aquí os dejo los enlaces necesarios para el proyecto:

Repositorio oficial de RC Engine Sound ESP32 en GitHub

Puedes hacer las placas de una manera profesional desde PCBway.com

lunes, 26 de agosto de 2024

pyTermTk y ttkDesigner: Slider y Label

Hemos visto algunos ejemplos con TermTk y TTkDesigner, hoy traemos otro más, el Slider o control deslizante. Como ya hemos visto antes con Button y Label o con el widged List, el widget Slider tiene una uso muy similar.

Antes de aplicar el código vamos a crear una pantalla donde pondremos los siguientes elementos:

  • Un widget tipo Slider al que llamaremos "sl1"
  • Un widget tipo Label al que llamaremos "lb1"
  • TermTk Slider
    ttkDesigner Slider

Creamos el objeto slider "sl01" desde "getWidgetByName" llamado "sl1". El objeto slider usa el parametro sliderMoved.connect, esto llamará a la función slot llamada "values". Otro parametro que podemos usar es: "valueChanged.connect".


        # TTkSlider
        self.sl01 = self.getWidgetByName("sl1")

        # TTkSlider conectado con slot
        self.sl01.sliderMoved.connect(self.values)
        #self.sl01.valueChanged.connect(self.values)
        

Creamos el objeto Label "lb01" desde "getWidgetByName" con el nombre que le hayamos dado, en este caso "lb1". Sera donde se muestren los datos que nos de el widget slider.


        # TTkLabel
        self.lb01 = self.getWidgetByName("lb1")
        

La función "values" aunque no tenga parametro, recibe el valor del slider y podemos llamarlo con "self.sl01.value()".


    # Funcion Slot
    @pyTTkSlot()
    def values(self):
        # Valor de la posicion de TTkSlider en TTkLabel
        self.lb01.setText(str(self.sl01.value()))

Con el programa completo tenemos una mejor visión de como interactuan las funciones slot con el objeto Slider:


from TermTk import TTkUtil, TTkUiLoader, TTk, TTkContainer, pyTTkSlot


class Slider(TTkContainer):
    def __init__(self):
        # Datos generados usando ttkDesigner
        TTkUiLoader.loadDict(TTkUtil.base64_deflate_2_obj(
    "eJx1Ultv0zAUTpa0adfdGUzbJNQn1L2MjWcugoqL8MoKjbYHxIOXWLWFY1dxMjakIR774Efz9/gtHNvZBSESWT4X2+f7vnN+xr9+R4H7rsxAJ+ekVEwKo1tP9g/2D4yO" +
    "qpoZm2plHCtl9FKafh1KUWEmSGl0e4ZLXCh3JP6AC2L04ghyp0zk8pvRnbFUrLJPfjEDtIBCouMJ+06c+xjtEN0dMdE/ZXlFDQrgMnjvCJvSyrrdEb5oku+DILR5CDR5" +
    "H0lOmGJnnJi5Tl4LDFZuzVRKnrKZ0YHRyRjnORNTVzTwP9HtI3wp68roLlBq7Fq3ubeAEG3RHZpcAYm3RBakKi+b64C7Mkp3Msp4XhLLzR13LzXM7cUB7f4V6tn6PRQT" +
    "umytbbRC6CoK6BqsdceGbvjt3pxuzul9+oBu2ZMRCu1P6Dbd0e1XssxB+7lupawC4nplwhlE+o/6R/iMcGiSr9h/w/FUWZ2CTk1371CiDz2TLbQITGgfingGHaeFfeSa" +
    "QMTPDhvotn0e+qa1VlEI0MP/QddxSi5A3hDGqDWUXMK4xLufDwpQ/CVnU1EQAU2Oax2VoA50O8okt3sCvpphYVCoE4g1dn0rsid8i1Hxwxt5FxqMIUquMUZ0HfYN9OMO" +
    "ut5xyQAAdsOJlnTrBPOauPIwg6yoC2/jC29nujeBGeKkP6nIzELrjvH0xqvpU+jiM1jPodQLCJB/Q7iudS+TQpDMllXG667bigjHZ6g7JckIO/dOW4FMGBq6phzhkTwn" +
    "+YCJag+kVFyCusuKVA65DzuNjumQjmF9pJ/ohKaAZP8PrFNMwg=="), self)

        # TTkSlider
        self.sl01 = self.getWidgetByName("sl1")

        # TTkSlider conectado con slot
        self.sl01.sliderMoved.connect(self.values)

        # TTkLabel
        self.lb01 = self.getWidgetByName("lb1")

    # Funcion Slot
    @pyTTkSlot()
    def values(self):
        # Valor de la posicion de TTkSlider en TTkLabel
        self.lb01.setText(str(self.sl01.value()))


root=TTk()
root.layout().addWidget(Slider())
root.mainloop()

Slider funcionando con TermTk

Aquí tienes algunos ejemplos sobre Termtk y TTkDesigner:

Programa de ejemplo de este articulo en GitHub

pyTermTk y ttkDesigner: List, ListSelect y Lablel

pyTermTk y ttkDesigner: Button y Lablel

Librería PyTermTk crea interfaces en terminal

Librería PyTermTk en GitHub

Sígueme para más artículos sobre TermTk y otros temas de programación, Saludos!!!