Project

General

Profile

Servidor » History » Version 2

cristobal hernandez, 12/18/2025 01:53 AM

1 2 cristobal hernandez
---
2 2 cristobal hernandez
3 2 cristobal hernandez
| [[Codificacion|⌂]] | [[Servidor]] | [[Interfaz]] |
4 2 cristobal hernandez
5 2 cristobal hernandez
---
6 2 cristobal hernandez
7 1 cristobal hernandez
h1. Servidor
8 1 cristobal hernandez
9 1 cristobal hernandez
<pre><code class="python">
10 1 cristobal hernandez
import time
11 1 cristobal hernandez
import glob
12 1 cristobal hernandez
import json
13 1 cristobal hernandez
import requests
14 1 cristobal hernandez
import os
15 1 cristobal hernandez
import random
16 1 cristobal hernandez
from datetime import datetime
17 1 cristobal hernandez
18 1 cristobal hernandez
import grovepi
19 1 cristobal hernandez
from grovepi import *
20 1 cristobal hernandez
from grove_rgb_lcd import *
21 1 cristobal hernandez
from gpiozero import AngularServo
22 1 cristobal hernandez
23 1 cristobal hernandez
import firebase_admin
24 1 cristobal hernandez
from firebase_admin import credentials, firestore, messaging
25 1 cristobal hernandez
26 1 cristobal hernandez
27 1 cristobal hernandez
# Archivos Locales
28 1 cristobal hernandez
FILE_ID = "savenemo_id.txt"
29 1 cristobal hernandez
FILE_CONFIG_LOCAL = "config.json"
30 1 cristobal hernandez
FILE_CREDENTIALS = "serviceAccountKey.json"
31 1 cristobal hernandez
32 1 cristobal hernandez
# Configuracion Sensor Temperatura
33 1 cristobal hernandez
SENSOR_TEMP_ID = "28-00000053d2ea"
34 1 cristobal hernandez
SENSOR_TEMP_PATH = f"/sys/bus/w1/devices/{SENSOR_TEMP_ID}/w1_slave"
35 1 cristobal hernandez
36 1 cristobal hernandez
# Puertos GrovePi
37 1 cristobal hernandez
PORT_ULTRASONIC  = 8   # D8
38 1 cristobal hernandez
PORT_RELAY_CAL   = 3   # D3 (Calefactor)
39 1 cristobal hernandez
PORT_PH          = 0   # A0
40 1 cristobal hernandez
PORT_SENSOR_LUZ  = 1   # A1
41 1 cristobal hernandez
SERVO_PIN = 18         # Pin GPIO (Servomotor)
42 1 cristobal hernandez
43 1 cristobal hernandez
# Variables Globales de Hardware
44 1 cristobal hernandez
alimentador_servo = None 
45 1 cristobal hernandez
46 1 cristobal hernandez
# Configuracion Inicial por Defecto
47 1 cristobal hernandez
DEFAULT_CONFIG = {
48 1 cristobal hernandez
    "nombre_pez": "Esperando App...",
49 1 cristobal hernandez
    "temp_min": 24.0, "temp_max": 28.0,
50 1 cristobal hernandez
    "ph_min": 6.5, "ph_max": 7.5,
51 1 cristobal hernandez
    "nivel_luz": 50, "nivel_agua": 80,
52 1 cristobal hernandez
    "horarios_comida": [],
53 1 cristobal hernandez
    "sistema_alimentacion_on": True 
54 1 cristobal hernandez
}
55 1 cristobal hernandez
56 1 cristobal hernandez
# Estado del Sistema
57 1 cristobal hernandez
config_actual = DEFAULT_CONFIG.copy()
58 1 cristobal hernandez
nemo_id = ""
59 1 cristobal hernandez
db = None
60 1 cristobal hernandez
firebase_activo = False
61 1 cristobal hernandez
ultimo_minuto_alimentacion = "" 
62 1 cristobal hernandez
ya_se_envio_notificacion = False
63 1 cristobal hernandez
64 1 cristobal hernandez
# Funciones BD
65 1 cristobal hernandez
66 1 cristobal hernandez
def obtener_id_unico():
67 1 cristobal hernandez
    """Lee o genera un ID unico para identificar esta pecera."""
68 1 cristobal hernandez
    if os.path.exists(FILE_ID):
69 1 cristobal hernandez
        with open(FILE_ID, 'r') as f:
70 1 cristobal hernandez
            return f.read().strip()
71 1 cristobal hernandez
    else:
72 1 cristobal hernandez
        nuevo_id = f"NEMO-{random.randint(1000, 9999)}"
73 1 cristobal hernandez
        with open(FILE_ID, 'w') as f:
74 1 cristobal hernandez
            f.write(nuevo_id)
75 1 cristobal hernandez
        return nuevo_id
76 1 cristobal hernandez
77 1 cristobal hernandez
def verificar_internet():
78 1 cristobal hernandez
    try:
79 1 cristobal hernandez
        requests.get('https://www.google.com', timeout=3)
80 1 cristobal hernandez
        return True
81 1 cristobal hernandez
    except: return False
82 1 cristobal hernandez
83 1 cristobal hernandez
def esperar_internet():
84 1 cristobal hernandez
    print("Buscando conexion a internet...")
85 1 cristobal hernandez
    while not verificar_internet():
86 1 cristobal hernandez
        time.sleep(3)
87 1 cristobal hernandez
    print("Conexion establecida.")
88 1 cristobal hernandez
89 1 cristobal hernandez
def iniciar_firebase():
90 1 cristobal hernandez
    global db, firebase_activo
91 1 cristobal hernandez
    try:
92 1 cristobal hernandez
        if os.path.exists(FILE_CREDENTIALS):
93 1 cristobal hernandez
            cred = credentials.Certificate(FILE_CREDENTIALS)
94 1 cristobal hernandez
            if not firebase_admin._apps:
95 1 cristobal hernandez
                firebase_admin.initialize_app(cred)
96 1 cristobal hernandez
            db = firestore.client()
97 1 cristobal hernandez
            firebase_activo = True
98 1 cristobal hernandez
            print("Firebase conectado exitosamente.")
99 1 cristobal hernandez
        else:
100 1 cristobal hernandez
            print("Falta archivo serviceAccountKey.json")
101 1 cristobal hernandez
    except Exception as e:
102 1 cristobal hernandez
        print(f"Error Firebase: {e}")
103 1 cristobal hernandez
        firebase_activo = False
104 1 cristobal hernandez
105 1 cristobal hernandez
def sincronizar_configuracion():
106 1 cristobal hernandez
    global config_actual
107 1 cristobal hernandez
    if not firebase_activo: return
108 1 cristobal hernandez
    try:
109 1 cristobal hernandez
        doc = db.collection('acuarios').document(nemo_id).collection('data').document('config').get()
110 1 cristobal hernandez
        if doc.exists:
111 1 cristobal hernandez
            datos = doc.to_dict()
112 1 cristobal hernandez
            if datos != config_actual: # Solo actualizar si hay cambios
113 1 cristobal hernandez
                print("Configuracion actualizada desde la nube.")
114 1 cristobal hernandez
                config_actual.update(datos)
115 1 cristobal hernandez
                with open(FILE_CONFIG_LOCAL, 'w') as f: json.dump(config_actual, f)
116 1 cristobal hernandez
    except: pass
117 1 cristobal hernandez
118 1 cristobal hernandez
def subir_estado(temp, ph, luz, agua, alertas):
119 1 cristobal hernandez
    """Sube los datos de sensores a Firestore."""
120 1 cristobal hernandez
    if not firebase_activo: return
121 1 cristobal hernandez
    try:
122 1 cristobal hernandez
        db.collection('acuarios').document(nemo_id).collection('data').document('estado').set({
123 1 cristobal hernandez
            'temp_actual': temp, 'ph_actual': ph, 'luz_actual': luz, 'agua_nivel': agua,
124 1 cristobal hernandez
            'alertas': alertas, 'ultima_actualizacion': datetime.now().isoformat()
125 1 cristobal hernandez
        })
126 1 cristobal hernandez
    except: pass
127 1 cristobal hernandez
128 1 cristobal hernandez
def enviar_notificacion_push(titulo, mensaje):
129 1 cristobal hernandez
    if not firebase_activo: return
130 1 cristobal hernandez
    try:
131 1 cristobal hernandez
        tokens_ref = db.collection('acuarios').document(nemo_id).collection('data').document('tokens')
132 1 cristobal hernandez
        doc = tokens_ref.get()
133 1 cristobal hernandez
        token = doc.to_dict().get('token_celular') if doc.exists else None
134 1 cristobal hernandez
        
135 1 cristobal hernandez
        if token:
136 1 cristobal hernandez
            msg = messaging.Message(
137 1 cristobal hernandez
                notification=messaging.Notification(title=titulo, body=mensaje),
138 1 cristobal hernandez
                token=token,
139 1 cristobal hernandez
            )
140 1 cristobal hernandez
            messaging.send(msg)
141 1 cristobal hernandez
            print(f"Push enviado: {titulo}")
142 1 cristobal hernandez
    except Exception as e: 
143 1 cristobal hernandez
        print(f"Error push: {e}")
144 1 cristobal hernandez
145 1 cristobal hernandez
def guardar_historial_evento(titulo, mensaje):
146 1 cristobal hernandez
    if not firebase_activo: return
147 1 cristobal hernandez
    try:
148 1 cristobal hernandez
        db.collection('acuarios').document(nemo_id).collection('historial').add({
149 1 cristobal hernandez
            'titulo': titulo,
150 1 cristobal hernandez
            'mensaje': mensaje,
151 1 cristobal hernandez
            'fecha': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
152 1 cristobal hernandez
            'timestamp': datetime.now().timestamp()
153 1 cristobal hernandez
        })
154 1 cristobal hernandez
    except: pass
155 1 cristobal hernandez
156 1 cristobal hernandez
# Hardware
157 1 cristobal hernandez
158 1 cristobal hernandez
def inicializar_hardware():
159 1 cristobal hernandez
    global alimentador_servo
160 1 cristobal hernandez
    try:
161 1 cristobal hernandez
        # Relay
162 1 cristobal hernandez
        pinMode(PORT_RELAY_CAL, "OUTPUT")
163 1 cristobal hernandez
        digitalWrite(PORT_RELAY_CAL, 0)
164 1 cristobal hernandez
        
165 1 cristobal hernandez
        # LCD
166 1 cristobal hernandez
        try:
167 1 cristobal hernandez
            setText("INICIANDO\nSISTEMA...")
168 1 cristobal hernandez
            setRGB(0, 0, 255) # Azul
169 1 cristobal hernandez
        except: print("LCD no detectado (continuando...)")
170 1 cristobal hernandez
        
171 1 cristobal hernandez
        # Servo Motor
172 1 cristobal hernandez
        alimentador_servo = AngularServo(SERVO_PIN, min_angle=0, max_angle=180, 
173 1 cristobal hernandez
                                         min_pulse_width=0.0006, max_pulse_width=0.0024)
174 1 cristobal hernandez
        print("Hardware inicializado correctamente.")
175 1 cristobal hernandez
    except Exception as e:
176 1 cristobal hernandez
        print(f"Error Hardware: {e}")
177 1 cristobal hernandez
        alimentador_servo = None
178 1 cristobal hernandez
179 1 cristobal hernandez
# Sensores
180 1 cristobal hernandez
def leer_temperatura():
181 1 cristobal hernandez
    try:
182 1 cristobal hernandez
        if not os.path.exists(SENSOR_TEMP_PATH):
183 1 cristobal hernandez
            print("Sensor de temperatura no encontrado (Cable desconectado?)")
184 1 cristobal hernandez
            return 25.0
185 1 cristobal hernandez
186 1 cristobal hernandez
        with open(SENSOR_TEMP_PATH, 'r') as f:
187 1 cristobal hernandez
            lineas = f.readlines()
188 1 cristobal hernandez
        
189 1 cristobal hernandez
        if lineas[0].strip()[-3:] == 'YES':
190 1 cristobal hernandez
            posicion_t = lineas[1].find('t=')
191 1 cristobal hernandez
            if posicion_t != -1:
192 1 cristobal hernandez
                temp_string = lineas[1][posicion_t+2:]
193 1 cristobal hernandez
                temp_c = float(temp_string) / 1000.0
194 1 cristobal hernandez
                return round(temp_c, 2)
195 1 cristobal hernandez
    except Exception as e:
196 1 cristobal hernandez
        print(f"Error leyendo temp: {e}")
197 1 cristobal hernandez
    
198 1 cristobal hernandez
    return 25.0
199 1 cristobal hernandez
200 1 cristobal hernandez
def leer_ph():
201 1 cristobal hernandez
    VOLTAJE_NEUTRO = 2.50   
202 1 cristobal hernandez
    FACTOR_CONVERSION = 4.17 
203 1 cristobal hernandez
204 1 cristobal hernandez
    try:
205 1 cristobal hernandez
        valor_raw = grovepi.analogRead(PORT_PH)
206 1 cristobal hernandez
        voltaje = float(valor_raw) * 5.0 / 1023
207 1 cristobal hernandez
        ph_actual = 7.0 + ((voltaje - VOLTAJE_NEUTRO) * FACTOR_CONVERSION)
208 1 cristobal hernandez
        return round(ph_actual, 1)
209 1 cristobal hernandez
    except: return 7.0
210 1 cristobal hernandez
211 1 cristobal hernandez
def leer_nivel_agua():
212 1 cristobal hernandez
    try:
213 1 cristobal hernandez
        altura_total = 40
214 1 cristobal hernandez
        dist = ultrasonicRead(PORT_ULTRASONIC)
215 1 cristobal hernandez
        if dist > altura_total: dist = 40
216 1 cristobal hernandez
        porc = int(100 - ((dist / altura_total) * 100))
217 1 cristobal hernandez
        return max(0, min(100, porc))
218 1 cristobal hernandez
    except: return 80
219 1 cristobal hernandez
220 1 cristobal hernandez
def leer_luz():
221 1 cristobal hernandez
    try:
222 1 cristobal hernandez
        lectura = analogRead(PORT_SENSOR_LUZ)
223 1 cristobal hernandez
        return int((lectura / 800.0) * 100)
224 1 cristobal hernandez
    except: return 50
225 1 cristobal hernandez
226 1 cristobal hernandez
# Actuadores
227 1 cristobal hernandez
228 1 cristobal hernandez
def gestionar_calefactor(temp_actual):
229 1 cristobal hernandez
    target = float(config_actual.get('temp_min', 24.0))
230 1 cristobal hernandez
    if temp_actual < target:
231 1 cristobal hernandez
        digitalWrite(PORT_RELAY_CAL, 1) # Encender
232 1 cristobal hernandez
        return True
233 1 cristobal hernandez
    elif temp_actual > target + 0.5:
234 1 cristobal hernandez
        digitalWrite(PORT_RELAY_CAL, 0) # Apagar
235 1 cristobal hernandez
        return False
236 1 cristobal hernandez
    return False
237 1 cristobal hernandez
238 1 cristobal hernandez
def mover_servo_alimentar():
239 1 cristobal hernandez
    if alimentador_servo is None: return False
240 1 cristobal hernandez
    try:
241 1 cristobal hernandez
        print("Dispensando comida...")
242 1 cristobal hernandez
        alimentador_servo.angle = 90
243 1 cristobal hernandez
        time.sleep(0.8) 
244 1 cristobal hernandez
        alimentador_servo.angle = 110
245 1 cristobal hernandez
        time.sleep(0.3)
246 1 cristobal hernandez
        alimentador_servo.angle = 90
247 1 cristobal hernandez
        time.sleep(0.3)
248 1 cristobal hernandez
        alimentador_servo.angle = 0
249 1 cristobal hernandez
        time.sleep(1)
250 1 cristobal hernandez
        alimentador_servo.value = None 
251 1 cristobal hernandez
        return True
252 1 cristobal hernandez
    except Exception as e:
253 1 cristobal hernandez
        print(f"Error Servo: {e}")
254 1 cristobal hernandez
        return False
255 1 cristobal hernandez
256 1 cristobal hernandez
def gestionar_comida():
257 1 cristobal hernandez
    global ultimo_minuto_alimentacion
258 1 cristobal hernandez
    if not config_actual.get('sistema_alimentacion_on', True): 
259 1 cristobal hernandez
        return False
260 1 cristobal hernandez
261 1 cristobal hernandez
    hora_actual = datetime.now().strftime("%H:%M")
262 1 cristobal hernandez
    horarios = config_actual.get("horarios_comida", [])
263 1 cristobal hernandez
264 1 cristobal hernandez
    if (hora_actual in horarios) and (hora_actual != ultimo_minuto_alimentacion):
265 1 cristobal hernandez
        print(f"Intentando alimentar ({hora_actual})...")
266 1 cristobal hernandez
        exito = mover_servo_alimentar()
267 1 cristobal hernandez
        if exito:
268 1 cristobal hernandez
            ultimo_minuto_alimentacion = hora_actual
269 1 cristobal hernandez
            # EMOJI REMOVIDO AQUI
270 1 cristobal hernandez
            enviar_notificacion_push("Hora de comer!", f"Tu pez ha sido alimentado a las {hora_actual}")
271 1 cristobal hernandez
            guardar_historial_evento("Comida Servida", "Dispensador automatico activado con exito.")
272 1 cristobal hernandez
            return True
273 1 cristobal hernandez
    return False
274 1 cristobal hernandez
275 1 cristobal hernandez
def actualizar_lcd(nemo_id, temp, ph, luz, agua, calefactor_on, comida_servida):
276 1 cristobal hernandez
    try:
277 1 cristobal hernandez
        if comida_servida:
278 1 cristobal hernandez
            setText("   DISPENSANDO\n    COMIDA...")
279 1 cristobal hernandez
            time.sleep(3)
280 1 cristobal hernandez
            return
281 1 cristobal hernandez
282 1 cristobal hernandez
        if calefactor_on: setRGB(255, 60, 0) # Naranja
283 1 cristobal hernandez
        else: setRGB(0, 255, 0) # Verde
284 1 cristobal hernandez
285 1 cristobal hernandez
        setText(f"ID:{nemo_id}\nT:{temp}C  pH:{ph}")
286 1 cristobal hernandez
        time.sleep(4)
287 1 cristobal hernandez
        setText(f"ID:{nemo_id}\nLuz:{luz}% Agua:{agua}%")
288 1 cristobal hernandez
        time.sleep(4)
289 1 cristobal hernandez
    except: pass
290 1 cristobal hernandez
291 1 cristobal hernandez
def main():
292 1 cristobal hernandez
    global nemo_id, ya_se_envio_notificacion
293 1 cristobal hernandez
    
294 1 cristobal hernandez
    print("\n--- INICIANDO SAVENEMO ---")
295 1 cristobal hernandez
    
296 1 cristobal hernandez
    inicializar_hardware()
297 1 cristobal hernandez
    nemo_id = obtener_id_unico()
298 1 cristobal hernandez
    print(f"ID DISPOSITIVO: {nemo_id}")
299 1 cristobal hernandez
    
300 1 cristobal hernandez
    esperar_internet()
301 1 cristobal hernandez
    iniciar_firebase()
302 1 cristobal hernandez
    
303 1 cristobal hernandez
    print("Loop principal iniciado.\n")
304 1 cristobal hernandez
305 1 cristobal hernandez
    while True:
306 1 cristobal hernandez
        try:
307 1 cristobal hernandez
            sincronizar_configuracion()
308 1 cristobal hernandez
            
309 1 cristobal hernandez
            temp = leer_temperatura()
310 1 cristobal hernandez
            ph = leer_ph()
311 1 cristobal hernandez
            agua = leer_nivel_agua()
312 1 cristobal hernandez
            luz = leer_luz()
313 1 cristobal hernandez
            
314 1 cristobal hernandez
            alertas = []
315 1 cristobal hernandez
            cfg = config_actual
316 1 cristobal hernandez
            if temp < float(cfg.get('temp_min', 20)): alertas.append("Temp Baja")
317 1 cristobal hernandez
            if temp > float(cfg.get('temp_max', 35)): alertas.append("Temp Alta")
318 1 cristobal hernandez
            if agua < int(cfg.get('nivel_agua', 80)): alertas.append("Nivel Bajo")
319 1 cristobal hernandez
            if ph < int(cfg.get('ph_min', 80)): alertas.append("PH Bajo")
320 1 cristobal hernandez
            if ph > int(cfg.get('ph_max', 80)): alertas.append("PH Alto")
321 1 cristobal hernandez
322 1 cristobal hernandez
            calefactor_on = gestionar_calefactor(temp)
323 1 cristobal hernandez
            comida_servida = gestionar_comida()
324 1 cristobal hernandez
325 1 cristobal hernandez
            if alertas:
326 1 cristobal hernandez
                if not ya_se_envio_notificacion:
327 1 cristobal hernandez
                    print(f"Alertas: {alertas}")
328 1 cristobal hernandez
                    # EMOJI REMOVIDO AQUI
329 1 cristobal hernandez
                    enviar_notificacion_push("ALERTA ACUARIO ", f"Atencion: {', '.join(alertas)}")
330 1 cristobal hernandez
                    guardar_historial_evento("Alerta Detectada", ', '.join(alertas))
331 1 cristobal hernandez
                    ya_se_envio_notificacion = True
332 1 cristobal hernandez
            else:
333 1 cristobal hernandez
                if ya_se_envio_notificacion: ya_se_envio_notificacion = False
334 1 cristobal hernandez
335 1 cristobal hernandez
            subir_estado(temp, ph, luz, agua, alertas)
336 1 cristobal hernandez
            
337 1 cristobal hernandez
            cal_status = 'ON' if calefactor_on else 'OFF'
338 1 cristobal hernandez
339 1 cristobal hernandez
            # Solo para ver por terminal
340 1 cristobal hernandez
            print(f"[{datetime.now().strftime('%H:%M')}] T:{temp}C pH:{ph} L:{luz}% A:{agua}% Cal:{cal_status}")
341 1 cristobal hernandez
            
342 1 cristobal hernandez
            actualizar_lcd(nemo_id, temp, ph, luz, agua, calefactor_on, comida_servida)
343 1 cristobal hernandez
344 1 cristobal hernandez
        except KeyboardInterrupt:
345 1 cristobal hernandez
            print("\nApagando sistema...")
346 1 cristobal hernandez
            try:
347 1 cristobal hernandez
                digitalWrite(PORT_RELAY_CAL, 0)
348 1 cristobal hernandez
                setRGB(0, 0, 0)
349 1 cristobal hernandez
                setText("")
350 1 cristobal hernandez
                if alimentador_servo: alimentador_servo.detach()
351 1 cristobal hernandez
            except: pass
352 1 cristobal hernandez
            break
353 1 cristobal hernandez
        except Exception as e:
354 1 cristobal hernandez
            print(f"Error en Loop: {e}")
355 1 cristobal hernandez
            time.sleep(5)
356 1 cristobal hernandez
357 1 cristobal hernandez
if __name__ == "__main__":
358 1 cristobal hernandez
    main()        
359 1 cristobal hernandez
</code></pre>