Merge pull request #1 from davidnewhall/dn2_features

Features.
This commit is contained in:
David Newhall II 2018-04-28 18:41:22 -07:00 committed by GitHub
commit 0913a89d85
16 changed files with 3975 additions and 144 deletions

View File

@ -3,3 +3,5 @@
/*.1.gz /*.1.gz
/*.1 /*.1
/vendor /vendor
.DS_Store
*~

View File

@ -1,12 +1,104 @@
# Unifi # Unifi
Collect your Unifi Controller Client data and send it to an InfluxDB instance. Grafana dashboard included. Collect your Unifi Controller Data and send it to an InfluxDB instance.
Grafana dashboards included.
![image](https://raw.githubusercontent.com/davidnewhall/unifi/master/grafana-unifi-dashboard.png)
## Installation ## Installation
[See the Wiki!](https://github.com/davidnewhall/unifi-poller/wiki/Installation) [See the Wiki!](https://github.com/davidnewhall/unifi-poller/wiki/Installation)
# Backstory
Okay, so here's the deal. I found a simple piece of code on github that
sorta did what I needed; we all know that story. I wanted more data, so
I added more data collection. I believe I've completely rewritten every
piece of original code, except the copyright/license file and that's fine
by me. I probably wouldn't have made it this far if
[Garrett](https://github.com/dewski/unifi) hadn't written the original
code I started with. Many props my man.
The original code pulled only the client data. This app now pulls data
for clients, access points, security gateways and switches. I currently
own two UAP-AC-PROs, one USG-3 and one US-24-250W. If your devices differ
this app may miss some data. I'm willing to help and make it better.
Open an [Issue](https://github.com/davidnewhall/unifi-poller/issues) and
we'll figure out how to get things working for you.
# What's this data good for?
I've been trying to get my UAP data into Grafana. Sure, google search that.
You'll find [this](https://community.ubnt.com/t5/UniFi-Wireless/Grafana-dashboard-for-UniFi-APs-now-available/td-p/1833532).
And that's all you'll find. What if you don't want to deal with SNMP?
Well, here you go. I've replicated 90% of what you see on those SNMP-powered
dashboards with this Go app running on the same mac as my Unifi controller.
All without enabling SNMP nor trying to understand those OIDs. Mad props
to [waterside](https://community.ubnt.com/t5/user/viewprofilepage/user-id/303058)
for making this dashboard; it gave me a fantastic start to making my own.
# What now...
- I probably suck at InfluxDB.
I don't know what should be a tag and what should be a field. I think
I did my best, but there's certainly room for improvements in both
the data input and the Grafana graphs (output).
- The USW and USG code needs love.
Up to this point, my focus has been on UAP. I have only included dashboards
that focus on UAP. I am still working on the other two, but it may be a while
before I get around to publishing them. Help is appreciated.
- Are there other devices that need to be included?
I have: switch, router, access point. Three total, and the type structs are
likely missing data for variants of these devices. e.g. Some UAPs have more
radios, I probably didn't properly account for that. Some gateways have more
ports, some switches have 10Gb, etc. These are things I do not have data on
to write code for. If you have these devices, and want them graphed, open an
Issue and lets discuss.
- Better Installation instructions.
If you're a nerd you can probably figure it out. I'd still like some pretty
pictures and maybe even a Twitch VOD.
- Sanity Checking
Did I actually graph the right data in the right way? Some validation would
be nice.
- Radios, Frequencies, Interfaces, vAPs
My access points only seem to have two radios, one interface and vAP per radio.
I'm not sure if the graphs, as-is, provide enough insight into APs with other
configurations. Help me figure that out?
# What's it look like?
Here's a picture of the Client dashboard.
![image](images/unifi-clients-dashboard.png?raw=true)
Here's a picture of the UAP dashboard. This only shows one device, but you can
select multiple to put specific stats side-by-side.
![image](images/unifi-uap-dashboard.png?raw=true)
# Woah, there's a library in here.
Sure is. If you want to write your own code around unifi data, you can import
the `unidev` library and easily poll your controller. If you have interest in
how to use the library, or need more features, open an Issue. I'm not committed
to documenting it unless it seems useful.
## Copyright & License ## Copyright & License
Copyright © 2016 Garrett Bjerkhoel. See [MIT-LICENSE](MIT-LICENSE) for details. - Copyright © 2016 Garrett Bjerkhoel.
- Copyright © 2018 David Newhall II.
- See [MIT-LICENSE](MIT-LICENSE) for license information.

View File

@ -4,7 +4,7 @@ import "time"
// Version will be injected at build time. // Version will be injected at build time.
var ( var (
Version = "v0.1" Version = "v0.2"
Debug = false Debug = false
) )

View File

@ -83,14 +83,14 @@ func GetConfig(configFile string) (Config, error) {
func (c *Config) PollUnifiController(infdb influx.Client, unifi *unidev.AuthedReq) { func (c *Config) PollUnifiController(infdb influx.Client, unifi *unidev.AuthedReq) {
ticker := time.NewTicker(c.Interval.value) ticker := time.NewTicker(c.Interval.value)
for range ticker.C { for range ticker.C {
clients, err := unifi.GetUnifiClients() clients, err := unifi.GetUnifiClientAssets()
if err != nil { if err != nil {
log.Println("unifi.GetUnifiClients():", err) log.Println("unifi.GetUnifiClientsAssets():", err)
continue continue
} }
devices, err := unifi.GetUnifiDevices() devices, err := unifi.GetUnifiDeviceAssets()
if err != nil { if err != nil {
log.Println("unifi.GetUnifiDevices():", err) log.Println("unifi.GetUnifiDeviceAssets():", err)
continue continue
} }
bp, err := influx.NewBatchPoints(influx.BatchPointsConfig{ bp, err := influx.NewBatchPoints(influx.BatchPointsConfig{
@ -102,10 +102,10 @@ func (c *Config) PollUnifiController(infdb influx.Client, unifi *unidev.AuthedRe
} }
for _, asset := range append(clients, devices...) { for _, asset := range append(clients, devices...) {
if pt, errr := asset.Point(); errr != nil { if pt, errr := asset.Points(); errr != nil {
log.Println("asset.Point():", errr) log.Println("asset.Points():", errr)
} else { } else {
bp.AddPoint(pt) bp.AddPoints(pt)
} }
} }

View File

@ -0,0 +1,5 @@
# Grafana Dashboards
Import these into Grafana to quickly visualize data from your devices.
They may/do use a few plugins: Clock, Discrete, Singlestat, Table

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 246 KiB

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

View File

@ -7,8 +7,9 @@ import (
influx "github.com/influxdata/influxdb/client/v2" influx "github.com/influxdata/influxdb/client/v2"
) )
// Point generates a client's datapoint for InfluxDB. // Points generates a client's datapoints for InfluxDB.
func (u UCL) Point() (*influx.Point, error) { func (u UCL) Points() ([]*influx.Point, error) {
var points []*influx.Point
if u.Name == "" && u.Hostname != "" { if u.Name == "" && u.Hostname != "" {
u.Name = u.Hostname u.Name = u.Hostname
} else if u.Hostname == "" && u.Name != "" { } else if u.Hostname == "" && u.Name != "" {
@ -95,6 +96,9 @@ func (u UCL) Point() (*influx.Point, error) {
"wired-tx_bytes-r": u.WiredTxBytesR, "wired-tx_bytes-r": u.WiredTxBytesR,
"wired-tx_packets": u.WiredTxPackets, "wired-tx_packets": u.WiredTxPackets,
} }
pt, err := influx.NewPoint("clients", tags, fields, time.Now())
return influx.NewPoint("clients", tags, fields, time.Now()) if err == nil {
points = append(points, pt)
}
return points, err
} }

View File

@ -7,8 +7,12 @@ import (
influx "github.com/influxdata/influxdb/client/v2" influx "github.com/influxdata/influxdb/client/v2"
) )
// Point generates a device's datapoint for InfluxDB. // Points generates a device's datapoints for InfluxDB.
func (u UAP) Point() (*influx.Point, error) { func (u UAP) Points() ([]*influx.Point, error) {
/* I generally suck at InfluxDB, so if I got the tags/fields wrong,
please send me a PR or open an Issue to address my faults. Thanks!
*/
var points []*influx.Point
tags := map[string]string{ tags := map[string]string{
"id": u.ID, "id": u.ID,
"mac": u.Mac, "mac": u.Mac,
@ -35,7 +39,7 @@ func (u UAP) Point() (*influx.Point, error) {
"has_speaker": strconv.FormatBool(u.HasSpeaker), "has_speaker": strconv.FormatBool(u.HasSpeaker),
"inform_ip": u.InformIP, "inform_ip": u.InformIP,
"isolated": strconv.FormatBool(u.Isolated), "isolated": strconv.FormatBool(u.Isolated),
"last_seen": strconv.Itoa(u.LastSeen), "last_seen": strconv.FormatFloat(u.LastSeen, 'f', 6, 64),
"last_uplink_mac": u.LastUplink.UplinkMac, "last_uplink_mac": u.LastUplink.UplinkMac,
"last_uplink_remote_port": strconv.Itoa(u.LastUplink.UplinkRemotePort), "last_uplink_remote_port": strconv.Itoa(u.LastUplink.UplinkRemotePort),
"known_cfgversion": u.KnownCfgversion, "known_cfgversion": u.KnownCfgversion,
@ -74,6 +78,7 @@ func (u UAP) Point() (*influx.Point, error) {
"loadavg_15": u.SysStats.Loadavg15, "loadavg_15": u.SysStats.Loadavg15,
"mem_buffer": u.SysStats.MemBuffer, "mem_buffer": u.SysStats.MemBuffer,
"mem_total": u.SysStats.MemTotal, "mem_total": u.SysStats.MemTotal,
"mem_used": u.SysStats.MemUsed,
"cpu": u.SystemStats.CPU, "cpu": u.SystemStats.CPU,
"mem": u.SystemStats.Mem, "mem": u.SystemStats.Mem,
"system_uptime": u.SystemStats.Uptime, "system_uptime": u.SystemStats.Uptime,
@ -165,5 +170,98 @@ func (u UAP) Point() (*influx.Point, error) {
"stat_wifi1-tx_packets": u.Stat.Wifi1TxPackets, "stat_wifi1-tx_packets": u.Stat.Wifi1TxPackets,
"stat_wifi1-tx_retries": u.Stat.Wifi1TxRetries, "stat_wifi1-tx_retries": u.Stat.Wifi1TxRetries,
} }
return influx.NewPoint("uap", tags, fields, time.Now()) pt, err := influx.NewPoint("uap", tags, fields, time.Now())
if err != nil {
return nil, err
}
points = append(points, pt)
for _, p := range u.RadioTable {
tags := map[string]string{
"device_name": u.Name,
"device_id": u.ID,
"device_mac": u.Mac,
"name": p.Name,
"wlangroup_id": p.WlangroupID,
"channel": p.Channel, // not the channel #
"radio": p.Radio,
}
fields := map[string]interface{}{
"builtin_ant_gain": p.BuiltinAntGain,
"current_antenna_gain": p.CurrentAntennaGain,
"has_dfs": p.HasDfs,
"has_fccdfs": p.HasFccdfs,
"ht": p.Ht,
"is_11ac": p.Is11Ac,
"max_txpower": p.MaxTxpower,
"min_rssi_enabled": p.MinRssiEnabled,
"min_txpower": p.MinTxpower,
"nss": p.Nss,
"radio_caps": p.RadioCaps,
"tx_power": p.TxPower,
"tx_power_mode": p.TxPowerMode,
}
for _, s := range u.RadioTableStats {
// This may be a tad slower but it allows putting
// all the radio stats into one table.
if p.Name == s.Name {
fields["ast_be_xmit"] = s.AstBeXmit
fields["ast_cst"] = s.AstCst
fields["channel"] = s.Channel
fields["ast_txto"] = s.AstTxto
fields["cu_self_rx"] = s.CuSelfRx
fields["cu_self_tx"] = s.CuSelfTx
fields["cu_total"] = s.CuTotal
fields["extchannel"] = s.Extchannel
fields["gain"] = s.Gain
fields["guest-num_sta"] = s.GuestNumSta
fields["num_sta"] = s.NumSta
fields["radio"] = s.Radio
fields["state"] = s.State
fields["radio_tx_packets"] = s.TxPackets
fields["radio_tx_power"] = s.TxPower
fields["radio_tx_retries"] = s.TxRetries
fields["user-num_sta"] = s.UserNumSta
}
}
for _, s := range u.VapTable {
if p.Name == s.RadioName {
tags["ap_mac"] = s.ApMac
tags["bssid"] = s.Bssid
fields["ccq"] = s.Ccq
fields["essid"] = s.Essid
fields["extchannel"] = s.Extchannel
tags["vap_id"] = s.ID
fields["is_guest"] = s.IsGuest
fields["is_wep"] = s.IsWep
fields["mac_filter_rejections"] = s.MacFilterRejections
fields["map_id"] = s.MapID
tags["vap_name"] = s.Name
fields["rx_bytes"] = s.RxBytes
fields["rx_crypts"] = s.RxCrypts
fields["rx_dropped"] = s.RxDropped
fields["rx_errors"] = s.RxErrors
fields["rx_frags"] = s.RxFrags
fields["rx_nwids"] = s.RxNwids
fields["rx_packets"] = s.RxPackets
fields["tx_bytes"] = s.TxBytes
fields["tx_dropped"] = s.TxDropped
fields["tx_errors"] = s.TxErrors
fields["tx_latency_avg"] = s.TxLatencyAvg
fields["tx_latency_max"] = s.TxLatencyMax
fields["tx_latency_min"] = s.TxLatencyMin
fields["tx_packets"] = s.TxPackets
fields["tx_power"] = s.TxPower
fields["tx_retries"] = s.TxRetries
fields["usage"] = s.Usage
tags["wlanconf_id"] = s.WlanconfID
}
}
pt, err := influx.NewPoint("uap_radios", tags, fields, time.Now())
if err != nil {
return points, err
}
points = append(points, pt)
}
return points, nil
} }

View File

@ -1,7 +1,12 @@
package unidev package unidev
// UAP is a Unifi Access Point // UAP is a Unifi Access Point.
type UAP struct { type UAP struct {
/* This was auto generated and then slowly edited by hand
to get all the data types right and graphable.
No ones feelings will be hurt if you want to break this
up into multiple structs, and/or make it better in general.
*/
ID string `json:"_id"` ID string `json:"_id"`
UUptime float64 `json:"_uptime"` UUptime float64 `json:"_uptime"`
AdoptIP string `json:"adopt_ip,omitempty"` AdoptIP string `json:"adopt_ip,omitempty"`
@ -36,17 +41,17 @@ type UAP struct {
Name string `json:"name"` Name string `json:"name"`
NumPort float64 `json:"num_port"` NumPort float64 `json:"num_port"`
} `json:"ethernet_table"` } `json:"ethernet_table"`
FwCaps int `json:"fw_caps"` FwCaps int `json:"fw_caps"`
GuestNumSta int `json:"guest-num_sta"` GuestNumSta int `json:"guest-num_sta"`
GuestToken string `json:"guest_token"` GuestToken string `json:"guest_token"`
HasEth1 bool `json:"has_eth1"` HasEth1 bool `json:"has_eth1"`
HasSpeaker bool `json:"has_speaker"` HasSpeaker bool `json:"has_speaker"`
InformIP string `json:"inform_ip"` InformIP string `json:"inform_ip"`
InformURL string `json:"inform_url"` InformURL string `json:"inform_url"`
IP string `json:"ip"` IP string `json:"ip"`
Isolated bool `json:"isolated"` Isolated bool `json:"isolated"`
KnownCfgversion string `json:"known_cfgversion"` KnownCfgversion string `json:"known_cfgversion"`
LastSeen int `json:"last_seen"` LastSeen float64 `json:"last_seen"`
LastUplink struct { LastUplink struct {
UplinkMac string `json:"uplink_mac"` UplinkMac string `json:"uplink_mac"`
UplinkRemotePort int `json:"uplink_remote_port"` UplinkRemotePort int `json:"uplink_remote_port"`
@ -132,28 +137,28 @@ type UAP struct {
Is11Ac bool `json:"is_11ac,omitempty"` Is11Ac bool `json:"is_11ac,omitempty"`
} `json:"radio_table"` } `json:"radio_table"`
RadioTableStats []struct { RadioTableStats []struct {
AstBeXmit interface{} `json:"ast_be_xmit"` AstBeXmit float64 `json:"ast_be_xmit"`
AstCst interface{} `json:"ast_cst"` AstCst float64 `json:"ast_cst"`
AstTxto interface{} `json:"ast_txto"` AstTxto float64 `json:"ast_txto"`
Channel float64 `json:"channel"` Channel float64 `json:"channel"`
CuSelfRx float64 `json:"cu_self_rx"` CuSelfRx float64 `json:"cu_self_rx"`
CuSelfTx float64 `json:"cu_self_tx"` CuSelfTx float64 `json:"cu_self_tx"`
CuTotal float64 `json:"cu_total"` CuTotal float64 `json:"cu_total"`
Extchannel float64 `json:"extchannel"` Extchannel float64 `json:"extchannel"`
Gain float64 `json:"gain"` Gain float64 `json:"gain"`
GuestNumSta float64 `json:"guest-num_sta"` GuestNumSta float64 `json:"guest-num_sta"`
Name string `json:"name"` Name string `json:"name"`
NumSta float64 `json:"num_sta"` NumSta float64 `json:"num_sta"`
Radio string `json:"radio"` Radio string `json:"radio"`
State string `json:"state"` State string `json:"state"`
TxPackets float64 `json:"tx_packets"` TxPackets float64 `json:"tx_packets"`
TxPower float64 `json:"tx_power"` TxPower float64 `json:"tx_power"`
TxRetries float64 `json:"tx_retries"` TxRetries float64 `json:"tx_retries"`
UserNumSta float64 `json:"user-num_sta"` UserNumSta float64 `json:"user-num_sta"`
} `json:"radio_table_stats"` } `json:"radio_table_stats"`
Rollupgrade bool `json:"rollupgrade"` Rollupgrade bool `json:"rollupgrade"`
RxBytes int `json:"rx_bytes"` RxBytes float64 `json:"rx_bytes"`
RxBytesD int `json:"rx_bytes-d"` RxBytesD float64 `json:"rx_bytes-d"`
ScanRadioTable []interface{} `json:"scan_radio_table"` ScanRadioTable []interface{} `json:"scan_radio_table"`
Scanning bool `json:"scanning"` Scanning bool `json:"scanning"`
Serial string `json:"serial"` Serial string `json:"serial"`
@ -257,20 +262,20 @@ type UAP struct {
} `json:"stat"` } `json:"stat"`
State int `json:"state"` State int `json:"state"`
SysStats struct { SysStats struct {
Loadavg1 string `json:"loadavg_1"` Loadavg1 float64 `json:"loadavg_1,string"`
Loadavg15 string `json:"loadavg_15"` Loadavg15 float64 `json:"loadavg_15,string"`
Loadavg5 string `json:"loadavg_5"` Loadavg5 float64 `json:"loadavg_5,string"`
MemBuffer int `json:"mem_buffer"` MemBuffer float64 `json:"mem_buffer"`
MemTotal int `json:"mem_total"` MemTotal float64 `json:"mem_total"`
MemUsed int `json:"mem_used"` MemUsed float64 `json:"mem_used"`
} `json:"sys_stats"` } `json:"sys_stats"`
SystemStats struct { SystemStats struct {
CPU string `json:"cpu"` CPU float64 `json:"cpu,string"`
Mem string `json:"mem"` Mem float64 `json:"mem,string"`
Uptime string `json:"uptime"` Uptime float64 `json:"uptime,string"`
} `json:"system-stats"` } `json:"system-stats"`
TxBytes float64 `json:"tx_bytes"` TxBytes float64 `json:"tx_bytes"`
TxBytesD int `json:"tx_bytes-d"` TxBytesD float64 `json:"tx_bytes-d"`
Type string `json:"type"` Type string `json:"type"`
Upgradable bool `json:"upgradable"` Upgradable bool `json:"upgradable"`
Uplink struct { Uplink struct {
@ -284,63 +289,63 @@ type UAP struct {
Netmask string `json:"netmask"` Netmask string `json:"netmask"`
NumPort int `json:"num_port"` NumPort int `json:"num_port"`
RxBytes float64 `json:"rx_bytes"` RxBytes float64 `json:"rx_bytes"`
RxBytesR int `json:"rx_bytes-r"` RxBytesR float64 `json:"rx_bytes-r"`
RxDropped int `json:"rx_dropped"` RxDropped float64 `json:"rx_dropped"`
RxErrors int `json:"rx_errors"` RxErrors float64 `json:"rx_errors"`
RxMulticast int `json:"rx_multicast"` RxMulticast float64 `json:"rx_multicast"`
RxPackets int `json:"rx_packets"` RxPackets float64 `json:"rx_packets"`
Speed int `json:"speed"` Speed float64 `json:"speed"`
TxBytes float64 `json:"tx_bytes"` TxBytes float64 `json:"tx_bytes"`
TxBytesR int `json:"tx_bytes-r"` TxBytesR float64 `json:"tx_bytes-r"`
TxDropped int `json:"tx_dropped"` TxDropped float64 `json:"tx_dropped"`
TxErrors int `json:"tx_errors"` TxErrors float64 `json:"tx_errors"`
TxPackets int `json:"tx_packets"` TxPackets float64 `json:"tx_packets"`
Type string `json:"type"` Type string `json:"type"`
Up bool `json:"up"` Up bool `json:"up"`
UplinkMac string `json:"uplink_mac"` UplinkMac string `json:"uplink_mac"`
UplinkRemotePort int `json:"uplink_remote_port"` UplinkRemotePort int `json:"uplink_remote_port"`
} `json:"uplink"` } `json:"uplink"`
UplinkTable []interface{} `json:"uplink_table"` UplinkTable []interface{} `json:"uplink_table"`
Uptime int `json:"uptime"` Uptime float64 `json:"uptime"`
UserNumSta int `json:"user-num_sta"` UserNumSta int `json:"user-num_sta"`
VapTable []struct { VapTable []struct {
ApMac string `json:"ap_mac"` ApMac string `json:"ap_mac"`
Bssid string `json:"bssid"` Bssid string `json:"bssid"`
Ccq int `json:"ccq"` Ccq int `json:"ccq"`
Channel int `json:"channel"` Channel int `json:"channel"`
Essid string `json:"essid"` Essid string `json:"essid"`
Extchannel int `json:"extchannel"` Extchannel int `json:"extchannel"`
ID string `json:"id"` ID string `json:"id"`
IsGuest bool `json:"is_guest"` IsGuest bool `json:"is_guest"`
IsWep bool `json:"is_wep"` IsWep bool `json:"is_wep"`
MacFilterRejections int `json:"mac_filter_rejections"` MacFilterRejections int `json:"mac_filter_rejections"`
MapID interface{} `json:"map_id"` MapID string `json:"map_id"`
Name string `json:"name"` Name string `json:"name"`
NumSta int `json:"num_sta"` NumSta int `json:"num_sta"`
Radio string `json:"radio"` Radio string `json:"radio"`
RadioName string `json:"radio_name"` RadioName string `json:"radio_name"`
RxBytes int `json:"rx_bytes"` RxBytes float64 `json:"rx_bytes"`
RxCrypts int `json:"rx_crypts"` RxCrypts float64 `json:"rx_crypts"`
RxDropped int `json:"rx_dropped"` RxDropped float64 `json:"rx_dropped"`
RxErrors int `json:"rx_errors"` RxErrors float64 `json:"rx_errors"`
RxFrags int `json:"rx_frags"` RxFrags float64 `json:"rx_frags"`
RxNwids int `json:"rx_nwids"` RxNwids float64 `json:"rx_nwids"`
RxPackets int `json:"rx_packets"` RxPackets float64 `json:"rx_packets"`
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
State string `json:"state"` State string `json:"state"`
T string `json:"t"` T string `json:"t"`
TxBytes int `json:"tx_bytes"` TxBytes float64 `json:"tx_bytes"`
TxDropped int `json:"tx_dropped"` TxDropped float64 `json:"tx_dropped"`
TxErrors int `json:"tx_errors"` TxErrors float64 `json:"tx_errors"`
TxLatencyAvg float64 `json:"tx_latency_avg"` TxLatencyAvg float64 `json:"tx_latency_avg"`
TxLatencyMax float64 `json:"tx_latency_max"` TxLatencyMax float64 `json:"tx_latency_max"`
TxLatencyMin float64 `json:"tx_latency_min"` TxLatencyMin float64 `json:"tx_latency_min"`
TxPackets int `json:"tx_packets"` TxPackets float64 `json:"tx_packets"`
TxPower int `json:"tx_power"` TxPower int `json:"tx_power"`
TxRetries int `json:"tx_retries"` TxRetries int `json:"tx_retries"`
Up bool `json:"up"` Up bool `json:"up"`
Usage string `json:"usage"` Usage string `json:"usage"`
WlanconfID string `json:"wlanconf_id"` WlanconfID string `json:"wlanconf_id"`
} `json:"vap_table"` } `json:"vap_table"`
Version string `json:"version"` Version string `json:"version"`
VersionIncompatible bool `json:"version_incompatible"` VersionIncompatible bool `json:"version_incompatible"`

View File

@ -13,9 +13,12 @@ import (
const LoginPath = "/api/login" const LoginPath = "/api/login"
// Asset provides a common interface to retreive metrics from a device or client. // Asset provides a common interface to retreive metrics from a device or client.
// It currently only supports InfluxDB, but could be amended to support other
// libraries that have a similar interface.
// This app only uses the .AddPoint/s() methods with the Asset type.
type Asset interface { type Asset interface {
// Point() means this is useful to influxdb.. // Point() means this is useful to influxdb..
Point() (*influx.Point, error) Points() ([]*influx.Point, error)
// Add more methods to achieve more usefulness from this library. // Add more methods to achieve more usefulness from this library.
} }

View File

@ -21,7 +21,7 @@ const (
) )
// GetUnifiClients returns a response full of clients' data from the Unifi Controller. // GetUnifiClients returns a response full of clients' data from the Unifi Controller.
func (c *AuthedReq) GetUnifiClients() ([]Asset, error) { func (c *AuthedReq) GetUnifiClients() ([]UCL, error) {
var response struct { var response struct {
Clients []UCL `json:"data"` Clients []UCL `json:"data"`
Meta struct { Meta struct {
@ -34,68 +34,103 @@ func (c *AuthedReq) GetUnifiClients() ([]Asset, error) {
return nil, err return nil, err
} else if body, err := ioutil.ReadAll(resp.Body); err != nil { } else if body, err := ioutil.ReadAll(resp.Body); err != nil {
return nil, err return nil, err
} else if err = json.Unmarshal(body, response); err != nil { } else if err = json.Unmarshal(body, &response); err != nil {
return nil, err return nil, err
} else if err = resp.Body.Close(); err != nil { } else if err = resp.Body.Close(); err != nil {
log.Println("resp.Body.Close():", err) // Not fatal? Just log it. log.Println("resp.Body.Close():", err) // Not fatal? Just log it.
} }
clients := []Asset{} return response.Clients, nil
for _, r := range response.Clients { }
clients = append(clients, r)
// GetUnifiClientAssets provides an interface to return common asset types.
func (c *AuthedReq) GetUnifiClientAssets() ([]Asset, error) {
clients, err := c.GetUnifiClients()
assets := []Asset{}
if err == nil {
for _, r := range clients {
assets = append(assets, r)
}
} }
return clients, nil return assets, err
} }
// GetUnifiDevices returns a response full of devices' data from the Unifi Controller. // GetUnifiDevices returns a response full of devices' data from the Unifi Controller.
func (c *AuthedReq) GetUnifiDevices() ([]Asset, error) { func (c *AuthedReq) GetUnifiDevices() ([]USG, []USW, []UAP, error) {
var parsed struct { var parsed struct {
Data []json.RawMessage `json:"data"` Data []json.RawMessage `json:"data"`
Meta struct { Meta struct {
Rc string `json:"rc"` Rc string `json:"rc"`
} `json:"meta"` } `json:"meta"`
} }
assets := []Asset{}
if req, err := c.UniReq(DevicePath, ""); err != nil { if req, err := c.UniReq(DevicePath, ""); err != nil {
return nil, err return nil, nil, nil, err
} else if resp, err := c.Do(req); err != nil { } else if resp, err := c.Do(req); err != nil {
return nil, err return nil, nil, nil, err
} else if body, err := ioutil.ReadAll(resp.Body); err != nil { } else if body, err := ioutil.ReadAll(resp.Body); err != nil {
return nil, err return nil, nil, nil, err
} else if err = json.Unmarshal(body, &parsed); err != nil { } else if err = json.Unmarshal(body, &parsed); err != nil {
return nil, err return nil, nil, nil, err
} else if err = resp.Body.Close(); err != nil { } else if err = resp.Body.Close(); err != nil {
log.Println("resp.Body.Close():", err) // Not fatal? Just log it. log.Println("resp.Body.Close():", err) // Not fatal? Just log it.
} }
var usgs []USG
var usws []USW
var uaps []UAP
for _, r := range parsed.Data { for _, r := range parsed.Data {
var usg USG
var usw USW
var uap UAP
// Unamrshal into a map and check "type" // Unamrshal into a map and check "type"
var obj map[string]interface{} var obj map[string]interface{}
if err := json.Unmarshal(r, &obj); err != nil { if err := json.Unmarshal(r, &obj); err != nil {
return nil, err return nil, nil, nil, err
} }
assetType := "" assetType := "- missing -"
if t, ok := obj["type"].(string); ok { if t, ok := obj["type"].(string); ok {
assetType = t assetType = t
} }
if Debug {
log.Println("Unmarshalling Device Type:", assetType)
}
// Unmarshal again into the correct type.. // Unmarshal again into the correct type..
var asset Asset
switch assetType { switch assetType {
case "uap": case "uap":
asset = &UAP{} if err := json.Unmarshal(r, &uap); err != nil {
case "ugw": return nil, nil, nil, err
asset = &USG{} }
uaps = append(uaps, uap)
case "ugw", "usg": // in case they ever fix the name in the api.
if err := json.Unmarshal(r, &usg); err != nil {
return nil, nil, nil, err
}
usgs = append(usgs, usg)
case "usw": case "usw":
asset = &USW{} if err := json.Unmarshal(r, &usw); err != nil {
return nil, nil, nil, err
}
usws = append(usws, usw)
default: default:
log.Println("unknown asset type -", assetType, "- skipping") log.Println("unknown asset type -", assetType, "- skipping")
continue continue
} }
if Debug {
log.Println("Unmarshalling", assetType)
}
if err := json.Unmarshal(r, asset); err != nil {
return nil, err
}
assets = append(assets, asset)
} }
return assets, nil return usgs, usws, uaps, nil
}
// GetUnifiDeviceAssets provides an interface to return common asset types.
func (c *AuthedReq) GetUnifiDeviceAssets() ([]Asset, error) {
usgs, usws, uaps, err := c.GetUnifiDevices()
assets := []Asset{}
if err == nil {
for _, r := range usgs {
assets = append(assets, r)
}
for _, r := range usws {
assets = append(assets, r)
}
for _, r := range uaps {
assets = append(assets, r)
}
}
return assets, err
} }

View File

@ -7,8 +7,9 @@ import (
influx "github.com/influxdata/influxdb/client/v2" influx "github.com/influxdata/influxdb/client/v2"
) )
// Point generates a device's datapoint for InfluxDB. // Points generates a device's datapoints for InfluxDB.
func (u USG) Point() (*influx.Point, error) { func (u USG) Points() ([]*influx.Point, error) {
var points []*influx.Point
tags := map[string]string{ tags := map[string]string{
"id": u.ID, "id": u.ID,
"mac": u.Mac, "mac": u.Mac,
@ -117,5 +118,9 @@ func (u USG) Point() (*influx.Point, error) {
"wan-tx_bytes": u.Stat.WanTxBytes, "wan-tx_bytes": u.Stat.WanTxBytes,
"wan-tx_packets": u.Stat.WanTxPackets, "wan-tx_packets": u.Stat.WanTxPackets,
} }
return influx.NewPoint("usg", tags, fields, time.Now()) pt, err := influx.NewPoint("usg", tags, fields, time.Now())
if err == nil {
points = append(points, pt)
}
return points, err
} }

View File

@ -7,8 +7,9 @@ import (
influx "github.com/influxdata/influxdb/client/v2" influx "github.com/influxdata/influxdb/client/v2"
) )
// Point generates a device's datapoint for InfluxDB. // Points generates a device's datapoints for InfluxDB.
func (u USW) Point() (*influx.Point, error) { func (u USW) Points() ([]*influx.Point, error) {
var points []*influx.Point
tags := map[string]string{ tags := map[string]string{
"id": u.ID, "id": u.ID,
"mac": u.Mac, "mac": u.Mac,
@ -109,5 +110,9 @@ func (u USW) Point() (*influx.Point, error) {
"stat_tx_retries": u.Stat.TxRetries, "stat_tx_retries": u.Stat.TxRetries,
// Add the port stats too. // Add the port stats too.
} }
return influx.NewPoint("usw", tags, fields, time.Now()) pt, err := influx.NewPoint("usw", tags, fields, time.Now())
if err == nil {
points = append(points, pt)
}
return points, err
} }