Motores de scripting
Escribe lógica de aplicación en Lua, Python, C o Pascal. Los cuatro lenguajes participan en igualdad de condiciones en el mismo universo — mismos paths, mismos eventos, mismos recursos.
La capa de lógica
El scripting de aplicaciones en TLS sigue una arquitectura en tres partes que separa el framework de los lenguajes y del código de usuario:
1. mod_logic — el framework agnóstico al lenguaje. Este módulo posee el espacio de nombres del path /logic/. Gestiona el directorio de scripts, mantiene la tabla de rutas que mapea paths HTTP a handlers de scripts y delega la ejecución al motor de lenguaje correspondiente. Cuando llega una solicitud a una ruta gestionada por script, mod_logic determina qué lenguaje la posee y reenvía el mensaje a ese motor a través de su path (por ejemplo, /logic_lua/functions/execute, /logic_python/functions/execute). mod_logic nunca interpreta código de usuario — orquesta.
2. Motores de lenguaje — un módulo por lenguaje. Cada motor (mod_logic_lua, mod_logic_python, mod_logic_c, mod_logic_pascal) es un módulo TLS estándar que registra sus propios paths y gestiona la carga, compilación cuando es necesario y ejecución de scripts. Cada motor expone la API completa de Portal a su lenguaje en el idioma más natural para ese lenguaje. Los motores son independientes — se puede cargar solo Lua, o los cuatro, o ninguno.
3. Scripts — código de aplicación del usuario. Los scripts residen en /var/lib/portal/<instance>/logic/<appname>/ y se escriben en el lenguaje elegido. Cada script registra rutas, se suscribe a eventos y llama a paths de Portal para interactuar con el resto del sistema. Un script es una aplicación — tiene un ciclo de vida (carga, ejecución, descarga) gestionado por su motor.
Lua 5.4 (mod_logic_lua)
Lua se ejecuta integrado en el proceso con transferencia de datos zero-copy entre el core en C y la VM de Lua. Esto lo convierte en la opción de scripting más rápida y la elección natural para handlers de alta frecuencia, procesadores de eventos y cualquier cosa que necesite estar cerca del hardware.
La API completa de Portal se expone como una tabla nativa de Lua:
local portal = require("portal")
-- Leer cualquier path
local status = portal.get("/core/status")
local devices = portal.get("/iot/resources/devices")
-- Llamar funciones
local result, code = portal.call("/cache/functions/set",
{key="temp", value="23.5"})
-- Registrar rutas HTTP
portal.route("GET", "/app/dashboard", "handle_dashboard")
-- Handlers de eventos
portal.on("/events/iot/state_change", "on_change")
-- Registro de log
portal.log("info", "App loaded")
function handle_dashboard(req)
local metrics = portal.get("/metrics/resources/memory")
return "Dashboard: " .. metrics
end
Ubicación de scripts: /var/lib/portal/<instance>/logic/<appname>/main.lua
Python 3 (mod_logic_python)
Python se ejecuta en un subproceso bifurcado para evitar conflictos de manejadores de señales de CPython con el event loop libev del core. La comunicación entre el proceso padre en C y el subproceso Python fluye a través de un puente de tuberías JSON:
Parent (C) ──stdin──> Python subprocess ──stdout──> Parent
JSON request JSON response
stderr (ROUTE:, LOG:, READY:)
La arquitectura de subprocesos significa que los scripts Python tienen acceso a todo el ecosistema Python — numpy, pandas, bibliotecas de aprendizaje automático — sin poner en riesgo la estabilidad del core. El puente JSON es invisible para el autor del script:
import portal
def handle_hello(req):
return "Hello from Python!"
def handle_compute(req):
import math
return f"Pi = {math.pi:.20f}"
portal.route("GET", "/app/pyapp/hello", handle_hello)
portal.route("GET", "/app/pyapp/compute", handle_compute)
Ubicación de scripts: /var/lib/portal/<instance>/logic/<appname>/main.py
C (mod_logic_c)
Los scripts en C se compilan a partir de archivos fuente .c usando gcc en el momento de la carga. El motor produce un objeto compartido (.so) y luego lo carga con dlopen en el proceso en ejecución. Esto otorga a los scripts velocidad de ejecución nativa con acceso directo a la API portal.h — sin marshalling, sin serialización, sin sobrecarga.
Cada script en C exporta tres funciones de ciclo de vida:
#include "portal/portal.h"
int app_load(portal_core_t *core) {
core->path_register(core, "/app/capp/fast", "logic_c");
return 0;
}
int app_handle(portal_core_t *core,
const portal_msg_t *msg,
portal_resp_t *resp) {
portal_resp_set_status(resp, PORTAL_OK);
portal_resp_set_body(resp, "Native speed!\n", 14);
return 0;
}
int app_unload(portal_core_t *core) {
core->path_unregister(core, "/app/capp/fast");
return 0;
}
Ubicación de scripts: /var/lib/portal/<instance>/logic/<appname>/main.c
Pascal (mod_logic_pascal)
Los scripts en Pascal se compilan a partir de archivos fuente .pas usando Free Pascal Compiler (fpc 3.2.2) en el momento de la carga. El motor sigue el mismo patrón de dlopen que el motor de C — compilar a objeto compartido, cargar en el proceso y llamar a las funciones exportadas con la convención de llamada cdecl.
Pascal aporta la misma velocidad nativa que C con una seguridad de tipos más fuerte y una sintaxis familiar para un público diferente. La API de Portal está disponible a través de una unidad Pascal que replica la cabecera en C.
Ubicación de scripts: /var/lib/portal/<instance>/logic/<appname>/main.pas
El patrón de composición
El verdadero poder surge cuando todas las capas trabajan juntas. Una sola solicitud HTTP puede fluir a través de mod_web, hacia mod_logic, descender a un script Lua, que llama a mod_iot, que se comunica con hardware físico — todo mediante el mismo mecanismo de paso de mensajes:
Browser → GET /api/app/dashboard
→ mod_web (HTTP → mensaje Portal)
→ core enruta a mod_logic (posee el path)
→ mod_logic detecta que es una ruta Lua
→ envía mensaje a /logic_lua/functions/execute
→ La función Lua se ejecuta:
portal.get("/iot/resources/devices")
→ core enruta a mod_iot
→ mod_iot consulta Tapo vía KLAP
→ devuelve lista de dispositivos
→ Lua formatea la respuesta
→ fluye de vuelta como respuesta HTTP
Cada flecha en esa cadena es un mensaje Portal estándar. El script Lua no sabe cómo mod_iot se comunica con el hardware. mod_web no sabe que un script Lua gestionó la solicitud. Cada componente solo ve paths y mensajes.
portal.get('/nodeB/serial/com1/read') y leer de forma transparente un puerto serie físico en una máquina remota. El script Python no sabe nada de TCP, TLS ni de que nodeB es una máquina separada.
Gestión por CLI
La capa de lógica y cada motor exponen recursos estándar para la monitorización y depuración desde la CLI de Portal:
portal:/> get /logic/resources/status # Estado del framework
portal:/> get /logic/resources/routes # Todas las rutas registradas
portal:/> get /logic_lua/resources/status # Estado del motor Lua
portal:/> get /logic_python/resources/status # Estado del motor Python