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_huaweio bien:
pip install git+https://github.com/juaquicar/jmq_olt_huawei.gitPara 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 modoenableyconfig. -
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
""aptxyprx.
-
-
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 | NoneDado 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)→strCalcula 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 | NoneCrea unUdpTransportTargetconawait 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
\nsi detecta líneas conMore,---- MoreoPress '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/
{ "id": "0", "tipo": "GPBD", "ports": [ { "id": 0, "schema_fsp": "0/0/0", "optical_state": "Online", "port_state": "Online", "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": "-", "onts": [ { "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, "prx": -21.57 } ] }, { "id": 1, "schema_fsp": "0/0/1", "optical_state": "Offline", "port_state": "Offline", // …otros campos "onts": [] } // … más puertos ] }