import machine
from machine import Pin, ADC, PWM, I2C
from lcd_i2c import LCD
import time, math
import network
from umqtt.simple import MQTTClient
import ubinascii
# =========================
# Configuración WiFi
# =========================
SSID = "TuSSID"
PASS = "TuClave"
# =========================
# MQTT (HiveMQ Cloud / Adafruit IO)
# =========================
MQTT_BROKER = "broker.hivemq.com" # o el broker que uses
MQTT_PORT = 8883 # puerto TLS
MQTT_USER = "" # si aplica
MQTT_PASS = "" # si aplica
CLIENT_ID = ubinascii.hexlify(machine.unique_id())
TOPIC_PUBLISH = b"freecooling/status"
TOPIC_SUBSCRIBE = b"freecooling/cmd"
USER_PASSWORD = "1234" # clave de usuario para cambiar persiana
# =========================
# Hardware
# =========================
adc_int = ADC(Pin(34))
adc_int.atten(ADC.ATTN_11DB)
adc_int.width(ADC.WIDTH_12BIT)
adc_ext = ADC(Pin(35))
adc_ext.atten(ADC.ATTN_11DB)
adc_ext.width(ADC.WIDTH_12BIT)
servo = PWM(Pin(23), freq=50)
sda = Pin(21, Pin.OUT)
scl = Pin(22, Pin.OUT)
i2c = I2C(0, sda=sda, scl=scl, freq=400000)
lcd_addr = i2c.scan()[0]
lcd = LCD(addr=lcd_addr, cols=16, rows=2, i2c=i2c)
lcd.begin()
boton_modo = Pin(18, Pin.IN, Pin.PULL_UP)
# =========================
# Parámetros
# =========================
T_MIN = 22.0
T_MAX = 24.0
HYST = 0.5
BETA = 3950
R0 = 10000
T0 = 298.15
DUTY_MIN = int(65535 * 0.025)
DUTY_MAX = int(65535 * 0.128)
modo_manual = False
manual_pos = 0
apertura = 0
# =========================
# Funciones
# =========================
def ntc_to_temp(adc_val, r_series=10000):
if adc_val <= 0:
return 0
v = adc_val / 4095.0 * 3.3
r_ntc = r_series * v / (3.3 - v)
t = 1.0 / (1.0/T0 + (1.0/BETA) * math.log(r_ntc/R0)) - 273.15
return t
def set_servo(percent):
duty = DUTY_MIN + int((DUTY_MAX - DUTY_MIN) * percent / 100)
servo.duty_u16(duty)
def mostrar_en_lcd(linea1, linea2=""):
lcd.clear()
lcd.print(linea1[:16])
lcd.set_cursor(0,1)
lcd.print(linea2[:16])
def decidir_apertura(t_int, t_ext, apertura_prev):
apertura = apertura_prev
if t_int < T_MIN - HYST:
apertura = 0
elif t_int > T_MAX + HYST and t_ext >= t_int:
apertura = 0
elif t_int > T_MIN:
delta = t_int - t_ext
if delta > 0:
apertura = int((delta / 8.0) * 100)
apertura = max(0, min(apertura, 100))
apertura = round(apertura / 10) * 10
return apertura
# =========================
# Callback MQTT
# =========================
def mqtt_callback(topic, msg):
global apertura
try:
payload = msg.decode()
# Espera formato "clave:pos" ej "1234:50"
if ":" in payload:
clave, pos = payload.split(":")
if clave == USER_PASSWORD:
pos = int(pos)
pos = max(0, min(pos, 100))
apertura = pos
set_servo(apertura)
except Exception as e:
print("Error MQTT:", e)
# =========================
# Conexión WiFi
# =========================
wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(SSID, PASS)
while not wifi.isconnected():
time.sleep(0.5)
ip = wifi.ifconfig()[0]
print("Conectado en", ip)
mostrar_en_lcd("WiFi Conectado", ip)
# =========================
# Cliente MQTT
# =========================
client = MQTTClient(CLIENT_ID, MQTT_BROKER, port=MQTT_PORT, user=MQTT_USER, password=MQTT_PASS, ssl=True)
client.set_callback(mqtt_callback)
client.connect()
client.subscribe(TOPIC_SUBSCRIBE)
# =========================
# Bucle principal
# =========================
set_servo(apertura)
while True:
t_int = ntc_to_temp(adc_int.read())
t_ext = ntc_to_temp(adc_ext.read())
# Botón modo manual
if boton_modo.value() == 0:
time.sleep_ms(300)
if boton_modo.value() == 0:
if not modo_manual:
modo_manual = True
manual_pos = 0
else:
manual_pos += 50
if manual_pos > 100:
modo_manual = False
time.sleep(1)
if modo_manual:
apertura = manual_pos
set_servo(apertura)
linea1 = "MODO MANUAL"
linea2 = "Persiana:{}%".format(apertura)
else:
nueva_apertura = decidir_apertura(t_int, t_ext, apertura)
if nueva_apertura != apertura:
apertura = nueva_apertura
set_servo(apertura)
linea1 = "Int:{:.1f} Ext:{:.1f}".format(t_int, t_ext)
linea2 = "Auto:{}%".format(apertura)
mostrar_en_lcd(linea1, linea2)
# Publicar estado en MQTT
msg = "Int:{:.1f},Ext:{:.1f},Persiana:{}".format(t_int, t_ext, apertura)
try:
client.publish(TOPIC_PUBLISH, msg)
client.check_msg() # revisa si hay mensaje entrante
except:
pass
time.sleep(2)