commit
7bd61d0be0
|
|
@ -3,3 +3,5 @@
|
|||
/*.1.gz
|
||||
/*.1
|
||||
/vendor
|
||||
.DS_Store
|
||||
*~
|
||||
|
|
|
|||
|
|
@ -1,12 +1,104 @@
|
|||
# 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.
|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
# 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 © 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.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import "time"
|
|||
|
||||
// Version will be injected at build time.
|
||||
var (
|
||||
Version = "v0.1"
|
||||
Version = "v0.2"
|
||||
Debug = false
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -83,14 +83,14 @@ func GetConfig(configFile string) (Config, error) {
|
|||
func (c *Config) PollUnifiController(infdb influx.Client, unifi *unidev.AuthedReq) {
|
||||
ticker := time.NewTicker(c.Interval.value)
|
||||
for range ticker.C {
|
||||
clients, err := unifi.GetUnifiClients()
|
||||
clients, err := unifi.GetUnifiClientAssets()
|
||||
if err != nil {
|
||||
log.Println("unifi.GetUnifiClients():", err)
|
||||
log.Println("unifi.GetUnifiClientsAssets():", err)
|
||||
continue
|
||||
}
|
||||
devices, err := unifi.GetUnifiDevices()
|
||||
devices, err := unifi.GetUnifiDeviceAssets()
|
||||
if err != nil {
|
||||
log.Println("unifi.GetUnifiDevices():", err)
|
||||
log.Println("unifi.GetUnifiDeviceAssets():", err)
|
||||
continue
|
||||
}
|
||||
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...) {
|
||||
if pt, errr := asset.Point(); errr != nil {
|
||||
log.Println("asset.Point():", errr)
|
||||
if pt, errr := asset.Points(); errr != nil {
|
||||
log.Println("asset.Points():", errr)
|
||||
} else {
|
||||
bp.AddPoint(pt)
|
||||
bp.AddPoints(pt)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
Before Width: | Height: | Size: 246 KiB After Width: | Height: | Size: 246 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
|
|
@ -7,8 +7,9 @@ import (
|
|||
influx "github.com/influxdata/influxdb/client/v2"
|
||||
)
|
||||
|
||||
// Point generates a client's datapoint for InfluxDB.
|
||||
func (u UCL) Point() (*influx.Point, error) {
|
||||
// Points generates a client's datapoints for InfluxDB.
|
||||
func (u UCL) Points() ([]*influx.Point, error) {
|
||||
var points []*influx.Point
|
||||
if u.Name == "" && u.Hostname != "" {
|
||||
u.Name = u.Hostname
|
||||
} 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_packets": u.WiredTxPackets,
|
||||
}
|
||||
|
||||
return influx.NewPoint("clients", tags, fields, time.Now())
|
||||
pt, err := influx.NewPoint("clients", tags, fields, time.Now())
|
||||
if err == nil {
|
||||
points = append(points, pt)
|
||||
}
|
||||
return points, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,12 @@ import (
|
|||
influx "github.com/influxdata/influxdb/client/v2"
|
||||
)
|
||||
|
||||
// Point generates a device's datapoint for InfluxDB.
|
||||
func (u UAP) Point() (*influx.Point, error) {
|
||||
// Points generates a device's datapoints for InfluxDB.
|
||||
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{
|
||||
"id": u.ID,
|
||||
"mac": u.Mac,
|
||||
|
|
@ -35,7 +39,7 @@ func (u UAP) Point() (*influx.Point, error) {
|
|||
"has_speaker": strconv.FormatBool(u.HasSpeaker),
|
||||
"inform_ip": u.InformIP,
|
||||
"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_remote_port": strconv.Itoa(u.LastUplink.UplinkRemotePort),
|
||||
"known_cfgversion": u.KnownCfgversion,
|
||||
|
|
@ -74,6 +78,7 @@ func (u UAP) Point() (*influx.Point, error) {
|
|||
"loadavg_15": u.SysStats.Loadavg15,
|
||||
"mem_buffer": u.SysStats.MemBuffer,
|
||||
"mem_total": u.SysStats.MemTotal,
|
||||
"mem_used": u.SysStats.MemUsed,
|
||||
"cpu": u.SystemStats.CPU,
|
||||
"mem": u.SystemStats.Mem,
|
||||
"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_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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
package unidev
|
||||
|
||||
// UAP is a Unifi Access Point
|
||||
// UAP is a Unifi Access Point.
|
||||
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"`
|
||||
UUptime float64 `json:"_uptime"`
|
||||
AdoptIP string `json:"adopt_ip,omitempty"`
|
||||
|
|
@ -36,17 +41,17 @@ type UAP struct {
|
|||
Name string `json:"name"`
|
||||
NumPort float64 `json:"num_port"`
|
||||
} `json:"ethernet_table"`
|
||||
FwCaps int `json:"fw_caps"`
|
||||
GuestNumSta int `json:"guest-num_sta"`
|
||||
GuestToken string `json:"guest_token"`
|
||||
HasEth1 bool `json:"has_eth1"`
|
||||
HasSpeaker bool `json:"has_speaker"`
|
||||
InformIP string `json:"inform_ip"`
|
||||
InformURL string `json:"inform_url"`
|
||||
IP string `json:"ip"`
|
||||
Isolated bool `json:"isolated"`
|
||||
KnownCfgversion string `json:"known_cfgversion"`
|
||||
LastSeen int `json:"last_seen"`
|
||||
FwCaps int `json:"fw_caps"`
|
||||
GuestNumSta int `json:"guest-num_sta"`
|
||||
GuestToken string `json:"guest_token"`
|
||||
HasEth1 bool `json:"has_eth1"`
|
||||
HasSpeaker bool `json:"has_speaker"`
|
||||
InformIP string `json:"inform_ip"`
|
||||
InformURL string `json:"inform_url"`
|
||||
IP string `json:"ip"`
|
||||
Isolated bool `json:"isolated"`
|
||||
KnownCfgversion string `json:"known_cfgversion"`
|
||||
LastSeen float64 `json:"last_seen"`
|
||||
LastUplink struct {
|
||||
UplinkMac string `json:"uplink_mac"`
|
||||
UplinkRemotePort int `json:"uplink_remote_port"`
|
||||
|
|
@ -132,28 +137,28 @@ type UAP struct {
|
|||
Is11Ac bool `json:"is_11ac,omitempty"`
|
||||
} `json:"radio_table"`
|
||||
RadioTableStats []struct {
|
||||
AstBeXmit interface{} `json:"ast_be_xmit"`
|
||||
AstCst interface{} `json:"ast_cst"`
|
||||
AstTxto interface{} `json:"ast_txto"`
|
||||
Channel float64 `json:"channel"`
|
||||
CuSelfRx float64 `json:"cu_self_rx"`
|
||||
CuSelfTx float64 `json:"cu_self_tx"`
|
||||
CuTotal float64 `json:"cu_total"`
|
||||
Extchannel float64 `json:"extchannel"`
|
||||
Gain float64 `json:"gain"`
|
||||
GuestNumSta float64 `json:"guest-num_sta"`
|
||||
Name string `json:"name"`
|
||||
NumSta float64 `json:"num_sta"`
|
||||
Radio string `json:"radio"`
|
||||
State string `json:"state"`
|
||||
TxPackets float64 `json:"tx_packets"`
|
||||
TxPower float64 `json:"tx_power"`
|
||||
TxRetries float64 `json:"tx_retries"`
|
||||
UserNumSta float64 `json:"user-num_sta"`
|
||||
AstBeXmit float64 `json:"ast_be_xmit"`
|
||||
AstCst float64 `json:"ast_cst"`
|
||||
AstTxto float64 `json:"ast_txto"`
|
||||
Channel float64 `json:"channel"`
|
||||
CuSelfRx float64 `json:"cu_self_rx"`
|
||||
CuSelfTx float64 `json:"cu_self_tx"`
|
||||
CuTotal float64 `json:"cu_total"`
|
||||
Extchannel float64 `json:"extchannel"`
|
||||
Gain float64 `json:"gain"`
|
||||
GuestNumSta float64 `json:"guest-num_sta"`
|
||||
Name string `json:"name"`
|
||||
NumSta float64 `json:"num_sta"`
|
||||
Radio string `json:"radio"`
|
||||
State string `json:"state"`
|
||||
TxPackets float64 `json:"tx_packets"`
|
||||
TxPower float64 `json:"tx_power"`
|
||||
TxRetries float64 `json:"tx_retries"`
|
||||
UserNumSta float64 `json:"user-num_sta"`
|
||||
} `json:"radio_table_stats"`
|
||||
Rollupgrade bool `json:"rollupgrade"`
|
||||
RxBytes int `json:"rx_bytes"`
|
||||
RxBytesD int `json:"rx_bytes-d"`
|
||||
RxBytes float64 `json:"rx_bytes"`
|
||||
RxBytesD float64 `json:"rx_bytes-d"`
|
||||
ScanRadioTable []interface{} `json:"scan_radio_table"`
|
||||
Scanning bool `json:"scanning"`
|
||||
Serial string `json:"serial"`
|
||||
|
|
@ -257,20 +262,20 @@ type UAP struct {
|
|||
} `json:"stat"`
|
||||
State int `json:"state"`
|
||||
SysStats struct {
|
||||
Loadavg1 string `json:"loadavg_1"`
|
||||
Loadavg15 string `json:"loadavg_15"`
|
||||
Loadavg5 string `json:"loadavg_5"`
|
||||
MemBuffer int `json:"mem_buffer"`
|
||||
MemTotal int `json:"mem_total"`
|
||||
MemUsed int `json:"mem_used"`
|
||||
Loadavg1 float64 `json:"loadavg_1,string"`
|
||||
Loadavg15 float64 `json:"loadavg_15,string"`
|
||||
Loadavg5 float64 `json:"loadavg_5,string"`
|
||||
MemBuffer float64 `json:"mem_buffer"`
|
||||
MemTotal float64 `json:"mem_total"`
|
||||
MemUsed float64 `json:"mem_used"`
|
||||
} `json:"sys_stats"`
|
||||
SystemStats struct {
|
||||
CPU string `json:"cpu"`
|
||||
Mem string `json:"mem"`
|
||||
Uptime string `json:"uptime"`
|
||||
CPU float64 `json:"cpu,string"`
|
||||
Mem float64 `json:"mem,string"`
|
||||
Uptime float64 `json:"uptime,string"`
|
||||
} `json:"system-stats"`
|
||||
TxBytes float64 `json:"tx_bytes"`
|
||||
TxBytesD int `json:"tx_bytes-d"`
|
||||
TxBytesD float64 `json:"tx_bytes-d"`
|
||||
Type string `json:"type"`
|
||||
Upgradable bool `json:"upgradable"`
|
||||
Uplink struct {
|
||||
|
|
@ -284,63 +289,63 @@ type UAP struct {
|
|||
Netmask string `json:"netmask"`
|
||||
NumPort int `json:"num_port"`
|
||||
RxBytes float64 `json:"rx_bytes"`
|
||||
RxBytesR int `json:"rx_bytes-r"`
|
||||
RxDropped int `json:"rx_dropped"`
|
||||
RxErrors int `json:"rx_errors"`
|
||||
RxMulticast int `json:"rx_multicast"`
|
||||
RxPackets int `json:"rx_packets"`
|
||||
Speed int `json:"speed"`
|
||||
RxBytesR float64 `json:"rx_bytes-r"`
|
||||
RxDropped float64 `json:"rx_dropped"`
|
||||
RxErrors float64 `json:"rx_errors"`
|
||||
RxMulticast float64 `json:"rx_multicast"`
|
||||
RxPackets float64 `json:"rx_packets"`
|
||||
Speed float64 `json:"speed"`
|
||||
TxBytes float64 `json:"tx_bytes"`
|
||||
TxBytesR int `json:"tx_bytes-r"`
|
||||
TxDropped int `json:"tx_dropped"`
|
||||
TxErrors int `json:"tx_errors"`
|
||||
TxPackets int `json:"tx_packets"`
|
||||
TxBytesR float64 `json:"tx_bytes-r"`
|
||||
TxDropped float64 `json:"tx_dropped"`
|
||||
TxErrors float64 `json:"tx_errors"`
|
||||
TxPackets float64 `json:"tx_packets"`
|
||||
Type string `json:"type"`
|
||||
Up bool `json:"up"`
|
||||
UplinkMac string `json:"uplink_mac"`
|
||||
UplinkRemotePort int `json:"uplink_remote_port"`
|
||||
} `json:"uplink"`
|
||||
UplinkTable []interface{} `json:"uplink_table"`
|
||||
Uptime int `json:"uptime"`
|
||||
Uptime float64 `json:"uptime"`
|
||||
UserNumSta int `json:"user-num_sta"`
|
||||
VapTable []struct {
|
||||
ApMac string `json:"ap_mac"`
|
||||
Bssid string `json:"bssid"`
|
||||
Ccq int `json:"ccq"`
|
||||
Channel int `json:"channel"`
|
||||
Essid string `json:"essid"`
|
||||
Extchannel int `json:"extchannel"`
|
||||
ID string `json:"id"`
|
||||
IsGuest bool `json:"is_guest"`
|
||||
IsWep bool `json:"is_wep"`
|
||||
MacFilterRejections int `json:"mac_filter_rejections"`
|
||||
MapID interface{} `json:"map_id"`
|
||||
Name string `json:"name"`
|
||||
NumSta int `json:"num_sta"`
|
||||
Radio string `json:"radio"`
|
||||
RadioName string `json:"radio_name"`
|
||||
RxBytes int `json:"rx_bytes"`
|
||||
RxCrypts int `json:"rx_crypts"`
|
||||
RxDropped int `json:"rx_dropped"`
|
||||
RxErrors int `json:"rx_errors"`
|
||||
RxFrags int `json:"rx_frags"`
|
||||
RxNwids int `json:"rx_nwids"`
|
||||
RxPackets int `json:"rx_packets"`
|
||||
SiteID string `json:"site_id"`
|
||||
State string `json:"state"`
|
||||
T string `json:"t"`
|
||||
TxBytes int `json:"tx_bytes"`
|
||||
TxDropped int `json:"tx_dropped"`
|
||||
TxErrors int `json:"tx_errors"`
|
||||
TxLatencyAvg float64 `json:"tx_latency_avg"`
|
||||
TxLatencyMax float64 `json:"tx_latency_max"`
|
||||
TxLatencyMin float64 `json:"tx_latency_min"`
|
||||
TxPackets int `json:"tx_packets"`
|
||||
TxPower int `json:"tx_power"`
|
||||
TxRetries int `json:"tx_retries"`
|
||||
Up bool `json:"up"`
|
||||
Usage string `json:"usage"`
|
||||
WlanconfID string `json:"wlanconf_id"`
|
||||
ApMac string `json:"ap_mac"`
|
||||
Bssid string `json:"bssid"`
|
||||
Ccq int `json:"ccq"`
|
||||
Channel int `json:"channel"`
|
||||
Essid string `json:"essid"`
|
||||
Extchannel int `json:"extchannel"`
|
||||
ID string `json:"id"`
|
||||
IsGuest bool `json:"is_guest"`
|
||||
IsWep bool `json:"is_wep"`
|
||||
MacFilterRejections int `json:"mac_filter_rejections"`
|
||||
MapID string `json:"map_id"`
|
||||
Name string `json:"name"`
|
||||
NumSta int `json:"num_sta"`
|
||||
Radio string `json:"radio"`
|
||||
RadioName string `json:"radio_name"`
|
||||
RxBytes float64 `json:"rx_bytes"`
|
||||
RxCrypts float64 `json:"rx_crypts"`
|
||||
RxDropped float64 `json:"rx_dropped"`
|
||||
RxErrors float64 `json:"rx_errors"`
|
||||
RxFrags float64 `json:"rx_frags"`
|
||||
RxNwids float64 `json:"rx_nwids"`
|
||||
RxPackets float64 `json:"rx_packets"`
|
||||
SiteID string `json:"site_id"`
|
||||
State string `json:"state"`
|
||||
T string `json:"t"`
|
||||
TxBytes float64 `json:"tx_bytes"`
|
||||
TxDropped float64 `json:"tx_dropped"`
|
||||
TxErrors float64 `json:"tx_errors"`
|
||||
TxLatencyAvg float64 `json:"tx_latency_avg"`
|
||||
TxLatencyMax float64 `json:"tx_latency_max"`
|
||||
TxLatencyMin float64 `json:"tx_latency_min"`
|
||||
TxPackets float64 `json:"tx_packets"`
|
||||
TxPower int `json:"tx_power"`
|
||||
TxRetries int `json:"tx_retries"`
|
||||
Up bool `json:"up"`
|
||||
Usage string `json:"usage"`
|
||||
WlanconfID string `json:"wlanconf_id"`
|
||||
} `json:"vap_table"`
|
||||
Version string `json:"version"`
|
||||
VersionIncompatible bool `json:"version_incompatible"`
|
||||
|
|
|
|||
|
|
@ -13,9 +13,12 @@ import (
|
|||
const LoginPath = "/api/login"
|
||||
|
||||
// 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 {
|
||||
// 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.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const (
|
|||
)
|
||||
|
||||
// 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 {
|
||||
Clients []UCL `json:"data"`
|
||||
Meta struct {
|
||||
|
|
@ -34,68 +34,103 @@ func (c *AuthedReq) GetUnifiClients() ([]Asset, error) {
|
|||
return nil, err
|
||||
} else if body, err := ioutil.ReadAll(resp.Body); err != nil {
|
||||
return nil, err
|
||||
} else if err = json.Unmarshal(body, response); err != nil {
|
||||
} else if err = json.Unmarshal(body, &response); err != nil {
|
||||
return nil, err
|
||||
} else if err = resp.Body.Close(); err != nil {
|
||||
log.Println("resp.Body.Close():", err) // Not fatal? Just log it.
|
||||
}
|
||||
clients := []Asset{}
|
||||
for _, r := range response.Clients {
|
||||
clients = append(clients, r)
|
||||
return response.Clients, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *AuthedReq) GetUnifiDevices() ([]Asset, error) {
|
||||
func (c *AuthedReq) GetUnifiDevices() ([]USG, []USW, []UAP, error) {
|
||||
var parsed struct {
|
||||
Data []json.RawMessage `json:"data"`
|
||||
Meta struct {
|
||||
Rc string `json:"rc"`
|
||||
} `json:"meta"`
|
||||
}
|
||||
assets := []Asset{}
|
||||
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 {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
} 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 {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
} else if err = resp.Body.Close(); err != nil {
|
||||
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 {
|
||||
var usg USG
|
||||
var usw USW
|
||||
var uap UAP
|
||||
// Unamrshal into a map and check "type"
|
||||
var obj map[string]interface{}
|
||||
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 {
|
||||
assetType = t
|
||||
}
|
||||
if Debug {
|
||||
log.Println("Unmarshalling Device Type:", assetType)
|
||||
}
|
||||
// Unmarshal again into the correct type..
|
||||
var asset Asset
|
||||
switch assetType {
|
||||
case "uap":
|
||||
asset = &UAP{}
|
||||
case "ugw":
|
||||
asset = &USG{}
|
||||
if err := json.Unmarshal(r, &uap); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
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":
|
||||
asset = &USW{}
|
||||
if err := json.Unmarshal(r, &usw); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
usws = append(usws, usw)
|
||||
default:
|
||||
log.Println("unknown asset type -", assetType, "- skipping")
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import (
|
|||
influx "github.com/influxdata/influxdb/client/v2"
|
||||
)
|
||||
|
||||
// Point generates a device's datapoint for InfluxDB.
|
||||
func (u USG) Point() (*influx.Point, error) {
|
||||
// Points generates a device's datapoints for InfluxDB.
|
||||
func (u USG) Points() ([]*influx.Point, error) {
|
||||
var points []*influx.Point
|
||||
tags := map[string]string{
|
||||
"id": u.ID,
|
||||
"mac": u.Mac,
|
||||
|
|
@ -117,5 +118,9 @@ func (u USG) Point() (*influx.Point, error) {
|
|||
"wan-tx_bytes": u.Stat.WanTxBytes,
|
||||
"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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import (
|
|||
influx "github.com/influxdata/influxdb/client/v2"
|
||||
)
|
||||
|
||||
// Point generates a device's datapoint for InfluxDB.
|
||||
func (u USW) Point() (*influx.Point, error) {
|
||||
// Points generates a device's datapoints for InfluxDB.
|
||||
func (u USW) Points() ([]*influx.Point, error) {
|
||||
var points []*influx.Point
|
||||
tags := map[string]string{
|
||||
"id": u.ID,
|
||||
"mac": u.Mac,
|
||||
|
|
@ -109,5 +110,9 @@ func (u USW) Point() (*influx.Point, error) {
|
|||
"stat_tx_retries": u.Stat.TxRetries,
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue