Paquete de integración con OLTs Huawei MA56XXT (como MA5603T) vía Telnet y SNMP asíncrono, diseñado para automatizar la recolección de información GPON desde Python.
Permite conectarse a una OLT Huawei MA56XXT y ejecutar operaciones como:
- Listar slots activos (
display board 0
) - Consultar puertos GPON por slot
- Obtener ONTs conectadas a cada puerto, incluyendo:
- Potencia TX (ptx) y RX (prx) vía SNMP asíncrono
- Realizar un escaneo completo (slots → puertos → ONTs)
- Manejar paginación, prompts dinámicos y errores comunes de sesión (bloqueo de usuario)
Requiere Python ≥ 3.7 y PySNMP ≥ 6.2 (para usar el HLAPI asíncrono).
pip install jmq_olt_huawei
o bien:
pip install git+https://github.com/juaquicar/jmq_olt_huawei.git
Para desarrollo en modo editable:
pip install -e .
jmq_olt_huawei/
│
├── ma56xxt.py # Lógica principal de conexión Telnet y SNMP asíncrono
├── __init__.py # Archivo de inicialización del paquete
├── Examples/ # Scripts de ejemplo (opcional)
├── tests/ # Pruebas automatizadas (pytest)
├── requirements.txt # Dependencias de desarrollo / CI
├── pyproject.toml # Configuración de build
├── LICENSE # Licencia MIT
└── README.md # Este archivo
A continuación se muestra un ejemplo mínimo de cómo usar APIMA56XXT
con llamadas asíncronas para obtener la lista completa de ONTs y sus potencias SNMP.
import asyncio
from jmq_olt_huawei.ma56xxt import APIMA56XXT, UserBusyError
from pprint import pprint
async def main():
api = APIMA56XXT(
host='192.168.88.25',
user='root',
password='admin',
prompt='MA5603T',
snmp_ip='192.168.88.25',
snmp_port=161,
snmp_community='public',
debug=True
)
try:
# Conexión Telnet (síncrona)
api.connect()
# Escaneo completo (async)
full = await api.scan_all()
pprint(full)
except UserBusyError as e:
print(f"ERROR: {e}")
finally:
api.close() # Cierre de Telnet
if __name__ == '__main__':
asyncio.run(main())
-
api.connect()
abre una sesión Telnet, hace login, entra en modoenable
yconfig
. -
await api.scan_all()
recorre:-
get_slots()
(síncrono, Telnet) -
Para cada slot tipo
GPBD
:get_ports(slot)
(síncrono) -
Para cada puerto con
optical_state == 'Online'
:await get_onts(slot, port_id)
(asíncrono)- Cada ONT “online” dispara consultas SNMP asíncronas a
await _snmp_potencia_tx(...)
yawait _snmp_potencia_rx(...)
.
- Cada ONT “online” dispara consultas SNMP asíncronas a
-
-
Devuelve una lista jerárquica de slots → puertos → ONTs con campos SNMP (
ptx
,prx
).
1. __init__(host, user, password, prompt, snmp_ip, snmp_port, snmp_community, timeout=2.0, debug=False)
Inicializa la clase:
host
(str): IP o hostname de la OLT.user
(str): Usuario Telnet.password
(str): Contraseña Telnet.prompt
(str): Prefijo del prompt (p.ej."MA5603T"
).snmp_ip
(str): IP de la OLT para SNMP.snmp_port
(int): Puerto SNMP (por defecto 161).snmp_community
(str): Comunidad SNMP (p.ej."public"
).timeout
(float): Segundos de espera en lecturas Telnet (por defecto 2.0).debug
(bool): SiTrue
, imprime logs de depuración.
api = APIMA56XXT(
host='192.168.88.25',
user='root',
password='admin',
prompt='MA5603T',
snmp_ip='192.168.88.25',
snmp_port=161,
snmp_community='public',
timeout=2.0,
debug=True
)
Abre sesión Telnet, hace login y entra en modo operativo:
api.connect()
# Si debug=True:
# [DEBUG] Conectando a 192.168.88.25
# Conectado en modo config
- No devuelve valor.
- Lanza excepción (
EOFError
,socket.error
) si la conexión falla. - Después de llamar, se puede invocar
get_slots()
,get_ports()
, etc.
Sale de los modos CLI (config
, enable
) y cierra la sesión Telnet:
api.disconnect()
# Si debug=True imprime:
# [DEBUG] Desconectado
- Protege contra llamadas dobles (si
self.tn is None
, no hace nada).
Obtiene los slots en la OLT con display board 0
:
slots = api.get_slots()
# Ejemplo:
# [('0', 'GPBD'), ('6', 'SCUN'), ('7', 'SCUN'), ('9', 'GICF')]
-
Ejecuta internamente:
_send('display board 0')
_read_until_prompt()
- Parsea cada línea para extraer
(slot_id, tipo)
.
-
Es método síncrono (no necesita
await
).
Obtiene la lista de puertos GPON de un slot específico:
ports = api.get_ports('0')
# Ejemplo de un diccionario en la lista:
# {
# 'id': 0,
# 'schema_fsp': '0/0/0',
# 'optical_state': 'Online',
# 'port_state': 'Offline',
# 'laser_state': 'Normal',
# 'bw': '1239040',
# 'temperature': '35',
# 'tx_bias': '12',
# 'voltage': '3.22',
# 'tx_power': '3.72',
# 'illegal_rogue_ont': 'Inexistent',
# 'max_distance': '40',
# 'wave_length': '1490',
# 'fiber_type': 'Single Mode',
# 'length': '-'
# }
-
Paso a paso:
_send(f'interface gpon 0/{slot}')
_read_until_prompt()
_send('display port state all')
_read_until_prompt()
- Parsea bloques con
_parse_port_block
. _send('quit')
+_read_until_prompt()
-
Retorna lista de diccionarios, uno por puerto.
-
Método síncrono (no usa
await
).
Devuelve la lista de ONTs conectadas a un puerto GPON:
onts = await api.get_onts('0', 0)
# Ejemplo de un diccionario ONT:
# {
# 'id': 2,
# 'schema_fsp': '0/0/0',
# 'sn': '48575443E010C4AC',
# 'control_flag': 'active',
# 'run_state': 'online',
# 'config_state': 'normal',
# 'match_state': 'match',
# 'protect_side': 'no',
# 'description': 'Cliente01',
# 'ptx': 2.34, # Potencia TX (mW), float o "" si no está disponible
# 'prx': -21.57 # Potencia RX (dBm), float o "" si no está disponible
# }
-
Internamente:
-
_send(f'interface gpon 0/{slot}')
(Telnet) +_read_until_prompt()
-
_send(f'display ont info {port_id} all')
+_read_until_prompt()
-
_send('quit')
+_read_until_prompt()
-
Parsea sección principal y descripción con
_parse_onts
. -
Para cada ONT con
run_state.lower() == 'online'
:- Llama a
await _snmp_potencia_tx(slot, port_id, ont_id)
- Llama a
await _snmp_potencia_rx(slot, port_id, ont_id)
- Llama a
-
Si SNMP falla, asigna
""
aptx
yprx
.
-
-
Debe invocarse con
await
. -
Retorna lista de diccionarios ONT, incluyendo campos SNMP (
ptx
,prx
).
Realiza un escaneo completo de la OLT:
-
Obtiene todos los slots vía
get_slots()
(síncrono). -
Para cada slot de tipo
GPBD
:-
Llama a
get_ports(slot)
(síncrono). -
Para cada puerto donde
optical_state.lower() == 'online'
:- Llama a
await get_onts(slot, port_id)
(asíncrono).
- Llama a
-
Agrega un campo
'onts'
a cada puerto (lista vacía si estáoffline
).
-
-
Devuelve lista de diccionarios, cada uno así:
- Debe invocarse con
await api.scan_all()
. - Incluye SNMP asíncrono para ONTs “online”.
-
_read_until_prompt()
Lee líneas desde Telnet hasta detectar el prompt dinámico. Atiende paginación (More
,---- More
) y errores de login acumulado (UserBusyError
). -
_parse_port_block(slot, bloque)
→dict | None
Dado un bloque de texto dedisplay port state all
, extrae campos comoid
,optical_state
,laser_state
,bw
, etc. -
_parse_onts(raw, slot, port_id)
→List[dict]
Dada la salida dedisplay ont info {port_id} all
, extrae la tabla de ONTs y sus descripciones. Devuelve lista de dicts conid
,sn
,run_state
, etc. -
_calcular_codigo(slot, pon)
→str
Calcula la parte numérica base del OID SNMP:base = 4194304000 código = base + pon_int * 256 + slot_int * 8192 devuelve "<código>."
-
“Helpers” SNMP asíncronos (
async def
)_consultagetsnmp(codigo_oid: str) → int | None
Crea unUdpTransportTarget
conawait UdpTransportTarget.create((ip, port), timeout=1, retries=0)
, luego haceawait get_cmd(...)
._snmp_potencia_tx(slot, port, ont) → float | ""
Llama aawait _consultagetsnmp(...)
con OID para potencia TX y procesa la respuesta (mW)._snmp_potencia_rx(slot, port, ont) → float | ""
Similar para potencia RX (dBm).
Para ejecutar pruebas unitarias:
pytest -s tests/test_ma56xxt.py
- Asegúrate de tener un entorno de testing adecuado o mocks para Telnet/SNMP.
- Los tests deben validar parsing de puertos, ONTs y comportamiento SNMP asíncrono.
- Prompt dinámico configurable
Detecta automáticamente prompts como
MA5603T>
,MA5603T(config)#
, etc. - Manejo de paginación
Envía
\n
si detecta líneas conMore
,---- More
oPress 'Q'
. - Bloqueo de usuario
Si la OLT informa “Reenter times have reached the upper limit”, lanza
UserBusyError
. - SNMP asíncrono (PySNMP ≥ 6.2) Obtiene potencias TX/RX de cada ONT “online” de forma concurrente sin bloquear la lectura Telnet.
- Escaneo jerárquico (slots → puertos → ONTs)
Permite obtener toda la información en una sola llamada a
await scan_all()
.
-
Haz un fork del repositorio.
-
Crea una rama:
git checkout -b feature/nombre-de-funcionalidad
-
Realiza tus cambios, añade tests y actualiza este README si es necesario.
-
Haz commit y push a tu rama.
-
Abre un Pull Request describiendo los cambios propuestos.
MIT © Juanma Quijada
- Homepage / GitHub: https://github.com/juaquicar/jmq_olt_huawei
- PyPI: https://pypi.org/project/jmq-olt-huawei/