diff --git a/lib/towerops/snmp/profiles/vendors/airfiber.ex b/lib/towerops/snmp/profiles/vendors/airfiber.ex index 47ba7133..7dd51a21 100644 --- a/lib/towerops/snmp/profiles/vendors/airfiber.ex +++ b/lib/towerops/snmp/profiles/vendors/airfiber.ex @@ -2,17 +2,19 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airfiber do @moduledoc """ Ubiquiti AirFiber device-specific SNMP handling. - Supports AirFiber 5, 5X, 5XHD, 24, 24HD, and 60 series. + Supports AirFiber 5, 5X, 5XHD, 24, 24HD, 60 series, and LTU series. Supported devices: - AirFiber 5/5X/5XHD - AirFiber 24/24HD - AirFiber 60/60 LR - AirFiber 60 XG + - AirFiber LTU (LTU Rocket, LTU-PRO, etc.) """ @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + alias Towerops.Snmp.Client alias Towerops.Snmp.Profiles.Vendors.Vendor @impl true @@ -28,13 +30,27 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airfiber do @impl true def discover_wireless_sensors(client_opts) do - Vendor.fetch_sensors(wireless_oid_defs(), client_opts) + # Detect if this is an LTU device by checking for LTU-specific OID + case Client.get(client_opts, "1.3.6.1.4.1.41112.1.10.1.2.2.0") do + {:ok, _frequency} -> + # LTU device detected - use AFLTU MIB OIDs + Vendor.fetch_sensors(ltu_wireless_oid_defs(), client_opts) + + {:error, _} -> + # Regular AirFiber device - use AirFIBER MIB OIDs + Vendor.fetch_sensors(airfiber_wireless_oid_defs(), client_opts) + end end @impl true def wireless_oid_defs do - # UBNT-AirFIBER-MIB OIDs - # Enterprise OID: 1.3.6.1.4.1.41112.1.3 + # Default to regular AirFiber OIDs for backward compatibility + airfiber_wireless_oid_defs() + end + + # UBNT-AirFIBER-MIB OIDs for regular AirFiber devices + # Enterprise OID: 1.3.6.1.4.1.41112.1.3 + defp airfiber_wireless_oid_defs do [ # TX Frequency (MHz) %{ @@ -94,4 +110,59 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airfiber do } ] end + + # UBNT-AFLTU-MIB OIDs for AirFiber LTU devices + # Enterprise OID: 1.3.6.1.4.1.41112.1.10 + defp ltu_wireless_oid_defs do + [ + # Frequency (MHz) - Configuration value + %{ + oid: "1.3.6.1.4.1.41112.1.10.1.2.2.0", + sensor_type: "frequency", + sensor_descr: "Frequency", + sensor_unit: "MHz", + sensor_divisor: 1 + }, + # TX Power (dBm EIRP) + %{ + oid: "1.3.6.1.4.1.41112.1.10.1.2.6.0", + sensor_type: "power", + sensor_descr: "Tx EIRP", + sensor_unit: "dBm", + sensor_divisor: 1 + }, + # Station RX Power Chain 0 (dBm) - uses station table index 1 + %{ + oid: "1.3.6.1.4.1.41112.1.10.1.4.1.5.1", + sensor_type: "rssi", + sensor_descr: "Rx Power Chain 0", + sensor_unit: "dBm", + sensor_divisor: 1 + }, + # Station RX Power Chain 1 (dBm) - uses station table index 1 + %{ + oid: "1.3.6.1.4.1.41112.1.10.1.4.1.6.1", + sensor_type: "rssi", + sensor_descr: "Rx Power Chain 1", + sensor_unit: "dBm", + sensor_divisor: 1 + }, + # Station TX Capacity (Kbps) - convert to Mbps by dividing by 1000 + %{ + oid: "1.3.6.1.4.1.41112.1.10.1.4.1.3.1", + sensor_type: "rate", + sensor_descr: "Tx Capacity", + sensor_unit: "Mbps", + sensor_divisor: 1000 + }, + # Station RX Capacity (Kbps) - convert to Mbps by dividing by 1000 + %{ + oid: "1.3.6.1.4.1.41112.1.10.1.4.1.4.1", + sensor_type: "rate", + sensor_descr: "Rx Capacity", + sensor_unit: "Mbps", + sensor_divisor: 1000 + } + ] + end end diff --git a/lib/towerops_web/live/device_live/form.ex b/lib/towerops_web/live/device_live/form.ex index fb9054ab..1c13b82d 100644 --- a/lib/towerops_web/live/device_live/form.ex +++ b/lib/towerops_web/live/device_live/form.ex @@ -111,7 +111,7 @@ defmodule ToweropsWeb.DeviceLive.Form do # Add agent_token_id to the changeset data device_with_agent = Map.put(device, :agent_token_id, agent_token_id) - changeset = Devices.change_device(device, %{}) + changeset = Devices.change_device(device_with_agent, %{}) # Get effective SNMP configuration and source snmp_config = Devices.get_snmp_config(device) diff --git a/priv/static/changelog.txt b/priv/static/changelog.txt index 956f6d80..c6521a2c 100644 --- a/priv/static/changelog.txt +++ b/priv/static/changelog.txt @@ -1,5 +1,12 @@ +Devices Working +* Mikrotik RouterOS +* Ubiquiti AC, LTU, AirFiber + +2025-01-27 +* Infrastructure improvements +* Bug fix: poller agent assignment on devices + 2025-01-26 * Cloud poller improvements * Accessability improved * Added initial help documentation - diff --git a/test/towerops/snmp/profiles/vendors/airfiber_test.exs b/test/towerops/snmp/profiles/vendors/airfiber_test.exs index 61e143f9..4a731627 100644 --- a/test/towerops/snmp/profiles/vendors/airfiber_test.exs +++ b/test/towerops/snmp/profiles/vendors/airfiber_test.exs @@ -61,9 +61,14 @@ defmodule Towerops.Snmp.Profiles.Vendors.AirfiberTest do end describe "discover_wireless_sensors/1" do - test "discovers sensors when SNMP responds" do - expect(SnmpMock, :get, 7, fn _, oid, _ -> + test "discovers sensors from regular AirFiber device" do + # First call checks for LTU device (returns error = not LTU) + # Then 7 calls for regular AirFiber sensors + expect(SnmpMock, :get, 8, fn _target, oid, _opts -> cond do + # LTU detection OID (should fail for regular AirFiber) + String.contains?(oid, "41112.1.10.1.2.2.0") -> {:error, :no_such_object} + # Regular AirFiber OIDs (should succeed) String.contains?(oid, "41112.1.3.2.1.6.1") -> {:ok, 5800} String.contains?(oid, "41112.1.3.2.1.7.1") -> {:ok, 5800} String.contains?(oid, "41112.1.3.2.1.8.1") -> {:ok, 25} @@ -79,11 +84,66 @@ defmodule Towerops.Snmp.Profiles.Vendors.AirfiberTest do assert is_list(sensors) assert length(sensors) == 7 + + # Verify regular AirFiber OIDs were used + freq_sensors = Enum.filter(sensors, &(&1.sensor_type == "frequency")) + assert length(freq_sensors) == 2 end - test "returns empty list when no sensors respond" do - expect(SnmpMock, :get, 7, fn _, _, _ -> - {:error, :no_such_object} + test "discovers sensors from LTU device with correct OIDs" do + # First call detects LTU device (returns frequency) + # Then 6 calls for LTU-specific sensors + expect(SnmpMock, :get, 7, fn _target, oid, _opts -> + cond do + # LTU detection OID (should succeed for LTU) + String.contains?(oid, "41112.1.10.1.2.2.0") -> {:ok, 5800} + # LTU-specific OIDs + String.contains?(oid, "41112.1.10.1.2.6.0") -> {:ok, 25} + String.contains?(oid, "41112.1.10.1.4.1.5.1") -> {:ok, -55} + String.contains?(oid, "41112.1.10.1.4.1.6.1") -> {:ok, -56} + String.contains?(oid, "41112.1.10.1.4.1.3.1") -> {:ok, 450_000} + String.contains?(oid, "41112.1.10.1.4.1.4.1") -> {:ok, 450_000} + true -> {:error, :no_such_object} + end + end) + + sensors = Airfiber.discover_wireless_sensors(@client_opts) + + assert is_list(sensors) + assert length(sensors) == 6 + + # Verify LTU OIDs were used + rate_sensors = Enum.filter(sensors, &(&1.sensor_type == "rate")) + assert length(rate_sensors) == 2 + # Verify the divisor is set correctly (division happens at display time) + assert Enum.all?(rate_sensors, &(&1.sensor_divisor == 1000)) + # Raw value should be 450000 Kbps (not divided during discovery) + assert Enum.all?(rate_sensors, &(&1.last_value == 450_000.0)) + end + + test "returns empty list when LTU device has no active stations" do + # LTU detected but no station data available + expect(SnmpMock, :get, 7, fn _target, oid, _opts -> + # LTU detection succeeds + if String.contains?(oid, "41112.1.10.1.2.2.0") do + {:ok, 5800} + else + # But station OIDs fail (no connected stations) + {:error, :no_such_object} + end + end) + + sensors = Airfiber.discover_wireless_sensors(@client_opts) + + # Frequency sensor should still be discovered + assert is_list(sensors) + assert length(sensors) == 1 + assert List.first(sensors).sensor_type == "frequency" + end + + test "returns empty list when device does not respond" do + expect(SnmpMock, :get, 8, fn _target, _oid, _opts -> + {:error, :timeout} end) sensors = Airfiber.discover_wireless_sensors(@client_opts)