feat: The Other Dude v9.0.1 — full-featured email system
ci: add GitHub Pages deployment workflow for docs site Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
145
poller/internal/device/wireless.go
Normal file
145
poller/internal/device/wireless.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"strconv"
|
||||
|
||||
routeros "github.com/go-routeros/routeros/v3"
|
||||
)
|
||||
|
||||
// WirelessStats holds aggregated wireless metrics for a single wireless interface.
|
||||
// Metrics are aggregated across all registered clients on that interface.
|
||||
type WirelessStats struct {
|
||||
Interface string `json:"interface"`
|
||||
ClientCount int `json:"client_count"`
|
||||
AvgSignal int `json:"avg_signal"` // dBm (negative), e.g. -67
|
||||
CCQ int `json:"ccq"` // 0–100 percentage; 0 if not available (v7)
|
||||
Frequency int `json:"frequency"` // MHz
|
||||
}
|
||||
|
||||
// CollectWireless queries the RouterOS device for wireless registration-table
|
||||
// entries and aggregates them per interface.
|
||||
//
|
||||
// Version routing:
|
||||
// - majorVersion >= 7: tries /interface/wifi/registration-table/print first;
|
||||
// falls back to /interface/wireless/registration-table/print if that fails.
|
||||
// - majorVersion < 7 (including 0 for unknown): uses the classic wireless path.
|
||||
//
|
||||
// Returns an empty slice (not an error) when the device has no wireless interfaces.
|
||||
func CollectWireless(client *routeros.Client, majorVersion int) ([]WirelessStats, error) {
|
||||
var registrations []map[string]string
|
||||
var useV7WiFi bool
|
||||
|
||||
if majorVersion >= 7 {
|
||||
// Try the v7 WiFi API first.
|
||||
regReply, err := client.Run("/interface/wifi/registration-table/print")
|
||||
if err == nil {
|
||||
useV7WiFi = true
|
||||
for _, s := range regReply.Re {
|
||||
registrations = append(registrations, s.Map)
|
||||
}
|
||||
} else {
|
||||
slog.Debug("v7 wifi registration-table not available, falling back to wireless", "error", err)
|
||||
// Fall back to classic wireless path.
|
||||
regReply, err = client.Run("/interface/wireless/registration-table/print")
|
||||
if err != nil {
|
||||
slog.Debug("device has no wireless interfaces", "error", err)
|
||||
return nil, nil
|
||||
}
|
||||
for _, s := range regReply.Re {
|
||||
registrations = append(registrations, s.Map)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
regReply, err := client.Run("/interface/wireless/registration-table/print")
|
||||
if err != nil {
|
||||
slog.Debug("device has no wireless interfaces", "error", err)
|
||||
return nil, nil
|
||||
}
|
||||
for _, s := range regReply.Re {
|
||||
registrations = append(registrations, s.Map)
|
||||
}
|
||||
}
|
||||
|
||||
if len(registrations) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Collect frequency per interface so we can include it in the stats.
|
||||
frequencies := collectWirelessFrequencies(client, majorVersion, useV7WiFi)
|
||||
|
||||
// Aggregate registration-table rows per interface.
|
||||
type ifaceAgg struct {
|
||||
count int
|
||||
signal int
|
||||
ccq int
|
||||
}
|
||||
|
||||
agg := make(map[string]*ifaceAgg)
|
||||
for _, r := range registrations {
|
||||
iface := r["interface"]
|
||||
if iface == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := agg[iface]; !ok {
|
||||
agg[iface] = &ifaceAgg{}
|
||||
}
|
||||
a := agg[iface]
|
||||
a.count++
|
||||
|
||||
if sig, err := strconv.Atoi(r["signal-strength"]); err == nil {
|
||||
a.signal += sig
|
||||
}
|
||||
if ccq, err := strconv.Atoi(r["tx-ccq"]); err == nil {
|
||||
a.ccq += ccq
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]WirelessStats, 0, len(agg))
|
||||
for iface, a := range agg {
|
||||
avgSignal := 0
|
||||
avgCCQ := 0
|
||||
if a.count > 0 {
|
||||
avgSignal = a.signal / a.count
|
||||
avgCCQ = a.ccq / a.count
|
||||
}
|
||||
result = append(result, WirelessStats{
|
||||
Interface: iface,
|
||||
ClientCount: a.count,
|
||||
AvgSignal: avgSignal,
|
||||
CCQ: avgCCQ,
|
||||
Frequency: frequencies[iface],
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// collectWirelessFrequencies returns a map of interface name → frequency (MHz).
|
||||
// Uses the v7 WiFi API or the classic wireless API based on the useV7WiFi flag.
|
||||
func collectWirelessFrequencies(client *routeros.Client, majorVersion int, useV7WiFi bool) map[string]int {
|
||||
freqs := make(map[string]int)
|
||||
|
||||
var cmd string
|
||||
if useV7WiFi {
|
||||
cmd = "/interface/wifi/print"
|
||||
} else {
|
||||
cmd = "/interface/wireless/print"
|
||||
}
|
||||
|
||||
reply, err := client.Run(cmd, "=.proplist=name,frequency")
|
||||
if err != nil {
|
||||
slog.Debug("could not collect wireless frequencies", "command", cmd, "error", err)
|
||||
return freqs
|
||||
}
|
||||
|
||||
for _, s := range reply.Re {
|
||||
m := s.Map
|
||||
name := m["name"]
|
||||
if freq, err := strconv.Atoi(m["frequency"]); err == nil {
|
||||
freqs[name] = freq
|
||||
}
|
||||
}
|
||||
|
||||
return freqs
|
||||
}
|
||||
Reference in New Issue
Block a user