commit
						0913a89d85
					
				|  | @ -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"` | ||||
|  | @ -46,7 +51,7 @@ type UAP struct { | |||
| 	IP              string  `json:"ip"` | ||||
| 	Isolated        bool    `json:"isolated"` | ||||
| 	KnownCfgversion string  `json:"known_cfgversion"` | ||||
| 	LastSeen        int    `json:"last_seen"` | ||||
| 	LastSeen        float64 `json:"last_seen"` | ||||
| 	LastUplink      struct { | ||||
| 		UplinkMac        string `json:"uplink_mac"` | ||||
| 		UplinkRemotePort int    `json:"uplink_remote_port"` | ||||
|  | @ -132,9 +137,9 @@ 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"` | ||||
| 		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"` | ||||
|  | @ -152,8 +157,8 @@ type UAP struct { | |||
| 		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,24 +289,24 @@ 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"` | ||||
|  | @ -314,28 +319,28 @@ type UAP struct { | |||
| 		IsGuest             bool    `json:"is_guest"` | ||||
| 		IsWep               bool    `json:"is_wep"` | ||||
| 		MacFilterRejections int     `json:"mac_filter_rejections"` | ||||
| 		MapID               interface{} `json:"map_id"` | ||||
| 		MapID               string  `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"` | ||||
| 		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             int         `json:"tx_bytes"` | ||||
| 		TxDropped           int         `json:"tx_dropped"` | ||||
| 		TxErrors            int         `json:"tx_errors"` | ||||
| 		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           int         `json:"tx_packets"` | ||||
| 		TxPackets           float64 `json:"tx_packets"` | ||||
| 		TxPower             int     `json:"tx_power"` | ||||
| 		TxRetries           int     `json:"tx_retries"` | ||||
| 		Up                  bool    `json:"up"` | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
| 	return 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 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 | ||||
| 	return usgs, usws, uaps, nil | ||||
| } | ||||
| 		assets = append(assets, asset) | ||||
| 
 | ||||
| // 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) | ||||
| 		} | ||||
| 	return assets, nil | ||||
| 		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