17 — OLT Management¶
Overview¶
An OLT (Optical Line Terminal) is the fiber network head-end device — the ISP-side equipment in an FTTH (Fiber to the Home) deployment. The OLT manages all ONTs (Optical Network Terminals) — the fiber modems at customer premises. PyroRadius integrates with OLT devices via SNMP to poll ONT status, track fiber signal levels (Rx power), provision new ONTs, and automatically associate fiber ports with customer records.
Important: PyroRadius polls OLTs via SNMP (
snmpbulkwalk), not SSH or Telnet CLI. Do not configure SSH credentials on OLTs for PyroRadius — only the SNMP read community string is required.
Network Architecture¶
Internet / Core Network
│
▼
OLT Device (Huawei MA5600T)
│ SNMP (UDP 161)
│ ◄─────────── PyroRadius Server polls via snmpbulkwalk
│
│ Optical splitter (1:16 or 1:32)
├──────────────────────────────┐
│ │
▼ ▼
ONT/ONU (customer 1) ONT/ONU (customer 2)
Rx power: -22 dBm Rx power: -28 dBm
PPPoE → MikroTik NAS PPPoE → MikroTik NAS
olts Table Structure¶
| Column | Type | Description |
|---|---|---|
id |
bigint PK | Auto-increment |
name |
varchar | Human-readable label (e.g. "HAJI-PARK-OLT") |
alias |
varchar nullable | Short alias |
ip |
varchar | OLT management IP address |
location |
varchar nullable | Physical location description |
device_type |
varchar nullable | e.g. "Huawei MA5600T" |
snmp_community |
varchar | SNMP read community string (default: public) |
snmp_write_community |
varchar nullable | SNMP write community (for ONT actions) |
snmp_version |
varchar | SNMP version: 2c or 3 (default: 2c) |
enabled |
boolean | Whether this OLT is included in polling |
status |
varchar | online / offline (set by last poll result) |
poll_interval_min |
int | Minutes between polls (default: 3, minimum: 1) |
last_polled_at |
timestamp | When the last successful poll completed |
sys_name |
varchar nullable | OLT sysName from SNMP |
sys_descr |
varchar nullable | OLT sysDescr |
uptime_sec |
bigint nullable | OLT uptime in seconds |
temperature |
decimal nullable | OLT board temperature |
version |
varchar nullable | OLT firmware version |
created_at |
timestamp | |
updated_at |
timestamp |
ont_cache Table Structure¶
Stores the most recent ONT data polled from each OLT. Upserted on every poll (not appended). Stale ONTs (deregistered from OLT) are deleted after each poll.
| Column | Type | Description |
|---|---|---|
id |
bigint PK | |
olt_id |
bigint FK | Which OLT this ONT belongs to |
snmp_index |
varchar | Full SNMP index key: portIndex.ontId (e.g. 4194312192.0) |
frame |
int | GPON frame number (from ifIndex decode) |
slot |
int | GPON slot number |
port |
int | GPON port number |
ont_id |
int | ONT ID within the port (0-based) |
description |
varchar | ONT description — used to join with customer PPPoE username |
sn_hex |
varchar | Raw serial number as space-separated hex bytes |
sn_decoded |
varchar | Decoded SN (e.g. HWTC5443023E1E0C) — vendor prefix + hex |
port_alias |
varchar nullable | Port name (e.g. "3Rd Part TEST") from ifAlias |
vendor |
varchar nullable | First 4 bytes of SN decoded as ASCII (e.g. HWTC, FHTT, ZTEG) |
oper_status |
int nullable | ONT operational status code (1=Online, 2=Offline, etc.) |
config_status |
int nullable | ONT config state from OLT |
rx_raw |
int nullable | Raw Rx power (OLT-measured, ×100 — e.g. -2250 = -22.50 dBm) |
deact_cause |
int nullable | Last deactivation cause code |
mac_count |
int nullable | Number of MACs seen on this ONT |
last_up_at |
timestamp nullable | Last time ONT came online (parsed from SNMP DateAndTime) |
last_down_at |
timestamp nullable | Last time ONT went offline |
created_at |
timestamp | |
updated_at |
timestamp | When this row was last refreshed by a poll |
rx_raw Scale¶
rx_raw = -2250 → actual Rx power = -22.50 dBm (strong)
rx_raw = -2750 → actual Rx power = -27.50 dBm (marginal)
rx_raw = -3100 → actual Rx power = -31.00 dBm (weak)
rx_raw = 2147483647 → sentinel value — no optical module / not readable
Conversion: rx_power_dBm = rx_raw / 100
PollOltOnts Command¶
php artisan olt:poll-onts [--olt=ID]
Scheduled every 1 minute (scheduler level). Per-OLT skip logic prevents actual SNMP polling more often than poll_interval_min (default: 3 minutes). The --olt=ID flag forces an immediate poll of a single OLT bypassing the interval check.
SNMP OIDs Polled (Huawei MA5600T — enterprise OID 2011.6.128.1.1.2)¶
| Key | OID suffix | Description |
|---|---|---|
| desc | .43.1.9 |
ONT description (→ PPPoE username) |
| sn | .43.1.3 |
ONT serial number (hex) |
| oper | .46.1.15 |
Operational run state |
| cfg | .47.1.1 |
Configuration state |
| deact | .46.1.24 |
Last deactivation cause |
| mac | .46.1.21 |
Connected device MAC count |
| up | .46.1.22 |
Last up time (SNMP DateAndTime) |
| down | .46.1.23 |
Last down time (SNMP DateAndTime) |
| rx | .51.1.6 |
OLT-measured Rx power (primary) |
| rx_ddm | .51.1.4 |
ONT DDM Rx (fallback if .51.1.6 = sentinel) |
| ifAlias | 1.3.6.1.2.1.31.1.1.1.18 |
Port name (ifAlias) |
Execution Strategy¶
1. Launch 8 snmpbulkwalk processes IN PARALLEL (-Cr125, timeout 10s):
desc, sn, oper, cfg, deact, mac, up, down
2. Wait for all 8 to complete, collect output.
3. Run RX power walk SEQUENTIALLY (-Cr75, timeout 15s):
OLT cannot handle 9 concurrent SNMP sessions — RX runs alone.
~34 PDUs for 2531 ONTs at -Cr75.
4. For any ONT where RX = sentinel (2147483647):
Run a single snmpget for .51.1.4 to get DDM Rx (one round-trip for all sentinels).
5. Run snmpget for ifAlias of each unique port index (one PDU, all ports).
6. Decode each ONT's ifIndex → frame/slot/port.
Decode SN hex → vendor prefix + full SN string.
7. Upsert all rows into ont_cache (chunks of 200).
8. Delete stale ONTs (in DB but not in this poll's result).
9. Update olts.last_polled_at = now(), status = 'online'.
Performance (HAJI-PARK-OLT, 2531 ONTs)¶
| Setting | Poll Duration |
|---|---|
| Before (−Cr20) | ~490 seconds (~8 min) |
| After (−Cr75) | ~146 seconds (~2.5 min) |
Data freshness with poll_interval_min = 3: data is at most 5–6 minutes old (3 min wait + 2.5 min poll).
SyncOntAssignments Command¶
php artisan olt:sync-ont-assignments
Scheduled every 1 minute. Joins ont_cache with customers on PPPoE username = ONT description, then updates customer records with current signal data.
// Join logic
DB::table('ont_cache')
->join('customers', 'ont_cache.description', '=', 'customers.pppoe_username')
->whereNull('customers.deleted_at')
->select(['customers.id', 'customers.ont_serial', 'ont_cache.sn_decoded',
'ont_cache.rx_raw', 'ont_cache.port_alias', 'customers.rx_power'])
->get();
// Only writes if value changed (IS DISTINCT FROM guard — prevents lock contention)
if ($changed) {
Customer::where('id', $match->id)->update([
'ont_serial' => $newOntSerial,
'rx_power' => $match->rx_raw / 100,
'rx_power_at' => now(),
]);
}
OltController Routes¶
| Method | Route | Action |
|---|---|---|
| GET | /ont-list |
GPON Management page (React SPA) |
| GET | /ont-list/{olt}/data |
ONT list data for a specific OLT |
| GET | /ont-list/{olt}/ports |
PON port list from ont_cache |
| GET | /ont-list/{olt}/onts |
ONTs on a specific port |
| POST | /ont-list/{olt}/reboot-ont |
Reboot an ONT via SNMP write |
| POST | /ont-list/{olt}/ont-action |
Activate/Deactivate an ONT |
| POST | /ont-list/{olt}/bulk-ont-action |
Bulk action on multiple ONTs |
| GET | /ont-list/{olt}/autofind |
Scan for unregistered ONTs |
| GET | /ont-list/{olt}/provision-meta |
Get provisioning metadata |
| POST | /ont-list/{olt}/confirm-ont |
Register/provision an ONT |
| POST | /ont-list/{olt}/delete-ont |
Remove an ONT from OLT |
| POST | /ont-list/{olt}/change-profile |
Change ONT line profile |
| POST | /ont-list/{olt}/change-service-vlan |
Change service VLAN |
| POST | /ont-list/{olt}/set-pppoe |
Set PPPoE username on ONT |
| POST | /ont-list/{olt}/set-ont-desc |
Update ONT description |
| GET | /ont-list/{olt}/wlan-info |
Get Wi-Fi config from ONT |
| GET | /ont-list/{olt}/find-ont |
Find ONT by serial number |
| GET | /ont-list/unassigned-customers |
Customers without linked ONTs |
| POST | /olts/{olt}/poll |
Force immediate SNMP poll |
Rx Power Signal Quality Thresholds¶
| Range | Status | Color | Meaning |
|---|---|---|---|
| > −27 dBm | Good | Green | Healthy signal |
| −27 to −30 dBm | Warning | Yellow | Marginal; clean connectors |
| < −30 dBm | Critical | Red | Poor signal; failing fiber/connector |
| No reading | Unknown | Grey | ONT not seen in last poll |
Adding a New OLT¶
Prerequisites¶
- OLT must have SNMP v2c enabled with a read community string.
- UDP port 161 on the OLT must be reachable from the PyroRadius server (your-server-ip).
- For write operations (reboot, provision): also configure the SNMP write community.
Steps¶
- Navigate to Network → GPON Management.
- Click Add OLT.
- Fill in: Name, IP, SNMP Community, SNMP Version, Poll Interval.
- Save. The first automatic poll runs within
poll_interval_minminutes.
ifIndex Decoding (Huawei MA5600T)¶
The Huawei MA5600T encodes the GPON port into ifIndex using a proprietary scheme:
ifIndex = 0xFA00_{frame_byte}{slot_port_byte}00
frame = (ifIndex >> 16) & 0xFF
slot = ((ifIndex >> 8) & 0xFF) >> 5 (high 3 bits of byte 1)
port = ((ifIndex >> 8) & 0xFF) & 0x0F (low 4 bits of byte 1)
Example: ifIndex = 4194312192
- hex = 0xFA010800
- frame = 0x01 = 1
- slot_port_byte = 0x08 → slot = 0, port = 8
ONT SNMP index format: portIndex.ontId (e.g. 4194312192.5 = frame 1, port 8, ONT 5)
Unregistered ONTs¶
ONTs that appear in the OLT's SNMP walk (connected to the fiber) but have no matching customer in customers (by PPPoE username in description) appear in the Unregistered ONT tab.
These are ONTs that are: - Physically plugged in but not provisioned in PyroRadius - Provisioned under a different username (needs manual match) - Test or spare ONTs
Use the Unregistered ONT tab to assign them to existing customers or provision new ones.