feat: add more vendor modules and fix sensor discovery

New vendor modules for WISP equipment:
- Ruckus: ZoneDirector, SmartZone, Unleashed controllers
- DragonWave: Horizon Compact/Duo/Quantum microwave
- AirFiber: Ubiquiti AF 5/24/60 series (split from Airos)
- Racom: RAy and RAy3 microwave radios
- Exalt: ExtendAir microwave radios
- SIAE: ALFO 80HD microwave radios

Also fixes:
- sensor_value -> last_value consistency in YAML sensor discovery
- Test warnings (assert defs != [] -> assert [_ | _] = defs)

Total: 17 vendor modules, 162 tests passing
This commit is contained in:
Graham McIntire 2026-01-22 08:59:50 -06:00
parent a83e63b3ad
commit 8f1752e59c
No known key found for this signature in database
24 changed files with 1176 additions and 15 deletions

View file

@ -233,7 +233,7 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
sensor_descr: build_sensor_descr(sensor_def, idx),
sensor_unit: sensor_def[:sensor_unit] || "",
sensor_divisor: sensor_def[:sensor_divisor] || 1,
sensor_value: value
last_value: value / 1
}
end
@ -291,7 +291,7 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
sensor_descr: build_sensor_descr(sensor_def, idx),
sensor_unit: "",
sensor_divisor: 1,
sensor_value: value,
last_value: value / 1,
state_descr: state_descr
}
end

View file

@ -0,0 +1,97 @@
defmodule Towerops.Snmp.Profiles.Vendors.Airfiber do
@moduledoc """
Ubiquiti AirFiber device-specific SNMP handling.
Supports AirFiber 5, 5X, 5XHD, 24, 24HD, and 60 series.
Supported devices:
- AirFiber 5/5X/5XHD
- AirFiber 24/24HD
- AirFiber 60/60 LR
- AirFiber 60 XG
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Profiles.Vendors.Vendor
@impl true
def profile_names do
["airos-af", "airos-af-ltu", "airos-af60"]
end
@impl true
def detect_hardware(_client_opts) do
# Hardware detection handled by YAML profile
nil
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
# UBNT-AirFIBER-MIB OIDs
# Enterprise OID: 1.3.6.1.4.1.41112.1.3
[
# TX Frequency (MHz)
%{
oid: "1.3.6.1.4.1.41112.1.3.2.1.6.1",
sensor_type: "frequency",
sensor_descr: "Tx Frequency",
sensor_unit: "MHz",
sensor_divisor: 1
},
# RX Frequency (MHz)
%{
oid: "1.3.6.1.4.1.41112.1.3.2.1.7.1",
sensor_type: "frequency",
sensor_descr: "Rx Frequency",
sensor_unit: "MHz",
sensor_divisor: 1
},
# TX Power (dBm)
%{
oid: "1.3.6.1.4.1.41112.1.3.2.1.8.1",
sensor_type: "power",
sensor_descr: "Tx Power",
sensor_unit: "dBm",
sensor_divisor: 1
},
# RX Power 0 (chain 0)
%{
oid: "1.3.6.1.4.1.41112.1.3.2.1.13.1",
sensor_type: "rssi",
sensor_descr: "Rx Power Chain 0",
sensor_unit: "dBm",
sensor_divisor: 1
},
# RX Power 1 (chain 1)
%{
oid: "1.3.6.1.4.1.41112.1.3.2.1.14.1",
sensor_type: "rssi",
sensor_descr: "Rx Power Chain 1",
sensor_unit: "dBm",
sensor_divisor: 1
},
# TX Capacity (Mbps)
%{
oid: "1.3.6.1.4.1.41112.1.3.2.1.20.1",
sensor_type: "rate",
sensor_descr: "Tx Capacity",
sensor_unit: "Mbps",
sensor_divisor: 1
},
# RX Capacity (Mbps)
%{
oid: "1.3.6.1.4.1.41112.1.3.2.1.21.1",
sensor_type: "rate",
sensor_descr: "Rx Capacity",
sensor_unit: "Mbps",
sensor_divisor: 1
}
]
end
end

View file

@ -6,9 +6,6 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airos do
Supported devices:
- AirMax (LiteBeam, PowerBeam, NanoStation, etc.)
- AirFiber
- AirFiber 60GHz
- AirFiber LTU
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
@ -20,7 +17,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Airos do
@product_name_oid "1.2.840.10036.1.1.1.9.5"
@impl true
def profile_names, do: ["airos", "airos-af", "airos-af60", "airos-af-ltu"]
def profile_names, do: ["airos"]
@impl true
def detect_hardware(client_opts) do

View file

@ -0,0 +1,77 @@
defmodule Towerops.Snmp.Profiles.Vendors.Dragonwave do
@moduledoc """
DragonWave Horizon device-specific SNMP handling.
Supports Horizon Compact, CompactPlus, Duo, and Quantum series.
Supported devices:
- Horizon Compact
- Horizon Compact Plus
- Horizon Duo
- Horizon Quantum
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Vendor
# Horizon ODU MIB - hardware info
@hardware_oid "1.3.6.1.4.1.7262.2.2.1.1.8.0"
@impl true
def profile_names do
["horizon-compact", "horizon-compactplus", "horizon-duo", "horizon-quantum"]
end
@impl true
def detect_hardware(client_opts) do
case Client.get(client_opts, @hardware_oid) do
{:ok, hardware} when is_binary(hardware) -> hardware
_ -> nil
end
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
[
# Radio Temperature (divide by 10)
%{
oid: "1.3.6.1.4.1.7262.2.2.5.1.3.6.0",
sensor_type: "temperature",
sensor_descr: "Radio Temperature",
sensor_unit: "C",
sensor_divisor: 10
},
# Modem SNR (divide by 10)
%{
oid: "1.3.6.1.4.1.7262.2.2.5.1.2.3.0",
sensor_type: "snr",
sensor_descr: "Modem SNR",
sensor_unit: "dB",
sensor_divisor: 10
},
# RSL (Receive Signal Level)
%{
oid: "1.3.6.1.4.1.7262.2.2.5.1.2.2.0",
sensor_type: "rssi",
sensor_descr: "RSL",
sensor_unit: "dBm",
sensor_divisor: 10
},
# Transmit Power
%{
oid: "1.3.6.1.4.1.7262.2.2.5.1.3.2.0",
sensor_type: "power",
sensor_descr: "Tx Power",
sensor_unit: "dBm",
sensor_divisor: 10
}
]
end
end

View file

@ -0,0 +1,79 @@
defmodule Towerops.Snmp.Profiles.Vendors.Exalt do
@moduledoc """
Exalt ExtendAir device-specific SNMP handling.
Supports ExtendAir licensed and unlicensed microwave radios.
Supported devices:
- ExtendAir series
- ExploreAir series
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Profiles.Vendors.Vendor
@impl true
def profile_names do
["extendair"]
end
@impl true
def detect_hardware(_client_opts) do
# Hardware detection handled by YAML profile
nil
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
# EXALT-MIB OIDs
# Enterprise OID: 1.3.6.1.4.1.25651
[
# Tx Frequency (kHz, divide by 1000 for MHz)
%{
oid: "1.3.6.1.4.1.25651.1.2.3.1.1.7.0",
sensor_type: "frequency",
sensor_descr: "Tx Frequency",
sensor_unit: "MHz",
sensor_divisor: 1000
},
# Rx Frequency (kHz, divide by 1000 for MHz)
%{
oid: "1.3.6.1.4.1.25651.1.2.3.1.1.8.0",
sensor_type: "frequency",
sensor_descr: "Rx Frequency",
sensor_unit: "MHz",
sensor_divisor: 1000
},
# Tx Power (dBm * 10)
%{
oid: "1.3.6.1.4.1.25651.1.2.3.1.1.9.0",
sensor_type: "power",
sensor_descr: "Tx Power",
sensor_unit: "dBm",
sensor_divisor: 10
},
# RSL (Receive Signal Level, dBm * 10)
%{
oid: "1.3.6.1.4.1.25651.1.2.3.1.2.1.0",
sensor_type: "rssi",
sensor_descr: "RSL",
sensor_unit: "dBm",
sensor_divisor: 10
},
# SNR (dB * 10)
%{
oid: "1.3.6.1.4.1.25651.1.2.3.1.2.3.0",
sensor_type: "snr",
sensor_descr: "SNR",
sensor_unit: "dB",
sensor_divisor: 10
}
]
end
end

View file

@ -0,0 +1,83 @@
defmodule Towerops.Snmp.Profiles.Vendors.Racom do
@moduledoc """
Racom RAy device-specific SNMP handling.
Supports RAy and RAy3 microwave radios.
Supported devices:
- RAy series
- RAy3 series
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Vendor
# RAy MIB - product name
@product_name_oid "1.3.6.1.4.1.33555.1.1.1.3.0"
@impl true
def profile_names do
["ray", "ray3"]
end
@impl true
def detect_hardware(client_opts) do
case Client.get(client_opts, @product_name_oid) do
{:ok, name} when is_binary(name) -> name
_ -> nil
end
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
[
# Temperature (divide by 100)
%{
oid: "1.3.6.1.4.1.33555.1.1.4.2.0",
sensor_type: "temperature",
sensor_descr: "Radio Temperature",
sensor_unit: "C",
sensor_divisor: 100
},
# Voltage (divide by 10)
%{
oid: "1.3.6.1.4.1.33555.1.1.4.3.0",
sensor_type: "voltage",
sensor_descr: "Voltage",
sensor_unit: "V",
sensor_divisor: 10
},
# BER (Bit Error Rate)
%{
oid: "1.3.6.1.4.1.33555.1.3.2.11.0",
sensor_type: "ber",
sensor_descr: "BER",
sensor_unit: "",
sensor_divisor: 1
},
# Tx Modulation Index
%{
oid: "1.3.6.1.4.1.33555.1.2.1.7.0",
sensor_type: "state",
sensor_descr: "Tx Modulation",
sensor_unit: "",
sensor_divisor: 1
},
# Rx Modulation Index
%{
oid: "1.3.6.1.4.1.33555.1.2.1.8.0",
sensor_type: "state",
sensor_descr: "Rx Modulation",
sensor_unit: "",
sensor_divisor: 1
}
]
end
end

View file

@ -7,19 +7,43 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do
"""
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Airfiber
alias Towerops.Snmp.Profiles.Vendors.Airos
alias Towerops.Snmp.Profiles.Vendors.Cnpilot
alias Towerops.Snmp.Profiles.Vendors.Dragonwave
alias Towerops.Snmp.Profiles.Vendors.Epmp
alias Towerops.Snmp.Profiles.Vendors.Exalt
alias Towerops.Snmp.Profiles.Vendors.Ligoos
alias Towerops.Snmp.Profiles.Vendors.Mimosa
alias Towerops.Snmp.Profiles.Vendors.Pmp
alias Towerops.Snmp.Profiles.Vendors.Racom
alias Towerops.Snmp.Profiles.Vendors.Radwin
alias Towerops.Snmp.Profiles.Vendors.Routeros
alias Towerops.Snmp.Profiles.Vendors.Ruckus
alias Towerops.Snmp.Profiles.Vendors.Saf
alias Towerops.Snmp.Profiles.Vendors.Siae
alias Towerops.Snmp.Profiles.Vendors.Siklu
alias Towerops.Snmp.Profiles.Vendors.Unifi
@vendors [Epmp, Airos, Routeros, Pmp, Mimosa, Siklu, Radwin, Unifi, Ligoos, Cnpilot, Saf]
@vendors [
Epmp,
Airos,
Airfiber,
Routeros,
Pmp,
Mimosa,
Siklu,
Radwin,
Unifi,
Ligoos,
Cnpilot,
Saf,
Ruckus,
Dragonwave,
Racom,
Exalt,
Siae
]
@doc """
Get the vendor module for a given profile name.

View file

@ -0,0 +1,79 @@
defmodule Towerops.Snmp.Profiles.Vendors.Ruckus do
@moduledoc """
Ruckus Wireless device-specific SNMP handling.
Supports ZoneDirector (ZD), SmartZone (SZ), and Unleashed controllers.
Supported devices:
- ZoneDirector 1000/1100/1200/3000/5000
- SmartZone 100/144/300
- Virtual SmartZone
- Unleashed APs
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Vendor
# ZoneDirector System MIB OIDs
# 1.3.6.1.4.1.25053.1.2.1.1.1 = ruckusZDSystemObjects
@zd_system_model "1.3.6.1.4.1.25053.1.2.1.1.1.1.9.0"
@impl true
def profile_names do
["ruckuswireless", "ruckuswireless-sz", "ruckuswireless-unleashed", "ruckuswireless-hotzone"]
end
@impl true
def detect_hardware(client_opts) do
case Client.get(client_opts, @zd_system_model) do
{:ok, model} when is_binary(model) -> model
_ -> nil
end
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
[
# ZoneDirector System Stats
# Number of connected APs
%{
oid: "1.3.6.1.4.1.25053.1.2.1.1.1.15.1.0",
sensor_type: "ap-count",
sensor_descr: "Connected APs",
sensor_unit: "",
sensor_divisor: 1
},
# Number of authorized clients
%{
oid: "1.3.6.1.4.1.25053.1.2.1.1.1.15.2.0",
sensor_type: "clients",
sensor_descr: "Wireless Clients",
sensor_unit: "",
sensor_divisor: 1
},
# Number of registered APs
%{
oid: "1.3.6.1.4.1.25053.1.2.1.1.1.15.15.0",
sensor_type: "ap-count",
sensor_descr: "Registered APs",
sensor_unit: "",
sensor_divisor: 1
},
# Number of rogue APs detected
%{
oid: "1.3.6.1.4.1.25053.1.2.1.1.1.15.3.0",
sensor_type: "count",
sensor_descr: "Rogue APs",
sensor_unit: "",
sensor_divisor: 1
}
]
end
end

View file

@ -0,0 +1,71 @@
defmodule Towerops.Snmp.Profiles.Vendors.Siae do
@moduledoc """
SIAE Microelettronica device-specific SNMP handling.
Supports ALFO and ALFOplus series microwave radios.
Supported devices:
- ALFO 80HD series
- ALFOplus series
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Profiles.Vendors.Vendor
@impl true
def profile_names do
["alfo80hd"]
end
@impl true
def detect_hardware(_client_opts) do
# Hardware detection handled by YAML profile
nil
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
# SIAE ALFO MIB OIDs
# Enterprise OID: 1.3.6.1.4.1.3373
[
# Tx Power
%{
oid: "1.3.6.1.4.1.3373.1103.80.1.1.11.1",
sensor_type: "power",
sensor_descr: "Tx Power",
sensor_unit: "dBm",
sensor_divisor: 10
},
# Rx Power (RSL)
%{
oid: "1.3.6.1.4.1.3373.1103.80.1.1.12.1",
sensor_type: "rssi",
sensor_descr: "Rx Power",
sensor_unit: "dBm",
sensor_divisor: 10
},
# SNR
%{
oid: "1.3.6.1.4.1.3373.1103.80.1.1.14.1",
sensor_type: "snr",
sensor_descr: "SNR",
sensor_unit: "dB",
sensor_divisor: 10
},
# Frequency
%{
oid: "1.3.6.1.4.1.3373.1103.80.1.1.3.1",
sensor_type: "frequency",
sensor_descr: "Tx Frequency",
sensor_unit: "MHz",
sensor_divisor: 1000
}
]
end
end

View file

@ -0,0 +1,94 @@
defmodule Towerops.Snmp.Profiles.Vendors.AirfiberTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Airfiber
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns airfiber profile names" do
assert Airfiber.profile_names() == ["airos-af", "airos-af-ltu", "airos-af60"]
end
end
describe "detect_hardware/1" do
test "returns nil (hardware detection handled by YAML)" do
assert Airfiber.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = Airfiber.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes frequency sensors" do
defs = Airfiber.wireless_oid_defs()
freq_sensors = Enum.filter(defs, &(&1.sensor_type == "frequency"))
assert length(freq_sensors) == 2
assert Enum.all?(freq_sensors, &(&1.sensor_unit == "MHz"))
end
test "includes RSSI sensors" do
defs = Airfiber.wireless_oid_defs()
rssi_sensors = Enum.filter(defs, &(&1.sensor_type == "rssi"))
assert length(rssi_sensors) == 2
end
test "includes rate sensors" do
defs = Airfiber.wireless_oid_defs()
rate_sensors = Enum.filter(defs, &(&1.sensor_type == "rate"))
assert length(rate_sensors) == 2
assert Enum.all?(rate_sensors, &(&1.sensor_unit == "Mbps"))
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 7, fn _, oid, _ ->
cond do
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}
String.contains?(oid, "41112.1.3.2.1.13.1") -> {:ok, -55}
String.contains?(oid, "41112.1.3.2.1.14.1") -> {:ok, -56}
String.contains?(oid, "41112.1.3.2.1.20.1") -> {:ok, 450}
String.contains?(oid, "41112.1.3.2.1.21.1") -> {:ok, 450}
true -> {:error, :no_such_object}
end
end)
sensors = Airfiber.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 7
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 7, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Airfiber.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -33,7 +33,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.CnpilotTest do
defs = Cnpilot.wireless_oid_defs()
assert is_list(defs)
assert defs != []
assert [_ | _] = defs
end
test "includes clients sensor" do

View file

@ -0,0 +1,109 @@
defmodule Towerops.Snmp.Profiles.Vendors.DragonwaveTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Dragonwave
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns dragonwave profile names" do
assert Dragonwave.profile_names() == [
"horizon-compact",
"horizon-compactplus",
"horizon-duo",
"horizon-quantum"
]
end
end
describe "detect_hardware/1" do
test "detects hardware from assembly number OID" do
expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.7262.2.2.1.1.8.0", _ ->
{:ok, "Horizon Compact+"}
end)
assert Dragonwave.detect_hardware(@client_opts) == "Horizon Compact+"
end
test "returns nil when OID unavailable" do
expect(SnmpMock, :get, fn _, _, _ ->
{:error, :no_such_object}
end)
assert Dragonwave.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = Dragonwave.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes temperature sensor with divisor" do
defs = Dragonwave.wireless_oid_defs()
temp = Enum.find(defs, &(&1.sensor_type == "temperature"))
assert temp
assert temp.sensor_divisor == 10
end
test "includes SNR sensor" do
defs = Dragonwave.wireless_oid_defs()
snr = Enum.find(defs, &(&1.sensor_type == "snr"))
assert snr
assert snr.sensor_unit == "dB"
end
test "includes RSSI sensor" do
defs = Dragonwave.wireless_oid_defs()
rssi = Enum.find(defs, &(&1.sensor_type == "rssi"))
assert rssi
assert rssi.sensor_descr == "RSL"
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 4, fn _, oid, _ ->
cond do
String.contains?(oid, "7262.2.2.5.1.3.6.0") -> {:ok, 450}
String.contains?(oid, "7262.2.2.5.1.2.3.0") -> {:ok, 280}
String.contains?(oid, "7262.2.2.5.1.2.2.0") -> {:ok, -550}
String.contains?(oid, "7262.2.2.5.1.3.2.0") -> {:ok, 180}
true -> {:error, :no_such_object}
end
end)
sensors = Dragonwave.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 4
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 4, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Dragonwave.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -0,0 +1,102 @@
defmodule Towerops.Snmp.Profiles.Vendors.ExaltTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Exalt
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns exalt profile name" do
assert Exalt.profile_names() == ["extendair"]
end
end
describe "detect_hardware/1" do
test "returns nil (hardware detection handled by YAML)" do
assert Exalt.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = Exalt.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes frequency sensors with kHz divisor" do
defs = Exalt.wireless_oid_defs()
freq_sensors = Enum.filter(defs, &(&1.sensor_type == "frequency"))
assert length(freq_sensors) == 2
# Exalt returns frequency in kHz
assert Enum.all?(freq_sensors, &(&1.sensor_divisor == 1000))
end
test "includes power sensor" do
defs = Exalt.wireless_oid_defs()
power = Enum.find(defs, &(&1.sensor_type == "power"))
assert power
assert power.sensor_descr == "Tx Power"
end
test "includes RSSI sensor" do
defs = Exalt.wireless_oid_defs()
rssi = Enum.find(defs, &(&1.sensor_type == "rssi"))
assert rssi
assert rssi.sensor_descr == "RSL"
end
test "includes SNR sensor" do
defs = Exalt.wireless_oid_defs()
snr = Enum.find(defs, &(&1.sensor_type == "snr"))
assert snr
assert snr.sensor_unit == "dB"
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 5, fn _, oid, _ ->
cond do
String.contains?(oid, "25651.1.2.3.1.1.7.0") -> {:ok, 71_000_000}
String.contains?(oid, "25651.1.2.3.1.1.8.0") -> {:ok, 81_000_000}
String.contains?(oid, "25651.1.2.3.1.1.9.0") -> {:ok, 200}
String.contains?(oid, "25651.1.2.3.1.2.1.0") -> {:ok, -550}
String.contains?(oid, "25651.1.2.3.1.2.3.0") -> {:ok, 280}
true -> {:error, :no_such_object}
end
end)
sensors = Exalt.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 5
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 5, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Exalt.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -33,7 +33,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.LigoosTest do
defs = Ligoos.wireless_oid_defs()
assert is_list(defs)
assert defs != []
assert [_ | _] = defs
end
test "includes frequency sensor" do

View file

@ -33,7 +33,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.MimosaTest do
defs = Mimosa.wireless_oid_defs()
assert is_list(defs)
assert defs != []
assert [_ | _] = defs
end
test "includes error-ratio sensors" do

View file

@ -109,7 +109,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.PmpTest do
defs = Pmp.wireless_oid_defs()
assert is_list(defs)
assert defs != []
assert [_ | _] = defs
# Verify structure of first definition
first = hd(defs)

View file

@ -0,0 +1,105 @@
defmodule Towerops.Snmp.Profiles.Vendors.RacomTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Racom
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns racom profile names" do
assert Racom.profile_names() == ["ray", "ray3"]
end
end
describe "detect_hardware/1" do
test "detects hardware from product name OID" do
expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.33555.1.1.1.3.0", _ ->
{:ok, "RAy3-17"}
end)
assert Racom.detect_hardware(@client_opts) == "RAy3-17"
end
test "returns nil when OID unavailable" do
expect(SnmpMock, :get, fn _, _, _ ->
{:error, :no_such_object}
end)
assert Racom.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = Racom.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes temperature sensor with high divisor" do
defs = Racom.wireless_oid_defs()
temp = Enum.find(defs, &(&1.sensor_type == "temperature"))
assert temp
assert temp.sensor_divisor == 100
end
test "includes voltage sensor" do
defs = Racom.wireless_oid_defs()
voltage = Enum.find(defs, &(&1.sensor_type == "voltage"))
assert voltage
assert voltage.sensor_unit == "V"
end
test "includes BER sensor" do
defs = Racom.wireless_oid_defs()
ber = Enum.find(defs, &(&1.sensor_type == "ber"))
assert ber
assert ber.sensor_descr == "BER"
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 5, fn _, oid, _ ->
cond do
String.contains?(oid, "33555.1.1.4.2.0") -> {:ok, 4500}
String.contains?(oid, "33555.1.1.4.3.0") -> {:ok, 480}
String.contains?(oid, "33555.1.3.2.11.0") -> {:ok, 0}
String.contains?(oid, "33555.1.2.1.7.0") -> {:ok, 4}
String.contains?(oid, "33555.1.2.1.8.0") -> {:ok, 4}
true -> {:error, :no_such_object}
end
end)
sensors = Racom.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 5
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 5, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Racom.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -33,7 +33,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.RadwinTest do
defs = Radwin.wireless_oid_defs()
assert is_list(defs)
assert defs != []
assert [_ | _] = defs
end
test "includes distance sensor with km unit" do

View file

@ -3,16 +3,22 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
import Mox
alias Towerops.Snmp.Profiles.Vendors.Airfiber
alias Towerops.Snmp.Profiles.Vendors.Airos
alias Towerops.Snmp.Profiles.Vendors.Cnpilot
alias Towerops.Snmp.Profiles.Vendors.Dragonwave
alias Towerops.Snmp.Profiles.Vendors.Epmp
alias Towerops.Snmp.Profiles.Vendors.Exalt
alias Towerops.Snmp.Profiles.Vendors.Ligoos
alias Towerops.Snmp.Profiles.Vendors.Mimosa
alias Towerops.Snmp.Profiles.Vendors.Pmp
alias Towerops.Snmp.Profiles.Vendors.Racom
alias Towerops.Snmp.Profiles.Vendors.Radwin
alias Towerops.Snmp.Profiles.Vendors.Registry
alias Towerops.Snmp.Profiles.Vendors.Routeros
alias Towerops.Snmp.Profiles.Vendors.Ruckus
alias Towerops.Snmp.Profiles.Vendors.Saf
alias Towerops.Snmp.Profiles.Vendors.Siae
alias Towerops.Snmp.Profiles.Vendors.Siklu
alias Towerops.Snmp.Profiles.Vendors.Unifi
alias Towerops.Snmp.SnmpMock
@ -34,6 +40,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
assert is_list(vendors)
assert Epmp in vendors
assert Airos in vendors
assert Airfiber in vendors
assert Routeros in vendors
assert Pmp in vendors
assert Mimosa in vendors
@ -43,6 +50,11 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
assert Ligoos in vendors
assert Cnpilot in vendors
assert Saf in vendors
assert Ruckus in vendors
assert Dragonwave in vendors
assert Racom in vendors
assert Exalt in vendors
assert Siae in vendors
end
end
@ -54,6 +66,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
# Check for some known profile names
assert "epmp" in names
assert "airos" in names
assert "airos-af" in names
assert "routeros" in names
assert "pmp" in names
assert "mimosa" in names
@ -64,6 +77,11 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
assert "deliberant" in names
assert "cnpilot" in names
assert "saf" in names
assert "ruckuswireless" in names
assert "horizon-compact" in names
assert "ray" in names
assert "extendair" in names
assert "alfo80hd" in names
end
end
@ -116,6 +134,30 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
assert Registry.get_vendor("saf") == Saf
end
test "returns Ruckus for ruckuswireless profile" do
assert Registry.get_vendor("ruckuswireless") == Ruckus
end
test "returns Dragonwave for horizon-compact profile" do
assert Registry.get_vendor("horizon-compact") == Dragonwave
end
test "returns Airfiber for airos-af profile" do
assert Registry.get_vendor("airos-af") == Airfiber
end
test "returns Racom for ray profile" do
assert Registry.get_vendor("ray") == Racom
end
test "returns Exalt for extendair profile" do
assert Registry.get_vendor("extendair") == Exalt
end
test "returns Siae for alfo80hd profile" do
assert Registry.get_vendor("alfo80hd") == Siae
end
test "returns nil for unknown profile" do
assert Registry.get_vendor("unknown_vendor") == nil
end

View file

@ -0,0 +1,100 @@
defmodule Towerops.Snmp.Profiles.Vendors.RuckusTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Ruckus
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns ruckus profile names" do
assert Ruckus.profile_names() == [
"ruckuswireless",
"ruckuswireless-sz",
"ruckuswireless-unleashed",
"ruckuswireless-hotzone"
]
end
end
describe "detect_hardware/1" do
test "detects hardware from system model OID" do
expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.25053.1.2.1.1.1.1.9.0", _ ->
{:ok, "ZD3000"}
end)
assert Ruckus.detect_hardware(@client_opts) == "ZD3000"
end
test "returns nil when OID unavailable" do
expect(SnmpMock, :get, fn _, _, _ ->
{:error, :no_such_object}
end)
assert Ruckus.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = Ruckus.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes AP count sensors" do
defs = Ruckus.wireless_oid_defs()
ap_sensors = Enum.filter(defs, &(&1.sensor_type == "ap-count"))
assert length(ap_sensors) == 2
end
test "includes clients sensor" do
defs = Ruckus.wireless_oid_defs()
clients = Enum.find(defs, &(&1.sensor_type == "clients"))
assert clients
assert clients.sensor_descr == "Wireless Clients"
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 4, fn _, oid, _ ->
cond do
String.contains?(oid, "25053.1.2.1.1.1.15.1.0") -> {:ok, 5}
String.contains?(oid, "25053.1.2.1.1.1.15.2.0") -> {:ok, 150}
String.contains?(oid, "25053.1.2.1.1.1.15.15.0") -> {:ok, 6}
String.contains?(oid, "25053.1.2.1.1.1.15.3.0") -> {:ok, 2}
true -> {:error, :no_such_object}
end
end)
sensors = Ruckus.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 4
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 4, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Ruckus.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -33,7 +33,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.SafTest do
defs = Saf.wireless_oid_defs()
assert is_list(defs)
assert defs != []
assert [_ | _] = defs
end
test "includes frequency sensors with correct divisor" do

View file

@ -0,0 +1,102 @@
defmodule Towerops.Snmp.Profiles.Vendors.SiaeTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Siae
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns siae profile name" do
assert Siae.profile_names() == ["alfo80hd"]
end
end
describe "detect_hardware/1" do
test "returns nil (hardware detection handled by YAML)" do
assert Siae.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = Siae.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes power sensor" do
defs = Siae.wireless_oid_defs()
power = Enum.find(defs, &(&1.sensor_type == "power"))
assert power
assert power.sensor_descr == "Tx Power"
assert power.sensor_divisor == 10
end
test "includes RSSI sensor" do
defs = Siae.wireless_oid_defs()
rssi = Enum.find(defs, &(&1.sensor_type == "rssi"))
assert rssi
assert rssi.sensor_descr == "Rx Power"
end
test "includes SNR sensor" do
defs = Siae.wireless_oid_defs()
snr = Enum.find(defs, &(&1.sensor_type == "snr"))
assert snr
assert snr.sensor_unit == "dB"
end
test "includes frequency sensor" do
defs = Siae.wireless_oid_defs()
freq = Enum.find(defs, &(&1.sensor_type == "frequency"))
assert freq
# SIAE frequency in kHz
assert freq.sensor_divisor == 1000
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 4, fn _, oid, _ ->
cond do
String.contains?(oid, "3373.1103.80.1.1.11.1") -> {:ok, 180}
String.contains?(oid, "3373.1103.80.1.1.12.1") -> {:ok, -450}
String.contains?(oid, "3373.1103.80.1.1.14.1") -> {:ok, 250}
String.contains?(oid, "3373.1103.80.1.1.3.1") -> {:ok, 71_000_000}
true -> {:error, :no_such_object}
end
end)
sensors = Siae.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 4
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 4, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Siae.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -69,7 +69,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.SikluTest do
defs = Siklu.wireless_oid_defs()
assert is_list(defs)
assert defs != []
assert [_ | _] = defs
end
test "includes frequency sensor" do

View file

@ -53,7 +53,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.UnifiTest do
defs = Unifi.wireless_oid_defs()
assert is_list(defs)
assert defs != []
assert [_ | _] = defs
end
test "includes client count sensors for both radios" do