Este manual explica cómo instalar desde cero un colector en Linux que:
- Lee datos de Opto22 groov EPIC por API REST (analogInputs y digitalInputs).
- Los guarda cada 2 segundos en MongoDB local.
- Corre automáticamente al iniciar con systemd.
- Incluye índices, TTL opcional, buenas prácticas y troubleshooting.
Probado el 22-08-2025 (America/Santiago). Asegúrate de usar JSON sin comentarios.
- Ubuntu/Debian con acceso a red LAN donde están los groov EPIC.
- Python 3 (con
venvypip) y privilegios para instalarmongodb-orgy crear un serviciosystemd.
Usa el repositorio oficial para obtener una versión reciente.
# Clave GPG y repo
curl -fsSL https://pgp.mongodb.com/server-8.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg --dearmor
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME)/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
# Instalar y habilitar
sudo apt update
sudo apt install -y mongodb-org
sudo systemctl enable --now mongod
# Verificar
mongosh --eval 'db.runCommand({ ping: 1 })'Notas
- Por defecto MongoDB escucha en
127.0.0.1(sólo local). - Warnings sobre XFS o autenticación son esperables; la sección Seguridad detalla cómo habilitar auth.
mkdir -p ~/groov_monitor && cd ~/groov_monitor
# Entorno virtual (evita conflictos/PEP 668)
sudo apt install -y python3-venv python3-pip
python3 -m venv .venv
source .venv/bin/activateCrea requirements.txt y luego instala:
cat > requirements.txt << 'EOF'
requests
pymongo
EOF
pip install -r requirements.txtSin comentarios (JSON puro). Ajusta IPs y API keys.
{
"devices": [
{
"ip": "192.168.100.11",
"api_key": "bie44GXYj2imJKnyUSxrMjafUiPjQBFM",
"endpoints": ["analogInputs", "digitalInputs"]
},
{
"ip": "192.168.100.12",
"api_key": "uWr2wd5hnL6JrHa9HHYwDCapQZvPsQKQ",
"endpoints": ["analogInputs", "digitalInputs"]
},
{
"ip": "192.168.100.14",
"api_key": "svKofyhEXmfZE9ethLFr423g4fFz2KgL",
"endpoints": ["analogInputs", "digitalInputs"]
}
],
"mongo": {
"uri": "mongodb://127.0.0.1:27017",
"db": "groov",
"collection": "measurements",
"ttl_days": 0
},
"sample_period_s": 2.0
}chmod 600 config.jsonGuarda lecturas cada ~2 s. Maneja NaN, booleanos, crea índices y soporta TTL si lo activas.
#!/usr/bin/env python3
import json, time, math, signal, sys
from datetime import datetime, timezone
from typing import Any, Dict, List
import requests
from pymongo import MongoClient, ASCENDING, DESCENDING
from pymongo.errors import PyMongoError
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
STOP = False
def handle_signal(signum, frame):
global STOP
STOP = True
for s in (signal.SIGINT, signal.SIGTERM):
signal.signal(s, handle_signal)
def load_cfg(path="config.json"):
with open(path, "r", encoding="utf-8") as fh:
return json.load(fh)
def ensure_indexes(coll, ttl_days: int):
coll.create_index(
[("device_ip", ASCENDING), ("endpoint", ASCENDING), ("name", ASCENDING), ("ts", DESCENDING)],
name="by_device_ep_name_ts"
)
if ttl_days and ttl_days > 0:
seconds = int(ttl_days * 86400)
coll.create_index("ts", name="ts_ttl", expireAfterSeconds=seconds)
def as_bool(v: Any):
if isinstance(v, bool):
return v, v
if isinstance(v, str):
low = v.strip().lower()
if low in ("true", "1", "on", "si", "sí"):
return True, v
if low in ("false", "0", "off", "no"):
return False, v
return None, v
def as_float_or_nan(v: Any):
if isinstance(v, (int, float)):
if isinstance(v, float) and (math.isnan(v) or v != v):
return None, v, True
return float(v), v, False
if isinstance(v, str):
if v.strip().lower() == "nan":
return None, v, True
try:
return float(v), v, False
except Exception:
return None, v, False
return None, v, False
def fetch_one(ip: str, api_key: str, endpoint: str, timeout=6):
url = f"https://{ip}/pac/device/strategy/ios/{endpoint}"
r = requests.get(url, headers={"apiKey": api_key}, verify=False, timeout=timeout)
r.raise_for_status()
data = r.json()
if not isinstance(data, list):
raise ValueError(f"Respuesta inesperada {ip} {endpoint}")
return data
def main():
cfg = load_cfg()
devices = cfg["devices"]
mongo_uri = cfg["mongo"]["uri"]
db_name = cfg["mongo"]["db"]
coll_name = cfg["mongo"]["collection"]
ttl_days = int(cfg["mongo"].get("ttl_days", 0))
period = float(cfg.get("sample_period_s", 2.0))
client = MongoClient(mongo_uri, appname="groov-collector")
db = client[db_name]
coll = db[coll_name]
ensure_indexes(coll, ttl_days)
print(f"[OK] Conectado a MongoDB en {mongo_uri}, db={db_name}, coll={coll_name}")
while not STOP:
t0 = time.monotonic()
ts = datetime.now(timezone.utc)
bulk_docs: List[Dict[str, Any]] = []
for dev in devices:
ip = dev["ip"]
key = dev["api_key"]
endpoints = dev.get("endpoints", ["analogInputs", "digitalInputs"])
for ep in endpoints:
try:
items = fetch_one(ip, key, ep)
except Exception as e:
print(f"[WARN] Falló lectura {ip} {ep}: {e}")
continue
for it in items:
name = it.get("name", "")
raw_value = it.get("value", None)
doc: Dict[str, Any] = {"ts": ts,"device_ip": ip,"endpoint": ep,"name": name}
if ep == "digitalInputs":
val_bool, raw = as_bool(raw_value)
doc.update({"value": val_bool,"raw_value": raw,"type": "digital"})
else:
val_float, raw, is_nan = as_float_or_nan(raw_value)
doc.update({"value": val_float,"raw_value": raw,"is_nan": is_nan,"type": "analog"})
bulk_docs.append(doc)
if bulk_docs:
try:
coll.insert_many(bulk_docs, ordered=False)
print(f"[OK] Insertados {len(bulk_docs)} docs @ {ts.isoformat()}")
except PyMongoError as e:
print(f"[ERROR] insert_many: {e}")
elapsed = time.monotonic() - t0
sleep_s = period - elapsed
if sleep_s > 0:
time.sleep(sleep_s)
print("[INFO] Saliendo limpio.")
if __name__ == "__main__":
main()chmod +x colectar_groov_mongo.py# Ejecutar en foreground
source .venv/bin/activate
python3 colectar_groov_mongo.pyEn otra terminal:
mongosh
use groov
show collections
db.measurements.countDocuments()
db.measurements.find().sort({ ts: -1 }).limit(5).pretty()
db.measurements.find({ endpoint: "digitalInputs", value: true }).sort({ ts: -1 }).limit(10).pretty()Crea /etc/systemd/system/groov_collector.service:
[Unit]
Description=Groov EPIC -> MongoDB Collector
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=marco
WorkingDirectory=/home/marco/groov_monitor
ExecStart=/home/marco/groov_monitor/.venv/bin/python /home/marco/groov_monitor/colectar_groov_mongo.py
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.targetHabilitar y verificar:
sudo systemctl daemon-reload
sudo systemctl enable --now groov_collector
systemctl status groov_collector --no-pager
journalctl -u groov_collector -fEjemplo de log correcto
[OK] Conectado a MongoDB en mongodb://127.0.0.1:27017, db=groov, coll=measurements
[OK] Período de muestreo: 2.0 s | TTL días: 0
[OK] Insertados 247 docs @ 2025-08-22T17:55:18.078511+00:00
...
Si quieres usuario/clave para MongoDB local:
mongosh
use admin
db.createUser({ user: "admin", pwd: "UnaClaveFuerte", roles: [ { role: "root", db: "admin" } ] })
# Edita /etc/mongod.conf y agrega:
# security:
# authorization: enabled
sudo systemctl restart mongod
# Crea usuario específico para DB groov
mongosh -u admin -p --authenticationDatabase admin
use groov
db.createUser({ user: "groovuser", pwd: "OtraClaveFuerte", roles: [ { role: "readWrite", db: "groov" } ] })Actualiza config.json del colector:
"mongo": {
"uri": "mongodb://groovuser:OtraClaveFuerte@127.0.0.1:27017/?authSource=groov",
"db": "groov",
"collection": "measurements",
"ttl_days": 0
}Reinicia el servicio:
sudo systemctl restart groov_collectorPara retener, por ejemplo, 30 días cambia en config.json:
"ttl_days": 30El script creará un índice TTL sobre ts y MongoDB borrará automáticamente lo más antiguo.
JSONDecodeErroral leer config → Verifica queconfig.jsonno tenga comentarios (//o/* ... */).- Timeout/SSL → El script usa
verify=Falsepor certificados propios; revisa conectividad y firewall. countDocuments() == 0→ Verifica logs conjournalctl -u groov_collector -f. Pruebacurl -ka cada endpoint con suapiKey.- Servicio no arranca → Revisa
User=y rutas en el service. Ejecuta manual:source .venv/bin/activate && python3 colectar_groov_mongo.py. - Latencia > 2 s → La pausa se ajusta dinámicamente. Si el ciclo supera 2 s, considera paralelizar lecturas o aumentar
sample_period_s.
# Ver estado y logs
systemctl status groov_collector --no-pager
journalctl -u groov_collector -f
# Detener / Iniciar / Reiniciar
sudo systemctl stop groov_collector
sudo systemctl start groov_collector
sudo systemctl restart groov_collector
# Consultas en MongoDB
mongosh --eval 'db.getSiblingDB("groov").measurements.countDocuments()'
mongosh
use groov
db.measurements.find().sort({ ts:-1 }).limit(5).pretty()# Detener servicio
sudo systemctl disable --now groov_collector
sudo rm -f /etc/systemd/system/groov_collector.service
sudo systemctl daemon-reload
# Borrar datos y proyecto (cuidado)
mongosh --eval 'db.getSiblingDB("groov").dropDatabase()'
rm -rf ~/groov_monitor~/groov_monitor/
├─ .venv/
├─ requirements.txt
├─ config.json
└─ colectar_groov_mongo.py
/etc/systemd/system/groov_collector.service
¡Listo! Con esto tendrás un colector robusto que arranca con el sistema y almacena datos del groov EPIC en MongoDB de forma continua.