hormigasais-edge-starter 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +48 -0
- package/README.md +46 -16
- package/agents/colonia_bus.py +58 -0
- package/agents/hormiga_centinela.py +66 -39
- package/agents/hormiga_centinela_hw.py +72 -0
- package/agents/hormiga_trampa.py +95 -0
- package/agents/hormiga_trampa_hw_ext.py +58 -0
- package/bootstrap.sh +38 -0
- package/generator.js +27 -33
- package/generator.py +17 -3
- package/package.json +5 -10
- package/.hormigasais/node_config.json +0 -27
- package/generators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/generators/__pycache__/agents_generator.cpython-313.pyc +0 -0
- package/generators/__pycache__/config_generator.cpython-313.pyc +0 -0
- package/generators/__pycache__/contracts_generator.cpython-313.pyc +0 -0
- package/generators/__pycache__/protocol_generator.cpython-313.pyc +0 -0
- package/generators/__pycache__/scripts_generator.cpython-313.pyc +0 -0
- package/protocols/__pycache__/__init__.cpython-313.pyc +0 -0
- package/protocols/__pycache__/constants.cpython-313.pyc +0 -0
- package/protocols/__pycache__/decoder.cpython-313.pyc +0 -0
- package/protocols/__pycache__/encoder.cpython-313.pyc +0 -0
- package/protocols/__pycache__/signer.cpython-313.pyc +0 -0
- package/protocols/__pycache__/verifier.cpython-313.pyc +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
HormigasAIS Source-Available License (Proprietary)
|
|
2
|
+
Versión 1.0 — hormigasais-edge-starter
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 Cristhiam Leonardo Hernández Quiñonez (CLHQ)
|
|
5
|
+
Todos los derechos reservados.
|
|
6
|
+
|
|
7
|
+
1. Naturaleza de esta licencia
|
|
8
|
+
|
|
9
|
+
Esta no es una licencia de código abierto ni una Business Source License.
|
|
10
|
+
Es una licencia propietaria de código fuente visible ("source-available"):
|
|
11
|
+
el código puede leerse, estudiarse y auditarse públicamente, pero su uso,
|
|
12
|
+
copia, modificación y redistribución requieren autorización explícita del
|
|
13
|
+
Licenciante.
|
|
14
|
+
|
|
15
|
+
2. Lo que SÍ está permitido sin autorización adicional
|
|
16
|
+
|
|
17
|
+
- Leer y estudiar el código fuente con fines de evaluación o auditoría.
|
|
18
|
+
- Hacer fork privado únicamente para revisión personal, sin distribuir
|
|
19
|
+
ni desplegar en producción.
|
|
20
|
+
- Instalar el paquete desde el registro npm y ejecutar el generador
|
|
21
|
+
(create-hormigas-node) para crear y probar un nodo propio con fines
|
|
22
|
+
de evaluación, aprendizaje o uso personal no comercial. Los archivos
|
|
23
|
+
de configuración generados para el nodo (p. ej. node_config.json)
|
|
24
|
+
pertenecen a quien los genera. El uso en producción sigue sujeto
|
|
25
|
+
a la sección 3.
|
|
26
|
+
|
|
27
|
+
3. Lo que requiere autorización explícita de CLHQ
|
|
28
|
+
|
|
29
|
+
- Uso en producción, propio o de terceros.
|
|
30
|
+
- Redistribución, en forma original o modificada.
|
|
31
|
+
- Creación de obras derivadas destinadas a distribución o uso público.
|
|
32
|
+
- Cualquier uso comercial.
|
|
33
|
+
|
|
34
|
+
4. Vigencia
|
|
35
|
+
|
|
36
|
+
Esta licencia no tiene fecha de conversión a una licencia de código
|
|
37
|
+
abierto. El Licenciante (CLHQ) retiene control indefinido sobre los
|
|
38
|
+
términos de uso de hormigasais-edge-starter, y puede otorgar licencias adicionales
|
|
39
|
+
o cambiar estos términos para versiones futuras a su propio criterio.
|
|
40
|
+
|
|
41
|
+
5. Sin garantía
|
|
42
|
+
|
|
43
|
+
EL SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO,
|
|
44
|
+
EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A GARANTÍAS DE
|
|
45
|
+
COMERCIABILIDAD, IDONEIDAD PARA UN PROPÓSITO PARTICULAR Y NO INFRACCIÓN.
|
|
46
|
+
|
|
47
|
+
Para solicitar una licencia de uso, contactar a CLHQ a través de
|
|
48
|
+
github.com/Thrumanshow.
|
package/README.md
CHANGED
|
@@ -4,28 +4,31 @@ Starter oficial para desplegar un **nodo soberano** de HormigasAIS con agentes L
|
|
|
4
4
|
|
|
5
5
|
> "Soberanía digital en el edge"
|
|
6
6
|
|
|
7
|
-
## 🚀 Instalación
|
|
7
|
+
## 🚀 Instalación
|
|
8
8
|
|
|
9
|
-
### Opción 1
|
|
9
|
+
### Opción 1 — Starter CLI vía npm (recomendado)
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
npx create-hormigas-node@latest
|
|
13
|
-
\`\`\`
|
|
11
|
+
El starter técnico se distribuye como paquete npm independiente, sin el contenido pedagógico completo:
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
npm install -g @hormigasais/edge-starter
|
|
14
|
+
hormigasais-edge --help
|
|
15
|
+
hormigasais-edge bootstrap
|
|
16
|
+
hormigasais-edge validate
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
git clone https://github.com/Thrumanshow/hormigasais-edge-starter.git
|
|
19
|
-
cd hormigasais-edge-starter
|
|
20
|
-
bash bootstrap.sh
|
|
21
|
-
python3 generator.py
|
|
22
|
-
\`\`\`
|
|
18
|
+
Por defecto, el laboratorio se crea en .hormigasais-edge-lab/ en el directorio actual. Puedes cambiar la ubicación con LAB_DIR:
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
LAB_DIR="$HOME/hormigasais-edge-lab" hormigasais-edge bootstrap
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
Paquete: [@hormigasais/edge-starter](https://www.npmjs.com/package/@hormigasais/edge-starter)
|
|
23
|
+
|
|
24
|
+
### Opción 2 — Clonar el repositorio completo (incluye el curso)
|
|
25
|
+
|
|
26
|
+
git clone https://github.com/Thrumanshow/hormigasais-edge-starter.git
|
|
27
|
+
cd hormigasais-edge-starter
|
|
28
|
+
bash bootstrap.sh
|
|
29
|
+
python3 generator.py
|
|
30
|
+
|
|
31
|
+
Usa esta opción si además del starter quieres acceso al curso práctico completo (./course/).
|
|
29
32
|
|
|
30
33
|
## ¿Qué incluye?
|
|
31
34
|
|
|
@@ -65,3 +68,30 @@ Author: Cristhiam Leonardo Hernández Quiñonez (CLHQ)
|
|
|
65
68
|
© HormigasAIS • Lenguaje Binario HormigasAIS (LBH)
|
|
66
69
|
|
|
67
70
|
[hormigasais.com](https://hormigasais.com) | [@Thrumanshow](https://github.com/Thrumanshow)
|
|
71
|
+
|
|
72
|
+
## Curso Práctico
|
|
73
|
+
|
|
74
|
+
El directorio [`course/`](./course/) contiene un laboratorio educativo aislado para aprender administración Edge y protocolos LBH desde Android/Termux. Sus scripts utilizan fixtures y no requieren secretos reales. Consulta [`course/README.md`](./course/README.md) para comenzar.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 📄 Componentes y Enlaces del Ecosistema
|
|
79
|
+
|
|
80
|
+
| Repo | Descripción |
|
|
81
|
+
| :--- | :--- |
|
|
82
|
+
| **LBH-Net** | Red soberana P2P :9199/:9200 |
|
|
83
|
+
| **Institucionalidad** | Foundation & Governance |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### Arquitectura y Ecosistema HormigasAIS
|
|
88
|
+
* **Red P2P Base:** [LBH-Net](https://github.com/HormigasAIS/LBH-Net) — Red soberana distribuida basada en Protocolo LBH (Termux / UDP :9199 / TCP :9200 / SQLite / Ed25519).
|
|
89
|
+
* **Vitrina Oficial:** [hormigasais.com](https://hormigasais.com)
|
|
90
|
+
* **Documentación Técnica:** [docs.hormigasais.com](https://docs.hormigasais.com)
|
|
91
|
+
* **Fundador:** CLHQ — Cristhiam Leonardo Hernández Quiñonez | El Salvador 2026 | DOI: 10.5281/zenodo.17767205
|
|
92
|
+
|
|
93
|
+
## Licencia
|
|
94
|
+
|
|
95
|
+
HormigasAIS Source-Available License v1.0 (ver `LICENSE`). Puedes leer y estudiar el código, e instalar el paquete para generar y probar un nodo propio con fines personales no comerciales. El uso en producción, la redistribución y el uso comercial requieren autorización de CLHQ.
|
|
96
|
+
|
|
97
|
+
Las versiones 0.1.0 a 0.1.2 se publicaron declarando MIT; desde la 0.1.3 rige esta licencia.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import subprocess, json, sys, time, threading
|
|
2
|
+
|
|
3
|
+
VERSION = "0.3.0"
|
|
4
|
+
|
|
5
|
+
def reporter(trampa_ref, intervalo=30):
|
|
6
|
+
while True:
|
|
7
|
+
time.sleep(intervalo)
|
|
8
|
+
estado = "ACTIVO" if trampa_ref.activo else "LETARGO"
|
|
9
|
+
print(f"\n[REPORTE] Capturas={trampa_ref.total_capturas} | "
|
|
10
|
+
f"Estado={estado} | Temp={trampa_ref.temperatura}C")
|
|
11
|
+
|
|
12
|
+
def iniciar_colonia():
|
|
13
|
+
print(f"[HormigasAIS] Colonia Nodo Escuela v{VERSION}")
|
|
14
|
+
print("[BUS] Acoplando subprocesos y cargando contratos...")
|
|
15
|
+
print("-" * 70)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from hormiga_trampa import HormigaTrampa
|
|
19
|
+
trampa = HormigaTrampa()
|
|
20
|
+
except ImportError:
|
|
21
|
+
print("[ERROR] Falta hormiga_trampa.py")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
threading.Thread(
|
|
25
|
+
target=reporter, args=(trampa, 30), daemon=True
|
|
26
|
+
).start()
|
|
27
|
+
|
|
28
|
+
proceso = subprocess.Popen(
|
|
29
|
+
["python3", "hormiga_centinela.py"],
|
|
30
|
+
stdout=subprocess.PIPE,
|
|
31
|
+
stderr=subprocess.STDOUT,
|
|
32
|
+
text=True,
|
|
33
|
+
bufsize=1,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
for linea in iter(proceso.stdout.readline, ""):
|
|
38
|
+
linea = linea.strip()
|
|
39
|
+
if "FEROMONA_EMITIDA ==>" in linea:
|
|
40
|
+
partes = linea.split("==>")
|
|
41
|
+
if len(partes) > 1:
|
|
42
|
+
trampa.escuchar_bus(partes[1].strip())
|
|
43
|
+
elif "*" in linea:
|
|
44
|
+
sys.stdout.write("* ")
|
|
45
|
+
sys.stdout.flush()
|
|
46
|
+
trampa.escuchar_bus('{"type":"env_clear"}')
|
|
47
|
+
except KeyboardInterrupt:
|
|
48
|
+
print("\n\n[INTERRUPCION] Apagado manual detectado.")
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"\n[FALLO BUS] Anomalia: {e}")
|
|
51
|
+
finally:
|
|
52
|
+
print("\n[FAILSAFE] Protocolo de desconexion segura...")
|
|
53
|
+
proceso.terminate()
|
|
54
|
+
trampa.apagado_seguro()
|
|
55
|
+
print("[OK] Colonia cerrada de forma soberana.")
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
iniciar_colonia()
|
|
@@ -1,41 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
""
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
import os
|
|
2
|
+
import time, json, random, sys, hmac, hashlib
|
|
3
|
+
|
|
4
|
+
VERSION = "0.3.0"
|
|
5
|
+
NODE_ID = "centinela_escuela_01"
|
|
6
|
+
TARGET = "mosquito_pulse"
|
|
7
|
+
SECRET = os.environ.get("LAB_ESCUELA_SECRET", "hormigasais-colonia-escuela-demo").encode()
|
|
8
|
+
|
|
9
|
+
class UmbralAdaptativo:
|
|
10
|
+
def __init__(self, base=0.75, ventana=20):
|
|
11
|
+
self.historial = []
|
|
12
|
+
self.base = base
|
|
13
|
+
self.ventana = ventana
|
|
14
|
+
|
|
15
|
+
def actualizar(self, v):
|
|
16
|
+
self.historial.append(v)
|
|
17
|
+
if len(self.historial) > self.ventana:
|
|
18
|
+
self.historial.pop(0)
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def umbral(self):
|
|
22
|
+
if len(self.historial) < 5:
|
|
23
|
+
return self.base
|
|
24
|
+
media = sum(self.historial) / len(self.historial)
|
|
25
|
+
return round(min(media + 0.15, 0.92), 3)
|
|
26
|
+
|
|
27
|
+
def firmar(paquete_dict):
|
|
28
|
+
payload = json.dumps(paquete_dict, sort_keys=True).encode()
|
|
29
|
+
sig = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()[:16]
|
|
30
|
+
paquete_dict["sig"] = sig
|
|
31
|
+
return json.dumps(paquete_dict)
|
|
32
|
+
|
|
33
|
+
def generar_feromona(intensidad):
|
|
34
|
+
p = {
|
|
35
|
+
"timestamp": round(time.time(), 6),
|
|
36
|
+
"type": TARGET,
|
|
37
|
+
"origin": NODE_ID,
|
|
38
|
+
"status": "active",
|
|
39
|
+
"intensity": round(intensidad, 2),
|
|
40
|
+
"version": VERSION,
|
|
41
|
+
}
|
|
42
|
+
return firmar(p)
|
|
43
|
+
|
|
44
|
+
def ejecutar():
|
|
45
|
+
ua = UmbralAdaptativo()
|
|
46
|
+
print(f"[HormigasAIS] Centinela [{NODE_ID}] v{VERSION}")
|
|
47
|
+
print(f"Escaneando espectro infrarrojo... Ctrl+C para detener.")
|
|
48
|
+
print("-" * 70)
|
|
49
|
+
try:
|
|
50
|
+
while True:
|
|
51
|
+
lectura = random.uniform(0.1, 1.0)
|
|
52
|
+
ua.actualizar(lectura)
|
|
53
|
+
if lectura > ua.umbral:
|
|
54
|
+
print(f"[ALERTA] Gradiente={round(lectura,2)} Umbral={ua.umbral}")
|
|
55
|
+
paquete = generar_feromona(lectura)
|
|
56
|
+
print(f"[XOXO-BUS] FEROMONA_EMITIDA ==> {paquete}")
|
|
57
|
+
print("[BUS] Nodos trampa alertados.")
|
|
58
|
+
print("-" * 70)
|
|
59
|
+
time.sleep(5)
|
|
60
|
+
else:
|
|
61
|
+
sys.stdout.write("* ")
|
|
62
|
+
sys.stdout.flush()
|
|
63
|
+
time.sleep(1.5)
|
|
64
|
+
except KeyboardInterrupt:
|
|
65
|
+
print("\n[STOP] Centinela detenido. Modo letargo.")
|
|
36
66
|
|
|
37
67
|
if __name__ == "__main__":
|
|
38
|
-
|
|
39
|
-
while True:
|
|
40
|
-
emit_pulse()
|
|
41
|
-
time.sleep(INTERVAL)
|
|
68
|
+
ejecutar()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import serial, json, sys, time, hmac, hashlib
|
|
3
|
+
|
|
4
|
+
VERSION = "1.0.0-hw"
|
|
5
|
+
NODE_ID = "centinela_hw_01"
|
|
6
|
+
SECRET = os.environ.get("LAB_ESCUELA_SECRET", "hormigasais-colonia-escuela-demo").encode()
|
|
7
|
+
PORT = "/dev/ttyUSB0" # Termux: ls /dev/tty* para confirmar
|
|
8
|
+
BAUD = 115200
|
|
9
|
+
|
|
10
|
+
def firmar(d):
|
|
11
|
+
payload = json.dumps(d, sort_keys=True).encode()
|
|
12
|
+
sig = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()[:16]
|
|
13
|
+
d["sig"] = sig
|
|
14
|
+
return json.dumps(d)
|
|
15
|
+
|
|
16
|
+
def abrir_serial():
|
|
17
|
+
intentos = 0
|
|
18
|
+
while intentos < 5:
|
|
19
|
+
try:
|
|
20
|
+
s = serial.Serial(PORT, BAUD, timeout=2)
|
|
21
|
+
print(f"[HW] Puerto {PORT} abierto a {BAUD} baud.")
|
|
22
|
+
return s
|
|
23
|
+
except serial.SerialException as e:
|
|
24
|
+
intentos += 1
|
|
25
|
+
print(f"[HW] Reintento {intentos}/5 — {e}")
|
|
26
|
+
time.sleep(3)
|
|
27
|
+
print(f"[ERROR] No se pudo abrir {PORT}. Verifica: pkg install python-pyserial")
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
|
|
30
|
+
def ejecutar():
|
|
31
|
+
print(f"[HormigasAIS] Centinela Hardware [{NODE_ID}] v{VERSION}")
|
|
32
|
+
print(f"[HW] Escuchando ESP8266 en {PORT}...")
|
|
33
|
+
print("-" * 70)
|
|
34
|
+
ser = abrir_serial()
|
|
35
|
+
try:
|
|
36
|
+
while True:
|
|
37
|
+
linea = ser.readline().decode("utf-8", errors="ignore").strip()
|
|
38
|
+
if not linea:
|
|
39
|
+
sys.stdout.write("* ")
|
|
40
|
+
sys.stdout.flush()
|
|
41
|
+
continue
|
|
42
|
+
try:
|
|
43
|
+
d = json.loads(linea)
|
|
44
|
+
except json.JSONDecodeError:
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# Boot message del ESP
|
|
48
|
+
if "boot" in d:
|
|
49
|
+
print(f"\n[HW] ESP8266 conectado: {d}")
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
# Evento de deteccion real
|
|
53
|
+
if d.get("type") == "mosquito_pulse":
|
|
54
|
+
d["origin"] = NODE_ID
|
|
55
|
+
d["version"] = VERSION
|
|
56
|
+
# Firmar antes de emitir al bus
|
|
57
|
+
paquete = firmar(d)
|
|
58
|
+
print(f"\n[ALERTA] Sensor PIR activado (I={d.get('intensity', 0):.2f})")
|
|
59
|
+
print(f"[XOXO-BUS] FEROMONA_EMITIDA ==> {paquete}")
|
|
60
|
+
print("[BUS] Nodos trampa alertados.")
|
|
61
|
+
print("-" * 70)
|
|
62
|
+
# Cooldown para no saturar
|
|
63
|
+
time.sleep(4)
|
|
64
|
+
|
|
65
|
+
except KeyboardInterrupt:
|
|
66
|
+
print("\n[STOP] Centinela hardware detenido.")
|
|
67
|
+
finally:
|
|
68
|
+
ser.close()
|
|
69
|
+
print("[HW] Puerto serial cerrado.")
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
ejecutar()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time, json, hmac, hashlib
|
|
3
|
+
|
|
4
|
+
VERSION = "0.3.0"
|
|
5
|
+
NODE_ID = "trampa_escuela_01"
|
|
6
|
+
TRIGGER_TYPE = "mosquito_pulse"
|
|
7
|
+
CICLOS_GRACIA = 4
|
|
8
|
+
SECRET = os.environ.get("LAB_ESCUELA_SECRET", "hormigasais-colonia-escuela-demo").encode()
|
|
9
|
+
LOG_FILE = "capturas.jsonl"
|
|
10
|
+
|
|
11
|
+
class HormigaTrampa:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.activo = False
|
|
14
|
+
self.temperatura = 24.0
|
|
15
|
+
self.ciclos_sin_feromona = 0
|
|
16
|
+
self.total_capturas = 0
|
|
17
|
+
self._ultimo_ts = 0.0 # anti-duplicado
|
|
18
|
+
|
|
19
|
+
# ── Verificación HMAC ──────────────────────────────────────────────
|
|
20
|
+
def _verificar(self, d):
|
|
21
|
+
sig_rx = d.pop("sig", "")
|
|
22
|
+
payload = json.dumps(d, sort_keys=True).encode()
|
|
23
|
+
sig_ok = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()[:16]
|
|
24
|
+
d["sig"] = sig_rx # restaurar para no mutar el dict
|
|
25
|
+
return hmac.compare_digest(sig_rx, sig_ok)
|
|
26
|
+
|
|
27
|
+
# ── Registro soberano ──────────────────────────────────────────────
|
|
28
|
+
def _registrar(self, intensidad):
|
|
29
|
+
entrada = {
|
|
30
|
+
"ts": round(time.time(), 3),
|
|
31
|
+
"node": NODE_ID,
|
|
32
|
+
"intensidad": intensidad,
|
|
33
|
+
"succion": "MAX" if intensidad > 0.85 else "MOD",
|
|
34
|
+
}
|
|
35
|
+
with open(LOG_FILE, "a") as f:
|
|
36
|
+
f.write(json.dumps(entrada) + "\n")
|
|
37
|
+
|
|
38
|
+
# ── Bus principal ──────────────────────────────────────────────────
|
|
39
|
+
def escuchar_bus(self, paquete_crudo):
|
|
40
|
+
try:
|
|
41
|
+
d = json.loads(paquete_crudo)
|
|
42
|
+
except Exception:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
if d.get("type") == TRIGGER_TYPE and d.get("status") == "active":
|
|
46
|
+
# Anti-duplicado: ignorar si mismo timestamp ya procesado
|
|
47
|
+
ts = d.get("timestamp", 0)
|
|
48
|
+
if ts == self._ultimo_ts:
|
|
49
|
+
return
|
|
50
|
+
self._ultimo_ts = ts
|
|
51
|
+
|
|
52
|
+
if not self._verificar(dict(d)):
|
|
53
|
+
print(f"[SEGURIDAD] Firma invalida — paquete descartado.")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
self.ciclos_sin_feromona = 0
|
|
57
|
+
self.reaccionar(d.get("intensity", 0.5))
|
|
58
|
+
else:
|
|
59
|
+
self.evaluar_disipacion()
|
|
60
|
+
|
|
61
|
+
# ── Reacción ───────────────────────────────────────────────────────
|
|
62
|
+
def reaccionar(self, intensidad):
|
|
63
|
+
self.activo = True
|
|
64
|
+
self.temperatura = 36.5
|
|
65
|
+
self.total_capturas += 1
|
|
66
|
+
succion = "100% (MAXIMA)" if intensidad > 0.85 else "60% (MODERADA)"
|
|
67
|
+
|
|
68
|
+
print(f"\n[{NODE_ID}] Feromona detectada (I={intensidad} | Captura #{self.total_capturas})")
|
|
69
|
+
print(f"[ACTUADOR] Resistencia IR -> {self.temperatura}C")
|
|
70
|
+
print(f"[MECANICO] Extractor al {succion}")
|
|
71
|
+
print("-" * 65)
|
|
72
|
+
self._registrar(intensidad)
|
|
73
|
+
|
|
74
|
+
# ── Disipación ─────────────────────────────────────────────────────
|
|
75
|
+
def evaluar_disipacion(self):
|
|
76
|
+
if not self.activo:
|
|
77
|
+
return
|
|
78
|
+
self.ciclos_sin_feromona += 1
|
|
79
|
+
restantes = CICLOS_GRACIA - self.ciclos_sin_feromona
|
|
80
|
+
if restantes > 0:
|
|
81
|
+
print(f"\n[{NODE_ID}] Silencio. Persistencia biologica ({restantes} ciclos)...")
|
|
82
|
+
print(f"[ACTUADOR] Manteniendo gradiente a {self.temperatura}C")
|
|
83
|
+
print("-" * 65)
|
|
84
|
+
else:
|
|
85
|
+
print(f"\n[{NODE_ID}] Feromona evaporada.")
|
|
86
|
+
self.apagado_seguro()
|
|
87
|
+
|
|
88
|
+
# ── Apagado ────────────────────────────────────────────────────────
|
|
89
|
+
def apagado_seguro(self):
|
|
90
|
+
if self.activo:
|
|
91
|
+
self.temperatura = 24.0
|
|
92
|
+
self.activo = False
|
|
93
|
+
print("[BAJO CONSUMO] Extractor OFF. IR -> 24C. Modo letargo.")
|
|
94
|
+
print(f"[STATS] Total capturas esta sesion: {self.total_capturas}")
|
|
95
|
+
print("-" * 65)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# EXTENSION para hormiga_trampa.py — agregar metodo activar_gpio()
|
|
2
|
+
# Conectar segundo ESP8266 (o mismo via segundo canal) que controle:
|
|
3
|
+
# D1 -> Relay Resistencia IR
|
|
4
|
+
# D2 -> Relay Ventilador
|
|
5
|
+
# D3 -> LED UV (ya activo en firmware, este lo modula)
|
|
6
|
+
|
|
7
|
+
import serial, json, time
|
|
8
|
+
|
|
9
|
+
PUERTO_TRAMPA = "/dev/ttyUSB1" # Si hay segundo ESP; si es uno solo, mismo puerto
|
|
10
|
+
BAUD_TRAMPA = 115200
|
|
11
|
+
|
|
12
|
+
class GPIOBridge:
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.ser = None
|
|
15
|
+
self._conectar()
|
|
16
|
+
|
|
17
|
+
def _conectar(self):
|
|
18
|
+
try:
|
|
19
|
+
self.ser = serial.Serial(PUERTO_TRAMPA, BAUD_TRAMPA, timeout=1)
|
|
20
|
+
print(f"[HW-TRAMPA] GPIO Bridge activo en {PUERTO_TRAMPA}")
|
|
21
|
+
except Exception as e:
|
|
22
|
+
print(f"[HW-TRAMPA] Sin GPIO Bridge: {e} — modo simulacion activo")
|
|
23
|
+
self.ser = None
|
|
24
|
+
|
|
25
|
+
def _enviar(self, cmd):
|
|
26
|
+
if self.ser and self.ser.is_open:
|
|
27
|
+
try:
|
|
28
|
+
self.ser.write((json.dumps(cmd) + "\n").encode())
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"[HW-TRAMPA] Error GPIO: {e}")
|
|
31
|
+
|
|
32
|
+
def activar_trampa(self, intensidad):
|
|
33
|
+
potencia = 100 if intensidad > 0.85 else 60
|
|
34
|
+
self._enviar({
|
|
35
|
+
"cmd": "trampa_on",
|
|
36
|
+
"ir_on": True,
|
|
37
|
+
"fan_pct": potencia,
|
|
38
|
+
"uv_on": True
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
def apagar_trampa(self):
|
|
42
|
+
self._enviar({
|
|
43
|
+
"cmd": "trampa_off",
|
|
44
|
+
"ir_on": False,
|
|
45
|
+
"fan_pct": 0,
|
|
46
|
+
"uv_on": True # UV siempre activo para atraccion pasiva
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
def cerrar(self):
|
|
50
|
+
if self.ser:
|
|
51
|
+
self.apagar_trampa()
|
|
52
|
+
time.sleep(0.5)
|
|
53
|
+
self.ser.close()
|
|
54
|
+
|
|
55
|
+
# Instrucciones de integracion:
|
|
56
|
+
# 1. En HormigaTrampa.__init__() agregar: self.gpio = GPIOBridge()
|
|
57
|
+
# 2. En reaccionar() agregar: self.gpio.activar_trampa(intensidad)
|
|
58
|
+
# 3. En apagado_seguro() agregar: self.gpio.apagar_trampa()
|
package/bootstrap.sh
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# HormigasAIS Edge Starter — bootstrap.sh
|
|
3
|
+
# Prepara el entorno antes de ejecutar generator.py
|
|
4
|
+
|
|
5
|
+
set -Eeuo pipefail
|
|
6
|
+
|
|
7
|
+
echo "🐜 HormigasAIS Edge Starter — Bootstrap"
|
|
8
|
+
echo "========================================"
|
|
9
|
+
|
|
10
|
+
# 1. Validar Python3
|
|
11
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
12
|
+
echo "❌ python3 no encontrado. Instálalo antes de continuar."
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
echo "✅ python3: $(python3 --version)"
|
|
16
|
+
|
|
17
|
+
# 2. Validar Node (opcional, solo si se usará generator.js)
|
|
18
|
+
if command -v node >/dev/null 2>&1; then
|
|
19
|
+
echo "✅ node: $(node --version)"
|
|
20
|
+
else
|
|
21
|
+
echo "⚠️ node no encontrado (opcional, solo necesario para generator.js)"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# 3. Crear directorios base si no existen
|
|
25
|
+
mkdir -p logs generators .hormigasais
|
|
26
|
+
echo "✅ Directorios base verificados: logs/, generators/, .hormigasais/"
|
|
27
|
+
|
|
28
|
+
# 4. Instalar dependencias npm solo si existe package.json y node/npm disponibles
|
|
29
|
+
if [ -f package.json ] && command -v npm >/dev/null 2>&1; then
|
|
30
|
+
echo "📦 Instalando dependencias npm..."
|
|
31
|
+
npm install --omit=dev
|
|
32
|
+
else
|
|
33
|
+
echo "⚠️ Saltando npm install (sin package.json o sin npm)"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
echo "========================================"
|
|
37
|
+
echo "✅ Bootstrap completado. Siguiente paso:"
|
|
38
|
+
echo " python3 generator.py"
|
package/generator.js
CHANGED
|
@@ -1,42 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const { execSync } = require('child_process');
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
5
4
|
|
|
6
5
|
const targetName = process.argv[2] || 'mi-colonia';
|
|
7
6
|
const targetDir = path.join(process.cwd(), targetName);
|
|
8
7
|
|
|
9
|
-
console.log(`🐜 [HormigasAIS]
|
|
8
|
+
console.log(`🐜 [HormigasAIS] Creando nodo: ${targetName}...`);
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
for (const dir of ['agents', 'protocols', 'scripts', '.hormigasais']) {
|
|
11
|
+
fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"cert": "SELLADO-SOBERANO-LBH"
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
fs.writeFileSync(path.join(targetDir, '.hormigasais', 'node_config.json'), JSON.stringify(config, null, 2));
|
|
14
|
+
const configFile = path.join(targetDir, '.hormigasais', 'node_config.json');
|
|
15
|
+
|
|
16
|
+
if (fs.existsSync(configFile)) {
|
|
17
|
+
console.log(' node_config.json ya existe, no se sobrescribe.');
|
|
18
|
+
} else {
|
|
19
|
+
const config = {
|
|
20
|
+
schema: 'LBH_NODE_CONFIG_V1',
|
|
21
|
+
node_id: 'NODE-' + Math.random().toString(36).substring(2, 8).toUpperCase(),
|
|
22
|
+
node_name: targetName,
|
|
23
|
+
protocol: 'LBH_BINARY_V2',
|
|
24
|
+
version: '2.0.0',
|
|
25
|
+
owner: { name: 'YOUR-NAME', email: 'YOUR-EMAIL' },
|
|
26
|
+
location: { country: 'YOUR-COUNTRY', city: 'YOUR-CITY' },
|
|
27
|
+
created_at: new Date().toISOString(),
|
|
28
|
+
status: 'INITIALIZED',
|
|
29
|
+
agents: ['hormiga_centinela', 'hormiga_relevo'],
|
|
30
|
+
certificate: 'PENDING'
|
|
31
|
+
};
|
|
32
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2) + '\n');
|
|
33
|
+
}
|
|
39
34
|
|
|
40
|
-
console.log(`✅ Nodo
|
|
41
|
-
console.log(`
|
|
42
|
-
Siguiente paso:\n cd ${targetName}\n ./scripts/ignicion.sh`);
|
|
35
|
+
console.log(`✅ Nodo creado en ${targetDir}`);
|
|
36
|
+
console.log(`Siguiente paso: cd ${targetName} y completa .hormigasais/node_config.json`);
|
package/generator.py
CHANGED
|
@@ -22,7 +22,6 @@ sys.path.insert(0, str(ROOT))
|
|
|
22
22
|
|
|
23
23
|
MODULES = [
|
|
24
24
|
"config_generator",
|
|
25
|
-
"readme_generator",
|
|
26
25
|
"protocol_generator",
|
|
27
26
|
"contracts_generator",
|
|
28
27
|
"agents_generator",
|
|
@@ -46,6 +45,9 @@ def main():
|
|
|
46
45
|
print("🐜 HormigasAIS Generator")
|
|
47
46
|
print("============================================")
|
|
48
47
|
|
|
48
|
+
fallos = []
|
|
49
|
+
faltantes = []
|
|
50
|
+
|
|
49
51
|
for module_name in MODULES:
|
|
50
52
|
|
|
51
53
|
print(f"[LOAD] {module_name}")
|
|
@@ -68,15 +70,27 @@ def main():
|
|
|
68
70
|
|
|
69
71
|
except ModuleNotFoundError:
|
|
70
72
|
|
|
71
|
-
print(f"[
|
|
73
|
+
print(f"[FALTA] {module_name} no existe — módulo obligatorio")
|
|
74
|
+
faltantes.append(module_name)
|
|
72
75
|
|
|
73
76
|
except Exception as e:
|
|
74
77
|
|
|
75
78
|
print(f"[FAIL] {module_name}")
|
|
76
79
|
print(e)
|
|
80
|
+
fallos.append(module_name)
|
|
77
81
|
|
|
78
82
|
print("")
|
|
79
|
-
|
|
83
|
+
|
|
84
|
+
if faltantes or fallos:
|
|
85
|
+
print("❌ Proceso terminado CON ERRORES.")
|
|
86
|
+
if faltantes:
|
|
87
|
+
print(f" Módulos faltantes: {", ".join(faltantes)}")
|
|
88
|
+
if fallos:
|
|
89
|
+
print(f" Módulos con fallo: {", ".join(fallos)}")
|
|
90
|
+
print("============================================")
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
print("✅ Proceso terminado correctamente.")
|
|
80
94
|
print("============================================")
|
|
81
95
|
|
|
82
96
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hormigasais-edge-starter",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Starter template para desplegar un nodo soberano HormigasAIS con agentes LBH. Incluye SDK de protocolos LBH, generadores
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Starter template para desplegar un nodo soberano HormigasAIS con agentes LBH. Incluye SDK de protocolos LBH, generadores automáticos y agentes edge.",
|
|
5
5
|
"main": "./generator.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-hormigas-node": "generator.js"
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"termux",
|
|
20
20
|
"offline-first"
|
|
21
21
|
],
|
|
22
|
-
"author": "Cristhiam Leonardo
|
|
23
|
-
"license": "
|
|
22
|
+
"author": "Cristhiam Leonardo Hernández Quiñonez",
|
|
23
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
24
24
|
"bugs": {
|
|
25
25
|
"url": "https://github.com/Thrumanshow/hormigasais-edge-starter/issues"
|
|
26
26
|
},
|
|
@@ -29,12 +29,8 @@
|
|
|
29
29
|
"start": "python3 generator.py",
|
|
30
30
|
"bootstrap": "bash bootstrap.sh",
|
|
31
31
|
"verify": "python3 scripts/verify_sdk.py",
|
|
32
|
-
"postinstall": "python3 generator.py --quick || echo 'HormigasAIS Edge Starter instalado'",
|
|
33
32
|
"test": "python3 scripts/verify_sdk.py"
|
|
34
33
|
},
|
|
35
|
-
"dependencies": {
|
|
36
|
-
"jspdf": "^2.5.1"
|
|
37
|
-
},
|
|
38
34
|
"engines": {
|
|
39
35
|
"node": ">=14.0.0"
|
|
40
36
|
},
|
|
@@ -48,9 +44,8 @@
|
|
|
48
44
|
"scripts",
|
|
49
45
|
"utils",
|
|
50
46
|
"generators",
|
|
51
|
-
".hormigasais",
|
|
52
47
|
"README.md",
|
|
53
48
|
"package.json"
|
|
54
49
|
],
|
|
55
50
|
"packageManager": "npm@11.15.0"
|
|
56
|
-
}
|
|
51
|
+
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema": "LBH_NODE_CONFIG_V1",
|
|
3
|
-
"node_id": "A16-u0_a350",
|
|
4
|
-
"node_name": "mi-colonia",
|
|
5
|
-
"protocol": "LBH_BINARY_V2",
|
|
6
|
-
"version": "2.0.0",
|
|
7
|
-
"owner": {
|
|
8
|
-
"name": "YOUR-NAME",
|
|
9
|
-
"email": "YOUR-EMAIL"
|
|
10
|
-
},
|
|
11
|
-
"location": {
|
|
12
|
-
"country": "SV",
|
|
13
|
-
"city": "San Miguel"
|
|
14
|
-
},
|
|
15
|
-
"master_node": "A16-SanMiguel-SV",
|
|
16
|
-
"created_at": "2026-07-24T02:27:25Z",
|
|
17
|
-
"status": "INITIALIZED",
|
|
18
|
-
"agents": [
|
|
19
|
-
"hormiga_centinela",
|
|
20
|
-
"hormiga_relevo"
|
|
21
|
-
],
|
|
22
|
-
"security": {
|
|
23
|
-
"hmac_required": true,
|
|
24
|
-
"algorithm": "HMAC-SHA256"
|
|
25
|
-
},
|
|
26
|
-
"certificate": "PENDING"
|
|
27
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|