from machine import Pin, I2C,SPI            
                                            
import ssd1306                              
import time                                 
import random                               
import dht

import urequests    #Para realizar request a un server
import network      #Para conectar a internet

# ----- Internet -----
def send_data(data):
    # Define la URL de tu servidor en PythonAnywhere
    url = "https://shizadecaf.pythonanywhere.com/changedata"
    
    # Envía el valores a través del request GET
    params = "?param1="+data[0]+"&param2="+data[1]
    response = urequests.get(url + params)
    
    # Verifica la respuesta
    print("Respuesta del servidor:", response.text)
    response.close()
    return "ok"
    



# ----- SENSOR DE TEMPERATURA Y HUMEDAD -----
def init_temp():    
    d = dht.DHT22(Pin(23))      #Definimos un objeto DHT para recibir los datos del sensor, conectado al pin 23
    return d

def get_temp(d):
    d.measure()                 #ejecutamos esta función para obtener los datos del sensor y posteriormente retornarlos
    return str(d.temperature()) , str(d.humidity())     #Retornamos tupla de temperatura y humedad, en formato string

# ----- LEDS -----
def init_leds(led_pins):
    leds=[Pin(pin,Pin.OUT)for pin in led_pins]
    return leds

def random_led(leds,timerr):
    for i in range(0,32,1):                     
        random_led_index= random.randint(0, len(leds)-1)    #Escoge un led al azar dentro del rango de pines definidos
        leds[random_led_index].off()                            #dicho led es encendido (los leds se encienden con off)
        time.sleep(timerr)                                         #se hace un retardo en segundos del valor time
        leds[random_led_index].on()                             #el led se apaga

def serial_led(leds,timerr):
    for led in leds:
        led.off()
        time.sleep(timerr)
        led.on()

    
# ----- PANTALLA OLED -----
def init_oled():
    i2c = I2C(1, scl=Pin(22), sda=Pin(21))          #Definimos el protocolo I2C como un objeto
    oled_width = 128        #Definimos la resolución de la pantalla oled, ancho y alto
    oled_height = 64        #La pantalla que tenemos disponible es de 128x64 pixeles
    oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)        #definimos la pantalla oled como un objeto de la clase ssd1306, recibe como parámetros las dimensiones de la pantalla y el protocólo i2c que previamente definimos.
    return oled

def clear_screen(oled):
    oled.fill(0)
    oled.show()             # Se actualiza el contenido de la pantalla oled con los cambios hechos en el buffer

def oled_text(oled,tex,x,y):
    oled.text(tex, x, y)    # Imprimimos un texto en las coordenadas x y
    oled.show()

def oled_text_multiline(oled,lines,x,y,salto):    
    for line in lines:              #recorremos todos los elemenos de la lista de lineas
        oled.text(line, x, y)       # e inmediatamente imprimimos dicha linea en las coordenadas definidas.
        y += salto            #tras cada linea impresa, hacemos un salto de linea (coordenada y) respecto al valor de distancia que definimos.
    oled.show()         #refrescamos la pantalla

# ----- SEVEN-SEGMENTS -----
def ini_max():              #Este método inicializa y controla un dispositivo conectado por el bus SPI, en este caso un display de 7 segmentos controlado por un decodificador MAX7219.
    cs = Pin(15,Pin.OUT)            #cs corresponde al chip select, conectado al pin GPIO 15 como salida digital para el dispositivo SPI.
    """
    CS es el pin que permite seleccionar al dispositivo con el que el ESP32 va a comunicarse en el bus SPI. 
    Al poner CS en bajo (off), el dispositivo sabe que debe recibir comandos.
    """
    hspi = SPI(1, 100000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))      #Se inicializa el periférico SPI usando el segundo bus del mismo (1), se define 100,000 como la frecuencia en hz de transmisión de datos
                            #el pin GPIO 14 será usado para la señal SCK (reloj del bus SPI), el 13 será usado para la señal MOSI (Master Out Slave In), y el pin 12 para MISO (Master In Slave Out) 
                            #aunque hemos definido el protocolo MISO, por esta ocasión no lo estaremos usando, ya que sólo mandamos señal desde la placa al display, no viceversa.
    
    cs.off()            #Baja la señal del pin CS, activando el dispositivo para que esté listo para recibir comandos.
    hspi.write(b'\x09\xff')  ## 1er decodificador
    """
    Se envía el comando 0x09 al dispositivo SPI para configurar el modo de decodificación de los dígitos. 
    El valor 0xFF habilita la decodificación BCD (Binary-Coded Decimal) para todos los dígitos.
    Este comando permite mostrar números en los displays de 7 segmentos usando un formato de decodificación (números del 0 al 9).
    """
    cs.on()             #sube la señal del pin CS, indicando al dispositivo que deje de escuchar los comandos. 

    cs.off()
    hspi.write(b'\x0a\x01') ## brillo
    """
        Se envía el comando 0x0A para ajustar el brillo del display.
        El valor 0x01 indica un brillo muy bajo, ya que el valor máximo sería 0x0F.
    """
    cs.on()
    cs.off()

    hspi.write(b'\x0b\x07') ## los 7 segmentos
    """
        Se envía el comando 0x0B para definir el número de dígitos activos en el display de 7 segmentos.
        El valor 0x07 indica que se están usando 8 dígitos (en un display típico de 8 dígitos, numerados de 0 a 7).
    """
    cs.on()
    cs.off()

    hspi.write(b'\x0c\x01')  ## modo normal
    """
        Se envía el comando 0x0C para poner el dispositivo en modo normal (es decir, encender la pantalla).
        El valor 0x01 activa el modo normal; el valor 0x00 sería para modo de apagado.
    """    
    cs.on()
    cs.off()

    hspi.write(b'\x0f\x00')  ## test
    """
        Se envía el comando 0x0F para desactivar el modo de prueba (test mode).
        El valor 0x00 desactiva el modo de prueba; en cambio, 0x01 activaría el modo de prueba, 
        donde se encienden todos los segmentos del display para verificar que funcionan.
    """
    cs.on()
    cs.off()

    #A continuación, se configuran los dígitos de cada display
    hspi.write(b'\x01\x08')     #Se muestra el número 8 en el display 01, 08 en hexadecimal, que también es 8 en decimal.
    cs.on()
    cs.off()
    hspi.write(b'\x02\x07')     #Se muestra el número 7 en el display 02
    cs.on()
    cs.off()
    hspi.write(b'\x03\x06')     #Se muestra el número 6 en el display 03
    cs.on()
    cs.off()
    hspi.write(b'\x04\x05')     #Se muestra el número 5 en el display 04
    cs.on()
    cs.off()
    hspi.write(b'\x05\x04')     #Se muestra el número 4 en el display 05
    cs.on()
    cs.off()
    hspi.write(b'\x06\x03')     #Se muestra el número 3 en el display 06
    cs.on()
    cs.off()
    hspi.write(b'\x07\x02')     #Se muestra el número 2 en el display 07
    cs.on()
    cs.off()
    hspi.write(b'\x08\x01')     #Se muestra el número 1 en el display 08
    cs.on()
    time.sleep(1)               #Pausamos la ejecución un segundo
    cs.off()
    hspi.write(b'\x0f\x01')     # regresamos el display al modo test, encendiendo todos los segmentos.
    cs.on()                     #Dejamos de escuchar los comandos, finalizando la ejecución.
   

def ini_max_tecnm():
    # Diccionario que mapea caracteres a segmentos de 7 segmentos (esto es solo un ejemplo)
    char_map = {	#Todo el abecedario y digitos numéricos
        'C': 0x4E,  
        'E': 0x4F,  
        'S': 0x5B,  
        'A': 0x77,  
        'R': 0x46,  
        '-': 0x01, 
        '2': 0x6D,  
        '4': 0x33,  
    }

    cs = Pin(15, Pin.OUT)
    hspi = SPI(1, 100000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))

    # Configuraciones iniciales del display
    cs.off()
    hspi.write(b'\x09\x00')  # 0x00 corresponde al modo de NO decodificación (caracteres personalizados)
    cs.on()

    cs.off()
    hspi.write(b'\x0a\x01')  # Brillo bajo
    cs.on()

    cs.off()
    hspi.write(b'\x0b\x07')  # Los 8 dígitos del display
    cs.on()

    cs.off()
    hspi.write(b'\x0c\x01')  # Modo normal (encendido)
    cs.on()

    cs.off()
    hspi.write(b'\x0f\x00')  # Desactivar modo test
    cs.on()

    # Mostrar los caracteres de "TECNM-24"
    text = reversed("CESAR-24")  # Volteamos el texto para que se imprima en orden inverso.

    for i, char in enumerate(text):
        cs.off()
        # Mapea el carácter a su representación en el display de 7 segmentos
        char_code = char_map.get(char.upper(), 0x00)  # Si el carácter no está en el mapa, muestra espacios en blanco
        hspi.write(bytearray([i+1, char_code]))  # Envía al display el carácter en la posición correcta
        cs.on()

    # Test de encendido de segmentos por 1 segundo
    time.sleep(5)
    cs.off()
    hspi.write(b'\x0f\x01')  # Modo test
    cs.on()


# ----------- EJECUCION ----------- 

buzzer=Pin(2,Pin.OUT)   #Establece en que pin está conectada la bocina. En este caso, en el pin 2. El OUT significa quem es un dispositivo de salida

buzzer.on()             #Enciende la bocina, generando un pitido durante 
time.sleep(0.2)           # medio segundo
buzzer.off()            # y pasado ese segundo, deja de sonar.
time.sleep(0.2)

# ----- inicializamos -----
d = init_temp()
oled = init_oled()
clear_screen(oled)
led_pins = [16,17,21,22,23,25,26,27]
leds = init_leds(led_pins)
# -- WIFI --

ssid = "Wokwi-GUEST"
password = ""
station = network.WLAN(network.STA_IF)
station.active(True)

try:
    oled_text(oled,"Conectando ...",5,5)
    station.connect(ssid, password)
    
    # Esperar a que se conecte
    for i in range(10):  # Intenta por 10 ciclos
        if station.isconnected():
            break
        time.sleep(1)  # Esperar 1 segundo entre intentos

    if not station.isconnected():
        raise RuntimeError("No se pudo conectar a la red WiFi")
    
    print("Conexión exitosa!", station.ifconfig())

except Exception as e:
    print("Error al conectarse a WiFi:", e)
    oled_text(oled,"No fue posible conectarse a internet.",5,5)
    time.sleep(2)
    clear_screen(oled)
    # Aquí puedes agregar lógica adicional si falla la conexión
    # como reiniciar el ESP32, etc.
    # machine.reset()  # Reinicia el ESP32
    pass

# ----- CICLO -----
while True:             #Este ciclo se repite de manera indefinida, en este caso, mientras el ESP32 esté encendido.
    random_led(leds,0.5)
    oled_text(oled,"Hello new world",5,5) 
    time.sleep(1)                       #pausamos la ejecución por un segundo
    oled_text(oled,"All the boys and girls",10,15) 
    serial_led(leds,0.1)
    time.sleep(1)
    oled_text(oled,"I got some true stories to tell ",15,25) 
    
    time.sleep(2)
    #ini_max()       #Iniciamos el display 7 segmentos y mostramos números del 1 al 8
        
    #ini_max_tecnm()     #Imprimimos "TECNM-24" en el display. (hace falta testeo)
    clear_screen(oled)
    time.sleep(1)
        
    temp = get_temp(d)
    lines = [
                "Temp: " +temp[0]+" C",
                "Humd: " +temp[1]+"%"
            ]   #definimos una lista para imprimir en la función oled multilinea, ajustamos la impresión de temperatura y humedad.
      

    oled_text_multiline(oled,lines,0,30,10)     #Imprimimos en contenido de la lista con la función multiline.
    time.sleep(1)
    tst = send_data(temp)
    
    if station.isconnected():
        if tst is "error":
            clear_screen(oled)
            oled_text(oled,"No se pudo enviar datos al server.",0,25) 
            time.sleep(2)

    clear_screen(oled)
    #La ejecución se repite de forma indefinida.




$abcdeabcde151015202530354045505560fghijfghij