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!!!

lunes, 5 de agosto de 2024

pyTermTk y ttkDesigner: Mostrar listas

Anteriormente ya hemos visto como crear boton y un label con pyTermTk y TTkDesigner. El siguiente widget que he probado es el widget list. Con las listas podemos mostrar la información de una manera estructurada, generalmente en vertical, y nos facilita su lectura. También nos permite seleccionar elementos de esta lista y de esta manera podemos interactuar con los datos que muestra.

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

  • Un widget tipo List al que llamaremos "ls1"
  • Un widget tipo Label al que llamaremos "lb1"

Lo primero que necesitamos es una lista con los elementos que queremos mostrar, en este caso frutas.
Lo siguiente sera cargar el widget de tipo list que hemos creado. Para esto vamos a crear una funcion que cargara los datos en el widget


    # Imprime los elemtos de la lista en TTkList
    def Print_Listbox(self, items):
        for item in items:
            self.ls01.addItem(item)

Ahora con la lista de elementos ya cargados en el widget list, la selección de cada uno de ellos, o de varios, ya que permite selección múltiple. Esto lo haremos mediante un slot, conectando el evento textClicked con su respectiva funcion. Para este caso ka función se llamará Current_Selection()


       # TTkList conecta con el slot al hacer click en el texto
        self.ls01.textClicked.connect(lambda : self.Current_Selection())
        
            @pyTTkSlot(str)
    def Current_Selection(self):
        data = ""
        for i in self.ls01.selectedLabels():
            data += i
            self.lb01.setText(data)
            

Ahora ya solo nos queda mostrar el resultado de la selección en el widget label. Este es el paso mas sencillo ya que solo tenemos que usar el atributo setText.


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

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

        # Genera una lista con fruta
        self.lista = ["Manzana", "Platano", "Melocotón", "Pera" ]
        
        # Data generated using ttkDesigner
        TTkUiLoader.loadDict(TTkUtil.base64_deflate_2_obj(
    "eJx1Uktv1DAQTkh2s0uhvEoPwKHHFY/Vwj9ALWrVsKhSI3pAHLzJaG3h2KvYoS1SJY57mOP0/zJOwuOCLcvfPP19k/xMby/vRN26oRlm36FxyhrC0bv5Yr4gTHyrKIRG" +
    "pRbOEd4rim+H1nihDDSE441oRO26lPSTqIHw7pJjF8pU9pJwcmad8qHlV5rlaZ4ApufqB3Tmi3wXcLpU5uBCVV5SHnExWyeg1tIHc7oUV0PwNIriEGfHEO892Wfl1EoD" +
    "bTH7YASjKsDCWl2oDWFEmJ2JqlJm3T0a9Rtw/FFc29YTTlnSgFsc6x6xIDmSz2R2wyKOwdbgm+uhnHl7cjgppdJVA522Lp+f5VbKeQp1MznFRLu3JHd68SnI+wHt5RnI" +
    "B3kkH/J51MmQj/vryVbubeVTuY+756ChDKM7WNoKKI9xcmSOBiNqMWl4wDyjpLQ63BnbbiNMyMzYN+C2ZzbpRK5A/0Nt9Zda/Ida/Jta/F9qaQFXLHEfR4dWW/4L0udf" +
    "FjUP8r1Wa1OD4W+XtvIlt3nF5zW3esNEoG1xp7TG9LIcj62d/wKW+9J7"), self)

        # TTkList
        self.ls01 = self.getWidgetByName("ls1")
        
        # TTkList conecta con el slot al hacer click en el texto
        self.ls01.textClicked.connect(lambda : self.Current_Selection())

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

        # LLama a la función donde se cargarán los elementos de la lista
        self.Print_Listbox(self.lista)

    @pyTTkSlot(str)
    def Current_Selection(self):
        data = ""
        for i in self.ls01.selectedLabels():
            data += i
            self.lb01.setText(data)


    # Imprime los elemtos de la lista en TTkList
    def Print_Listbox(self, items):
        for item in items:
            self.ls01.addItem(item)


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



TermTk ofrece una forma intuitiva y eficiente de crear interfaces de usuario en Python. Al dominar la creación de listas, podrás construir aplicaciones más robustas y personalizadas.

Aquí tienes algunos ejemplos sobre Termtk y TTkDesigner:

Programa de ejemplo de este articulo en GitHub

pyTermTk y ttkDesigner: Button y Lablel

Librería PyTermTk crea interfaces en terminal

Sigueme para más articulos sobre TermTk y otros temas de programación, Saludos!!!

jueves, 1 de agosto de 2024

pyTermTk y ttkDesigner: Button y Lablel

No lo vamos a negar, pyTermTk me ha encantado. Siempre hemos tenido ganas de poder hacer el terminal un poquito más amigable, pero no siempre es una tarea sencilla.

Ya hemos visto como instalar pyTermTk y ttkDesigner. Ahora veremos como funciona, accedemos al programa desde la terminal con el comando:


$ttkDesigner

A partir de aquí, tenemos la pantalla dividida en secciones:

  • Lista de elementos:

    Tenemos muchos de donde escoger, Layouts, Containers, Buttons, etc... Solo tenemos que mantener pulsado el elemento y arrastrar hacia el marco principal

  • Árbol de inspección:

    Todos los elementos que adjuntemos al proyectos ordenados por jerarquia

  • Propiedades del widget:

    Son las características de cada elemento, tales como alto, ancho, posición, etc...

  • Editor de Signal y slots:

    Se emite una señal cuando se produce un evento en particular.
    Un slot es una función que se llama en respuesta a una señal particular.
    TermTk TTkWidgets tiene muchas signals y slots predefinidos, pero es posible crear una subclase a cualquier TTkWidgets y añadir nuestras propias signals/slots. En este ejemplo solo usaremos los slots.



  • Logs:

    Registro de lo que pasa en el programa en tiempo de ejecución

  • File:

    Estas son las opciones clásicas, abrir, guardar, importar, exportar. Los archivos se guardan en formato JSON.

  • Edit:

    También cuenta con opciones como deshacer rehacer, Otra opción es KeypressView que nos abrirá una pequeña ventana que nos muestra que tecla hemos pulsado.

  • Preview:

    Muestra una vista previa de la interfaz que estamos creando:

  • Quick export:

    Con quick export, exportaremos el proyecto de una manera sencilla y tenemos varias ociones para hacerlo, Normal y Compressed En la parte superior izquierda nos aprece un icono de disquete . Al pulsar sobre el nos aparece una pantalla donde podemos guardar la interfaz en formato estandar .py. Luego podemos modificarlo a nuestro antojo con cualquier editor de texto:

    Normal: Al pulsar sobre la opción Normal nos aparece el código de una manera legible podemos ver como esta estructurado.

    Compressed: Muestra el mismo código pero de una manera comprimida. Con esta opción no podemos leer nada del código. La parte positiva es que es más fácil a la hora de copiar y pegar ya que siempre usa la misma estructura a diferencia de JSON

  • Vista en contraste:

    La vista de los elementos cambia a bloques para para poder ver mejor su posición relativa en la pantalla. Es muy util para ver donde están situados exactamente los bloques y dejarlos justo donde queramos que estén



from TermTk import TTkUtil, TTkUiLoader, TTk

# Data generated using ttkDesigner
widget = TTkUiLoader.loadDict(TTkUtil.base64_deflate_2_obj(
    "eJx1kt9r1EAQxxOTXC620qq10hb0xJd7Og7xoa34YA9/4HpSMFhQfNgmy83S3O6RZLUVCj5eYR7X/9fZJFcFaZawMzu7s5+Z7/4Kf+8HXvNd2iHG30VZSa0sRs9G49HY" +
    "YlAbaV0oygpeVRbX0/RsolXNpRKlxd6Cl3xeNVvCj3wuLN6eUuxEqlz/sNg/1pWsXcpvdshCdktg+En+FI37hW0KTKZSDU5kXoNlHh0m752QM6idm0z5eRd873m+i9NC" +
    "F29X4s+ykqeFsEuMXytOVu7MVOsilQuLnsX4mOe5VLPmUq8dAnsf+IU2tcWESupsg72itaggiGAX4ksq4q3Qc1GXF91x4q5thf0MZJGXwtXWbG8ydZW7g0NI4CmsuVt3" +
    "WCDgjrOesETABvNgk/67TQ1wr53uL2FrCQ9gGx66nQHz3RCwA7vYO9JlTh1fYpTKmsqFbVxv7xq8Kfiscu3w+gb2/iGHRy3wYxYRMAwo61/QI1PXpMsKdNyCRiRRC7rl" +
    "kDdYn0CDm0AxTMU5tbC3SjZcYjIBkZ05IewVxo1DklwZDErqC4kaZLpwc0x+teDKMh9jWuts0yL2G1VORXFN+HJF6F8T+o4wIkL/xlY+x6hNg9FEF5rebLj3dTwn2V8V" +
    "cqbmQtFLCw3skxwH9B9SrhdEIf5f4sbgWqaVEpl70hXpbkZ/AFN2DDk="))

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

Ya tenemos el código básico, ahora vamos a transformarlo en una clase. Paral la clase tenemos que pasarle el tipo del elemento principal. En TTkDesigner viene indicado en el árbol de inspección:



from TermTk import TTkUtil, TTkUiLoader, TTk, TTkContainer

class Interfaz_Label(TTkContainer):
	def __init__(self):
    		TTkUiLoader.loadDict(TTkUtil.base64_deflate_2_obj(
    "eJx1kt9r1EAQxxOTXC620qq10hb0xJd7Og7xoa34YA9/4HpSMFhQfNgmy83S3O6RZLUVCj5eYR7X/9fZJFcFaZawMzu7s5+Z7/4Kf+8HXvNd2iHG30VZSa0sRs9G49HY" +
    "YlAbaV0oygpeVRbX0/RsolXNpRKlxd6Cl3xeNVvCj3wuLN6eUuxEqlz/sNg/1pWsXcpvdshCdktg+En+FI37hW0KTKZSDU5kXoNlHh0m752QM6idm0z5eRd873m+i9NC" +
    "F29X4s+ykqeFsEuMXytOVu7MVOsilQuLnsX4mOe5VLPmUq8dAnsf+IU2tcWESupsg72itaggiGAX4ksq4q3Qc1GXF91x4q5thf0MZJGXwtXWbG8ydZW7g0NI4CmsuVt3" +
    "WCDgjrOesETABvNgk/67TQ1wr53uL2FrCQ9gGx66nQHz3RCwA7vYO9JlTh1fYpTKmsqFbVxv7xq8Kfiscu3w+gb2/iGHRy3wYxYRMAwo61/QI1PXpMsKdNyCRiRRC7rl" +
    "kDdYn0CDm0AxTMU5tbC3SjZcYjIBkZ05IewVxo1DklwZDErqC4kaZLpwc0x+teDKMh9jWuts0yL2G1VORXFN+HJF6F8T+o4wIkL/xlY+x6hNg9FEF5rebLj3dTwn2V8V" +
    "cqbmQtFLCw3skxwH9B9SrhdEIf5f4sbgWqaVEpl70hXpbkZ/AFN2DDk="), self)

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



Ahora que ya tenemos la interfaz diseñada vamos a crear los objetos para cada elemento de la pantalla y de esta manera poder interactuar con ellos.
Aquí es donde entran en juego las signals y los slots. Para usar los slots hay que importar la libreria pyTTkSlot. Será como los widgets se comuniquen entre ellos y con el programa principal.

En este ejemplo al llamar a mi_función antes tenemos que poner pyTTkSlot() indicando si el dato que pasamos es, por ejempolo, un String o un valor entero.



# Importamos pyTTkSlot
from TTkTerm import pyTTkSlot


# Para una función que recibe un string:

pyTTkSlot(str)
def mi_funcion(data):
	# Imprime el resultado
	print(data)
	

# Para una función que recibe un valor entero:

pyTTkSlot(int)
def mi_funcion(valor):
	# Imprime el resultado
	print(str(valor))


Ahora solo tenemos que aplicar esta función al botón bt1


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

class Interfaz_Label(TTkContainer):
	def __init__(self):
    		TTkUiLoader.loadDict(TTkUtil.base64_deflate_2_obj(
    "eJx1kktv1DAQgBOSbDa0UB6lqK0Ey217WS0cOPA40BUP1SyqREQlEAdvYu1YzdqrxIEWqRLHrTRH838ZJykFocaKPA97/M3jZ/jrIPCa78wOMf4mykpqZTF6MhqPxhYD" +
    "U0vrXFFW8KqyuJ6mxxOtDJdKlBZ7S17yRdUcCT/whbB4fUq+I6ly/d1i/1BX0riQX+2QheyawPCj/CEa9TO7KTCZSjU4krkByzy6TNo7IedgnJpM+UnnPPA83/nJ0Plb" +
    "S/xJVnJWCLvC+LXiJOVOTLUuUrm06FmMD3meSzVvHvXaJbD3np/q2lhMKKVOrrFXtBIlBBHsQHxGSbwVeiFMedpdJ25jK+xnIIu8FC635ngTqcvcXRxC8o9pzb2/zQIB" +
    "N5z0iCUCNpgHt+i/3WQDd9rt7go2V3APtuC+Oxkw3y0B27CDvX1d5lT7FUapNJQ4bOF6+8TgTcHnlSuM169h968c4EGL/pBFhA4DinqJvF8bQx26QA5m5nEHG1HDWthN" +
    "h73B+gQbXAWLYSpOqKC9i4B7K0wmILJj1xZ7jnGjUIPOawxKKgm1OMh04faY9GrJlWU+xmTr5LrF7Dc9monikrKYXVL6fyh9RxkRpX9lSZ9i1IbCaKILTVMc7n4ZL2gQ" +
    "XhVyrhZC0eyFNTyjtjyn/wXFekkk4n8Tr2tcy7RSInNDXtEk1KPfcuQTkg=="), self)
		
    	    self.window = self.getWidgetByName("TTkWindow")
    	    self.window.setTitle("Titulo ventana")
        
    	    self.bt1 = self.getWidgetByName("bt1")
            self.bt1.clicked.connect(lambda : self.cambiar_label("Hola!!!"))
            
    	    self.lb1 = self.getWidgetByName("lb1")
    
	pyTTkSlot(str)
	def cambiar_label(self, data):
		self.lb1.setText(data)
        self.bt1.setText("Pulsado!")


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



Y eso es todo en nuestra introducción a la librería pyTermTk y su TUI. Ya no hay excusas para crear interfaces de usuario en la terminal, ahora es más sencillo de lo que parece gracias a esta librería. Su arquitectura modular y la posibilidad de crear widgets personalizados te permiten adaptar la librería a tus necesidades más específicas. Os animo a experimentar con ella y recordar que hay una amplia documentación en GitHub.


Programa de ejemplo de este articulo en GitHub

Librería PyTermTk crea interfaces en terminal

Repositorio oficial pyTermTk

Diseñador de Interfaces de Texto TTkDesigner 

Saludos!!!