Merge branch 'master' of ../unifi into merge-them-all
This commit is contained in:
		
						commit
						8deb07970c
					
				|  | @ -0,0 +1,2 @@ | |||
| /vendor | ||||
| *.swp | ||||
|  | @ -0,0 +1,9 @@ | |||
| language: go | ||||
| go: | ||||
| - 1.16.x | ||||
| before_install: | ||||
|   # download super-linter: golangci-lint | ||||
| - curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.38.0 | ||||
| script: | ||||
| - golangci-lint run --enable-all -D exhaustivestruct,nlreturn,wrapcheck,maligned,interfacer | ||||
| - go test ./... | ||||
|  | @ -0,0 +1,22 @@ | |||
| MIT LICENSE. | ||||
| Copyright (c) 2018-2020 David Newhall II | ||||
| Copyright (c) 2016 Garrett Bjerkhoel | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of this software and associated documentation files (the | ||||
| "Software"), to deal in the Software without restriction, including | ||||
| without limitation the rights to use, copy, modify, merge, publish, | ||||
| distribute, sublicense, and/or sell copies of the Software, and to | ||||
| permit persons to whom the Software is furnished to do so, subject to | ||||
| the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | @ -0,0 +1,64 @@ | |||
| # Go Library: `unifi` | ||||
| 
 | ||||
| It connects to a Unifi Controller, given a url, username and password. Returns | ||||
| an authenticated http Client you may use to query the device for data. Also | ||||
| contains some built-in methods for de-serializing common client and device | ||||
| data. The data is provided in a large struct you can consume in your application. | ||||
| 
 | ||||
| This library is designed to PULL data FROM the controller. It has no methods that | ||||
| update settings or change things on the controller. | ||||
| [Someone expressed interest](https://github.com/unpoller/unifi/issues/31) in | ||||
| adding methods to update data, and I'm okay with that. I'll even help add them. | ||||
| [Tell me what you want to do](https://github.com/unpoller/unifi/issues/new), and we'll make it happen. | ||||
| 
 | ||||
| Pull requests, feature requests, code reviews and feedback are welcomed! | ||||
| 
 | ||||
| Here's a working example: | ||||
| ```golang | ||||
| package main | ||||
| 
 | ||||
| import "log" | ||||
| import "github.com/unpoller/unifi" | ||||
| 
 | ||||
| func main() { | ||||
| 	c := *unifi.Config{ | ||||
| 		User: "admin", | ||||
| 		Pass: "superSecret1234", | ||||
| 		URL:  "https://127.0.0.1:8443/", | ||||
| 		// Log with log.Printf or make your own interface that accepts (msg, fmt) | ||||
| 		ErrorLog: log.Printf, | ||||
| 		DebugLog: log.Printf, | ||||
| 	} | ||||
| 	uni, err := unifi.NewUnifi(c) | ||||
| 	if err != nil { | ||||
| 		log.Fatalln("Error:", err) | ||||
| 	} | ||||
| 
 | ||||
| 	sites, err := uni.GetSites() | ||||
| 	if err != nil { | ||||
| 		log.Fatalln("Error:", err) | ||||
| 	} | ||||
| 	clients, err := uni.GetClients(sites) | ||||
| 	if err != nil { | ||||
| 		log.Fatalln("Error:", err) | ||||
| 	} | ||||
| 	devices, err := uni.GetDevices(sites) | ||||
| 	if err != nil { | ||||
| 		log.Fatalln("Error:", err) | ||||
| 	} | ||||
| 
 | ||||
| 	log.Println(len(sites), "Unifi Sites Found: ", sites) | ||||
| 	log.Println(len(clients), "Clients connected:") | ||||
| 	for i, client := range clients { | ||||
| 		log.Println(i+1, client.ID, client.Hostname, client.IP, client.Name, client.LastSeen) | ||||
| 	} | ||||
| 
 | ||||
| 	log.Println(len(devices.USWs), "Unifi Switches Found") | ||||
| 	log.Println(len(devices.USGs), "Unifi Gateways Found") | ||||
| 
 | ||||
| 	log.Println(len(devices.UAPs), "Unifi Wireless APs Found:") | ||||
| 	for i, uap := range devices.UAPs { | ||||
| 		log.Println(i+1, uap.Name, uap.IP) | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
|  | @ -0,0 +1,120 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type Alarm struct { | ||||
| 	Archived              FlexBool  `json:"archived"` | ||||
| 	DestPort              int       `json:"dest_port"` | ||||
| 	SrcPort               int       `json:"src_port"` | ||||
| 	FlowID                int64     `json:"flow_id"` | ||||
| 	InnerAlertGID         int64     `json:"inner_alert_gid"` | ||||
| 	InnerAlertRev         int64     `json:"inner_alert_rev"` | ||||
| 	InnerAlertSeverity    int64     `json:"inner_alert_severity"` | ||||
| 	InnerAlertSignatureID int64     `json:"inner_alert_signature_id"` | ||||
| 	Time                  int64     `json:"time"` | ||||
| 	Timestamp             int64     `json:"timestamp"` | ||||
| 	Datetime              time.Time `json:"datetime"` | ||||
| 	HandledTime           time.Time `json:"handled_time,omitempty"` | ||||
| 	AppProto              string    `json:"app_proto,omitempty"` | ||||
| 	Catname               string    `json:"catname"` | ||||
| 	DestIP                string    `json:"dest_ip"` | ||||
| 	DstMAC                string    `json:"dst_mac"` | ||||
| 	DstIPASN              string    `json:"dstipASN,omitempty"` | ||||
| 	DstIPCountry          string    `json:"dstipCountry,omitempty"` | ||||
| 	EventType             string    `json:"event_type"` | ||||
| 	HandledAdminID        string    `json:"handled_admin_id,omitempty"` | ||||
| 	Host                  string    `json:"host"` | ||||
| 	ID                    string    `json:"_id"` | ||||
| 	InIface               string    `json:"in_iface"` | ||||
| 	InnerAlertAction      string    `json:"inner_alert_action"` | ||||
| 	InnerAlertCategory    string    `json:"inner_alert_category"` | ||||
| 	InnerAlertSignature   string    `json:"inner_alert_signature"` | ||||
| 	Key                   string    `json:"key"` | ||||
| 	Msg                   string    `json:"msg"` | ||||
| 	Proto                 string    `json:"proto"` | ||||
| 	SiteID                string    `json:"site_id"` | ||||
| 	SiteName              string    `json:"-"` | ||||
| 	SourceName            string    `json:"-"` | ||||
| 	SrcIP                 string    `json:"src_ip"` | ||||
| 	SrcIPASN              string    `json:"srcipASN,omitempty"` | ||||
| 	SrcIPCountry          string    `json:"srcipCountry,omitempty"` | ||||
| 	SrcMAC                string    `json:"src_mac"` | ||||
| 	Subsystem             string    `json:"subsystem"` | ||||
| 	UniqueAlertID         string    `json:"unique_alertid"` | ||||
| 	USGIP                 string    `json:"usgip"` | ||||
| 	USGIPASN              string    `json:"usgipASN"` | ||||
| 	USGIPCountry          string    `json:"usgipCountry"` | ||||
| 	TxID                  FlexInt   `json:"tx_id,omitempty"` | ||||
| 	DestIPGeo             IPGeo     `json:"dstipGeo"` | ||||
| 	SourceIPGeo           IPGeo     `json:"usgipGeo"` | ||||
| 	USGIPGeo              IPGeo     `json:"srcipGeo,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // GetAlarms returns Alarms for a list of Sites.
 | ||||
| func (u *Unifi) GetAlarms(sites []*Site) ([]*Alarm, error) { | ||||
| 	data := []*Alarm{} | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		response, err := u.GetAlarmsSite(site) | ||||
| 		if err != nil { | ||||
| 			return data, err | ||||
| 		} | ||||
| 
 | ||||
| 		data = append(data, response...) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // GetAlarmsSite retreives the Alarms for a single Site.
 | ||||
| func (u *Unifi) GetAlarmsSite(site *Site) ([]*Alarm, error) { | ||||
| 	if site == nil || site.Name == "" { | ||||
| 		return nil, ErrNoSiteProvided | ||||
| 	} | ||||
| 
 | ||||
| 	u.DebugLog("Polling Controller for Alarms, site %s", site.SiteName) | ||||
| 
 | ||||
| 	var ( | ||||
| 		path   = fmt.Sprintf(APIEventPathAlarms, site.Name) | ||||
| 		alarms struct { | ||||
| 			Data alarms `json:"data"` | ||||
| 		} | ||||
| 	) | ||||
| 
 | ||||
| 	if err := u.GetData(path, &alarms, ""); err != nil { | ||||
| 		return alarms.Data, err | ||||
| 	} | ||||
| 
 | ||||
| 	for i := range alarms.Data { | ||||
| 		// Add special SourceName value.
 | ||||
| 		alarms.Data[i].SourceName = u.URL | ||||
| 		// Add the special "Site Name" to each event. This becomes a Grafana filter somewhere.
 | ||||
| 		alarms.Data[i].SiteName = site.SiteName | ||||
| 	} | ||||
| 
 | ||||
| 	sort.Sort(alarms.Data) | ||||
| 
 | ||||
| 	return alarms.Data, nil | ||||
| } | ||||
| 
 | ||||
| // alarms satisfies the sort.Sort Interface.
 | ||||
| type alarms []*Alarm | ||||
| 
 | ||||
| // Len satisfies sort.Interface.
 | ||||
| func (a alarms) Len() int { | ||||
| 	return len(a) | ||||
| } | ||||
| 
 | ||||
| // Swap satisfies sort.Interface.
 | ||||
| func (a alarms) Swap(i, j int) { | ||||
| 	a[i], a[j] = a[j], a[i] | ||||
| } | ||||
| 
 | ||||
| // Less satisfies sort.Interface. Sort our list by Datetime.
 | ||||
| func (a alarms) Less(i, j int) bool { | ||||
| 	return a[i].Datetime.Before(a[j].Datetime) | ||||
| } | ||||
|  | @ -0,0 +1,130 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // anomaly is the type UniFi returns, but not the type this library returns.
 | ||||
| type anomaly struct { | ||||
| 	Anomaly    string  `json:"anomaly"` | ||||
| 	MAC        string  `json:"mac"` | ||||
| 	Timestamps []int64 `json:"timestamps"` | ||||
| } | ||||
| 
 | ||||
| // Anomaly is the reformatted data type that this library returns.
 | ||||
| type Anomaly struct { | ||||
| 	Datetime   time.Time | ||||
| 	SourceName string | ||||
| 	SiteName   string | ||||
| 	Anomaly    string | ||||
| 	DeviceMAC  string | ||||
| 	//	DeviceName string // we do not have this....
 | ||||
| } | ||||
| 
 | ||||
| // GetAnomalies returns Anomalies for a list of Sites.
 | ||||
| func (u *Unifi) GetAnomalies(sites []*Site, timeRange ...time.Time) ([]*Anomaly, error) { | ||||
| 	data := []*Anomaly{} | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		response, err := u.GetAnomaliesSite(site, timeRange...) | ||||
| 		if err != nil { | ||||
| 			return data, err | ||||
| 		} | ||||
| 
 | ||||
| 		data = append(data, response...) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // GetAnomaliesSite retreives the Anomalies for a single Site.
 | ||||
| func (u *Unifi) GetAnomaliesSite(site *Site, timeRange ...time.Time) ([]*Anomaly, error) { | ||||
| 	if site == nil || site.Name == "" { | ||||
| 		return nil, ErrNoSiteProvided | ||||
| 	} | ||||
| 
 | ||||
| 	u.DebugLog("Polling Controller for Anomalies, site %s", site.SiteName) | ||||
| 
 | ||||
| 	var ( | ||||
| 		path      = fmt.Sprintf(APIAnomaliesPath, site.Name) | ||||
| 		anomalies = anomalies{} | ||||
| 		data      struct { | ||||
| 			Data []*anomaly `json:"data"` | ||||
| 		} | ||||
| 	) | ||||
| 
 | ||||
| 	if params, err := makeAnomalyParams("hourly", timeRange...); err != nil { | ||||
| 		return anomalies, err | ||||
| 	} else if err := u.GetData(path+params, &data, ""); err != nil { | ||||
| 		return anomalies, err | ||||
| 	} | ||||
| 
 | ||||
| 	for _, d := range data.Data { | ||||
| 		for _, ts := range d.Timestamps { | ||||
| 			anomalies = append(anomalies, &Anomaly{ | ||||
| 				Datetime:   time.Unix(ts/int64(time.Microsecond), 0), | ||||
| 				SourceName: u.URL, | ||||
| 				SiteName:   site.SiteName, | ||||
| 				Anomaly:    d.Anomaly, | ||||
| 				DeviceMAC:  d.MAC, | ||||
| 				//				DeviceName: d.Anomaly,
 | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	sort.Sort(anomalies) | ||||
| 
 | ||||
| 	return anomalies, nil | ||||
| } | ||||
| 
 | ||||
| // anomalies satisfies the sort.Sort interface.
 | ||||
| type anomalies []*Anomaly | ||||
| 
 | ||||
| // Len satisfies sort.Interface.
 | ||||
| func (a anomalies) Len() int { | ||||
| 	return len(a) | ||||
| } | ||||
| 
 | ||||
| // Swap satisfies sort.Interface.
 | ||||
| func (a anomalies) Swap(i, j int) { | ||||
| 	a[i], a[j] = a[j], a[i] | ||||
| } | ||||
| 
 | ||||
| // Less satisfies sort.Interface. Sort our list by Datetime.
 | ||||
| func (a anomalies) Less(i, j int) bool { | ||||
| 	return a[i].Datetime.Before(a[j].Datetime) | ||||
| } | ||||
| 
 | ||||
| func makeAnomalyParams(scale string, timeRange ...time.Time) (string, error) { | ||||
| 	out := []string{} | ||||
| 
 | ||||
| 	if scale != "" { | ||||
| 		out = append(out, "scale="+scale) | ||||
| 	} | ||||
| 
 | ||||
| 	switch len(timeRange) { | ||||
| 	case 0: | ||||
| 		end := time.Now().Unix() * int64(time.Microsecond) | ||||
| 		out = append(out, "end="+strconv.FormatInt(end, 10)) | ||||
| 	case 1: | ||||
| 		start := timeRange[0].Unix() * int64(time.Microsecond) | ||||
| 		end := time.Now().Unix() * int64(time.Microsecond) | ||||
| 		out = append(out, "end="+strconv.FormatInt(end, 10), "start="+strconv.FormatInt(start, 10)) | ||||
| 	case 2: // nolint: gomnd
 | ||||
| 		start := timeRange[0].Unix() * int64(time.Microsecond) | ||||
| 		end := timeRange[1].Unix() * int64(time.Microsecond) | ||||
| 		out = append(out, "end="+strconv.FormatInt(end, 10), "start="+strconv.FormatInt(start, 10)) | ||||
| 	default: | ||||
| 		return "", ErrInvalidTimeRange | ||||
| 	} | ||||
| 
 | ||||
| 	if len(out) == 0 { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 
 | ||||
| 	return "?" + strings.Join(out, "&"), nil | ||||
| } | ||||
|  | @ -0,0 +1,154 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // GetClients returns a response full of clients' data from the UniFi Controller.
 | ||||
| func (u *Unifi) GetClients(sites []*Site) ([]*Client, error) { | ||||
| 	data := make([]*Client, 0) | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		var response struct { | ||||
| 			Data []*Client `json:"data"` | ||||
| 		} | ||||
| 
 | ||||
| 		u.DebugLog("Polling Controller, retreiving UniFi Clients, site %s ", site.SiteName) | ||||
| 
 | ||||
| 		clientPath := fmt.Sprintf(APIClientPath, site.Name) | ||||
| 		if err := u.GetData(clientPath, &response); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		for i, d := range response.Data { | ||||
| 			// Add special SourceName value.
 | ||||
| 			response.Data[i].SourceName = u.URL | ||||
| 			// Add the special "Site Name" to each client. This becomes a Grafana filter somewhere.
 | ||||
| 			response.Data[i].SiteName = site.SiteName | ||||
| 			// Fix name and hostname fields. Sometimes one or the other is blank.
 | ||||
| 			response.Data[i].Hostname = strings.TrimSpace(pick(d.Hostname, d.Name, d.Mac)) | ||||
| 			response.Data[i].Name = strings.TrimSpace(pick(d.Name, d.Hostname)) | ||||
| 		} | ||||
| 
 | ||||
| 		data = append(data, response.Data...) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // GetClientsDPI garners dpi data for clients.
 | ||||
| func (u *Unifi) GetClientsDPI(sites []*Site) ([]*DPITable, error) { | ||||
| 	var data []*DPITable | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		u.DebugLog("Polling Controller, retreiving Client DPI data, site %s", site.SiteName) | ||||
| 
 | ||||
| 		var response struct { | ||||
| 			Data []*DPITable `json:"data"` | ||||
| 		} | ||||
| 
 | ||||
| 		clientDPIpath := fmt.Sprintf(APIClientDPI, site.Name) | ||||
| 		if err := u.GetData(clientDPIpath, &response, `{"type":"by_app"}`); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		for _, d := range response.Data { | ||||
| 			d.SourceName = site.SourceName | ||||
| 			d.SiteName = site.SiteName | ||||
| 			data = append(data, d) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // Client defines all the data a connected-network client contains.
 | ||||
| type Client struct { | ||||
| 	SourceName       string   `json:"-"` | ||||
| 	Anomalies        int64    `json:"anomalies,omitempty"` | ||||
| 	ApMac            string   `json:"ap_mac"` | ||||
| 	ApName           string   `json:"-"` | ||||
| 	AssocTime        int64    `json:"assoc_time"` | ||||
| 	Blocked          bool     `json:"blocked,omitempty"` | ||||
| 	Bssid            string   `json:"bssid"` | ||||
| 	BytesR           int64    `json:"bytes-r"` | ||||
| 	Ccq              int64    `json:"ccq"` | ||||
| 	Channel          FlexInt  `json:"channel"` | ||||
| 	DevCat           FlexInt  `json:"dev_cat"` | ||||
| 	DevFamily        FlexInt  `json:"dev_family"` | ||||
| 	DevID            FlexInt  `json:"dev_id"` | ||||
| 	DevVendor        FlexInt  `json:"dev_vendor,omitempty"` | ||||
| 	DhcpendTime      FlexInt  `json:"dhcpend_time,omitempty"` | ||||
| 	Satisfaction     FlexInt  `json:"satisfaction,omitempty"` | ||||
| 	Essid            string   `json:"essid"` | ||||
| 	FirstSeen        int64    `json:"first_seen"` | ||||
| 	FixedIP          string   `json:"fixed_ip"` | ||||
| 	GwMac            string   `json:"gw_mac"` | ||||
| 	GwName           string   `json:"-"` | ||||
| 	Hostname         string   `json:"hostname"` | ||||
| 	ID               string   `json:"_id"` | ||||
| 	IP               string   `json:"ip"` | ||||
| 	IdleTime         int64    `json:"idle_time"` | ||||
| 	Is11R            FlexBool `json:"is_11r"` | ||||
| 	IsGuest          FlexBool `json:"is_guest"` | ||||
| 	IsGuestByUAP     FlexBool `json:"_is_guest_by_uap"` | ||||
| 	IsGuestByUGW     FlexBool `json:"_is_guest_by_ugw"` | ||||
| 	IsGuestByUSW     FlexBool `json:"_is_guest_by_usw"` | ||||
| 	IsWired          FlexBool `json:"is_wired"` | ||||
| 	LastSeen         int64    `json:"last_seen"` | ||||
| 	LastSeenByUAP    int64    `json:"_last_seen_by_uap"` | ||||
| 	LastSeenByUGW    int64    `json:"_last_seen_by_ugw"` | ||||
| 	LastSeenByUSW    int64    `json:"_last_seen_by_usw"` | ||||
| 	LatestAssocTime  int64    `json:"latest_assoc_time"` | ||||
| 	Mac              string   `json:"mac"` | ||||
| 	Name             string   `json:"name"` | ||||
| 	Network          string   `json:"network"` | ||||
| 	NetworkID        string   `json:"network_id"` | ||||
| 	Noise            int64    `json:"noise"` | ||||
| 	Note             string   `json:"note"` | ||||
| 	Noted            FlexBool `json:"noted"` | ||||
| 	OsClass          FlexInt  `json:"os_class"` | ||||
| 	OsName           FlexInt  `json:"os_name"` | ||||
| 	Oui              string   `json:"oui"` | ||||
| 	PowersaveEnabled FlexBool `json:"powersave_enabled"` | ||||
| 	QosPolicyApplied FlexBool `json:"qos_policy_applied"` | ||||
| 	Radio            string   `json:"radio"` | ||||
| 	RadioName        string   `json:"radio_name"` | ||||
| 	RadioProto       string   `json:"radio_proto"` | ||||
| 	RadioDescription string   `json:"-"` | ||||
| 	RoamCount        int64    `json:"roam_count"` | ||||
| 	Rssi             int64    `json:"rssi"` | ||||
| 	RxBytes          int64    `json:"rx_bytes"` | ||||
| 	RxBytesR         int64    `json:"rx_bytes-r"` | ||||
| 	RxPackets        int64    `json:"rx_packets"` | ||||
| 	RxRate           int64    `json:"rx_rate"` | ||||
| 	Signal           int64    `json:"signal"` | ||||
| 	SiteID           string   `json:"site_id"` | ||||
| 	SiteName         string   `json:"-"` | ||||
| 	SwDepth          int      `json:"sw_depth"` | ||||
| 	SwMac            string   `json:"sw_mac"` | ||||
| 	SwName           string   `json:"-"` | ||||
| 	SwPort           FlexInt  `json:"sw_port"` | ||||
| 	TxBytes          int64    `json:"tx_bytes"` | ||||
| 	TxBytesR         int64    `json:"tx_bytes-r"` | ||||
| 	TxPackets        int64    `json:"tx_packets"` | ||||
| 	TxRetries        int64    `json:"tx_retries"` | ||||
| 	TxPower          int64    `json:"tx_power"` | ||||
| 	TxRate           int64    `json:"tx_rate"` | ||||
| 	Uptime           int64    `json:"uptime"` | ||||
| 	UptimeByUAP      int64    `json:"_uptime_by_uap"` | ||||
| 	UptimeByUGW      int64    `json:"_uptime_by_ugw"` | ||||
| 	UptimeByUSW      int64    `json:"_uptime_by_usw"` | ||||
| 	UseFixedIP       FlexBool `json:"use_fixedip"` | ||||
| 	UserGroupID      string   `json:"usergroup_id"` | ||||
| 	UserID           string   `json:"user_id"` | ||||
| 	Vlan             FlexInt  `json:"vlan"` | ||||
| 	WifiTxAttempts   int64    `json:"wifi_tx_attempts"` | ||||
| 	WiredRxBytes     int64    `json:"wired-rx_bytes"` | ||||
| 	WiredRxBytesR    int64    `json:"wired-rx_bytes-r"` | ||||
| 	WiredRxPackets   int64    `json:"wired-rx_packets"` | ||||
| 	WiredTxBytes     int64    `json:"wired-tx_bytes"` | ||||
| 	WiredTxBytesR    int64    `json:"wired-tx_bytes-r"` | ||||
| 	WiredTxPackets   int64    `json:"wired-tx_packets"` | ||||
| } | ||||
|  | @ -0,0 +1,213 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // GetDevices returns a response full of devices' data from the UniFi Controller.
 | ||||
| func (u *Unifi) GetDevices(sites []*Site) (*Devices, error) { | ||||
| 	devices := new(Devices) | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		var response struct { | ||||
| 			Data []json.RawMessage `json:"data"` | ||||
| 		} | ||||
| 
 | ||||
| 		devicePath := fmt.Sprintf(APIDevicePath, site.Name) | ||||
| 		if err := u.GetData(devicePath, &response); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		loopDevices := u.parseDevices(response.Data, site) | ||||
| 		devices.UAPs = append(devices.UAPs, loopDevices.UAPs...) | ||||
| 		devices.USGs = append(devices.USGs, loopDevices.USGs...) | ||||
| 		devices.USWs = append(devices.USWs, loopDevices.USWs...) | ||||
| 		devices.UDMs = append(devices.UDMs, loopDevices.UDMs...) | ||||
| 		devices.UXGs = append(devices.UXGs, loopDevices.UXGs...) | ||||
| 	} | ||||
| 
 | ||||
| 	return devices, nil | ||||
| } | ||||
| 
 | ||||
| // GetUSWs returns all switches, an error, or nil if there are no switches.
 | ||||
| func (u *Unifi) GetUSWs(site *Site) ([]*USW, error) { | ||||
| 	var response struct { | ||||
| 		Data []json.RawMessage `json:"data"` | ||||
| 	} | ||||
| 
 | ||||
| 	err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return u.parseDevices(response.Data, site).USWs, nil | ||||
| } | ||||
| 
 | ||||
| // GetUAPs returns all access points, an error, or nil if there are no APs.
 | ||||
| func (u *Unifi) GetUAPs(site *Site) ([]*UAP, error) { | ||||
| 	var response struct { | ||||
| 		Data []json.RawMessage `json:"data"` | ||||
| 	} | ||||
| 
 | ||||
| 	err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return u.parseDevices(response.Data, site).UAPs, nil | ||||
| } | ||||
| 
 | ||||
| // GetUDMs returns all dream machines, an error, or nil if there are no UDMs.
 | ||||
| func (u *Unifi) GetUDMs(site *Site) ([]*UDM, error) { | ||||
| 	var response struct { | ||||
| 		Data []json.RawMessage `json:"data"` | ||||
| 	} | ||||
| 
 | ||||
| 	err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return u.parseDevices(response.Data, site).UDMs, nil | ||||
| } | ||||
| 
 | ||||
| // GetUXGs returns all 10Gb gateways, an error, or nil if there are no UXGs.
 | ||||
| func (u *Unifi) GetUXGs(site *Site) ([]*UXG, error) { | ||||
| 	var response struct { | ||||
| 		Data []json.RawMessage `json:"data"` | ||||
| 	} | ||||
| 
 | ||||
| 	err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return u.parseDevices(response.Data, site).UXGs, nil | ||||
| } | ||||
| 
 | ||||
| // GetUSGs returns all 1Gb gateways, an error, or nil if there are no USGs.
 | ||||
| func (u *Unifi) GetUSGs(site *Site) ([]*USG, error) { | ||||
| 	var response struct { | ||||
| 		Data []json.RawMessage `json:"data"` | ||||
| 	} | ||||
| 
 | ||||
| 	err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return u.parseDevices(response.Data, site).USGs, nil | ||||
| } | ||||
| 
 | ||||
| // parseDevices parses the raw JSON from the Unifi Controller into device structures.
 | ||||
| func (u *Unifi) parseDevices(data []json.RawMessage, site *Site) *Devices { | ||||
| 	devices := new(Devices) | ||||
| 
 | ||||
| 	for _, r := range data { | ||||
| 		// Loop each item in the raw JSON message, detect its type and unmarshal it.
 | ||||
| 		o := make(map[string]interface{}) | ||||
| 		if u.unmarshalDevice("map", r, &o) != nil { | ||||
| 			u.ErrorLog("unknown asset type - cannot find asset type in payload - skipping") | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		assetType, _ := o["type"].(string) | ||||
| 		u.DebugLog("Unmarshalling Device Type: %v, site %s ", assetType, site.SiteName) | ||||
| 		// Choose which type to unmarshal into based on the "type" json key.
 | ||||
| 
 | ||||
| 		switch assetType { // Unmarshal again into the correct type..
 | ||||
| 		case "uap": | ||||
| 			u.unmarshallUAP(site, r, devices) | ||||
| 		case "ugw", "usg": // in case they ever fix the name in the api.
 | ||||
| 			u.unmarshallUSG(site, r, devices) | ||||
| 		case "usw": | ||||
| 			u.unmarshallUSW(site, r, devices) | ||||
| 		case "udm": | ||||
| 			u.unmarshallUDM(site, r, devices) | ||||
| 		case "uxg": | ||||
| 			u.unmarshallUXG(site, r, devices) | ||||
| 		default: | ||||
| 			u.ErrorLog("unknown asset type - %v - skipping", assetType) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return devices | ||||
| } | ||||
| 
 | ||||
| func (u *Unifi) unmarshallUAP(site *Site, payload json.RawMessage, devices *Devices) { | ||||
| 	dev := &UAP{SiteName: site.SiteName, SourceName: u.URL} | ||||
| 	if u.unmarshalDevice("uap", payload, dev) == nil { | ||||
| 		dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) | ||||
| 		dev.site = site | ||||
| 		devices.UAPs = append(devices.UAPs, dev) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (u *Unifi) unmarshallUSG(site *Site, payload json.RawMessage, devices *Devices) { | ||||
| 	dev := &USG{SiteName: site.SiteName, SourceName: u.URL} | ||||
| 	if u.unmarshalDevice("ugw", payload, dev) == nil { | ||||
| 		dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) | ||||
| 		dev.site = site | ||||
| 		devices.USGs = append(devices.USGs, dev) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (u *Unifi) unmarshallUSW(site *Site, payload json.RawMessage, devices *Devices) { | ||||
| 	dev := &USW{SiteName: site.SiteName, SourceName: u.URL} | ||||
| 	if u.unmarshalDevice("usw", payload, dev) == nil { | ||||
| 		dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) | ||||
| 		dev.site = site | ||||
| 		devices.USWs = append(devices.USWs, dev) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (u *Unifi) unmarshallUXG(site *Site, payload json.RawMessage, devices *Devices) { | ||||
| 	dev := &UXG{SiteName: site.SiteName, SourceName: u.URL} | ||||
| 	if u.unmarshalDevice("uxg", payload, dev) == nil { | ||||
| 		dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) | ||||
| 		dev.site = site | ||||
| 		devices.UXGs = append(devices.UXGs, dev) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (u *Unifi) unmarshallUDM(site *Site, payload json.RawMessage, devices *Devices) { | ||||
| 	dev := &UDM{SiteName: site.SiteName, SourceName: u.URL} | ||||
| 	if u.unmarshalDevice("udm", payload, dev) == nil { | ||||
| 		dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) | ||||
| 		dev.site = site | ||||
| 		devices.UDMs = append(devices.UDMs, dev) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // unmarshalDevice handles logging for the unmarshal operations in parseDevices().
 | ||||
| func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{}) (err error) { | ||||
| 	if err = json.Unmarshal(data, v); err != nil { | ||||
| 		u.ErrorLog("json.Unmarshal(%v): %v", dev, err) | ||||
| 		u.ErrorLog("Enable Debug Logging to output the failed payload.") | ||||
| 
 | ||||
| 		json, err := data.MarshalJSON() | ||||
| 		u.DebugLog("Failed Payload: %s (marshal err: %v)", json, err) | ||||
| 		u.DebugLog("The above payload can prove useful during torubleshooting when you open an Issue:") | ||||
| 		u.DebugLog("==- https://github.com/unifi-poller/unifi/issues/new -==") | ||||
| 	} | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("json unmarshal: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // pick returns the first non empty string in a list.
 | ||||
| // used in a few places around this library.
 | ||||
| func pick(strings ...string) string { | ||||
| 	for _, s := range strings { | ||||
| 		if s != "" { | ||||
| 			return s | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return "" | ||||
| } | ||||
|  | @ -0,0 +1,299 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // Known commands that can be sent to device manager. All of these are implemented.
 | ||||
| //nolint:lll // https://ubntwiki.com/products/software/unifi-controller/api#callable
 | ||||
| const ( | ||||
| 	DevMgrPowerCycle      = "power-cycle"      // mac = switch mac (required), port_idx = PoE port to cycle (required)
 | ||||
| 	DevMgrAdopt           = "adopt"            // mac = device mac (required)
 | ||||
| 	DevMgrRestart         = "restart"          // mac = device mac (required)
 | ||||
| 	DevMgrForceProvision  = "force-provision"  // mac = device mac (required)
 | ||||
| 	DevMgrSpeedTest       = "speedtest"        // Start a speed test
 | ||||
| 	DevMgrSpeedTestStatus = "speedtest-status" // Get current state of the speed test
 | ||||
| 	DevMgrSetLocate       = "set-locate"       // mac = device mac (required): blink unit to locate
 | ||||
| 	DevMgrUnsetLocate     = "unset-locate"     // mac = device mac (required): led to normal state
 | ||||
| 	DevMgrUpgrade         = "upgrade"          // mac = device mac (required): upgrade firmware
 | ||||
| 	DevMgrUpgradeExternal = "upgrade-external" // mac = device mac (required), url = firmware URL (required)
 | ||||
| 	DevMgrMigrate         = "migrate"          // mac = device mac (required), inform_url = New Inform URL for device (required)
 | ||||
| 	DevMgrCancelMigrate   = "cancel-migrate"   // mac = device mac (required)
 | ||||
| 	DevMgrSpectrumScan    = "spectrum-scan"    // mac = AP mac     (required): trigger RF scan
 | ||||
| ) | ||||
| 
 | ||||
| // devMgrCmd is the type marshalled and sent to APIDevMgrPath.
 | ||||
| type devMgrCmd struct { | ||||
| 	Cmd    string `json:"cmd"`                  // Required.
 | ||||
| 	Mac    string `json:"mac"`                  // Device MAC (required for most, but not all).
 | ||||
| 	URL    string `json:"url,omitempty"`        // External Upgrade only.
 | ||||
| 	Inform string `json:"inform_url,omitempty"` // Migration only.
 | ||||
| 	Port   int    `json:"port_idx,omitempty"`   // Power Cycle only.
 | ||||
| } | ||||
| 
 | ||||
| // devMgrCommandReply is for commands with a return value.
 | ||||
| func (s *Site) devMgrCommandReply(cmd *devMgrCmd) ([]byte, error) { | ||||
| 	data, err := json.Marshal(cmd) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("json marshal: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	b, err := s.controller.GetJSON(fmt.Sprintf(APIDevMgrPath, s.Name), string(data)) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("controller: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return b, nil | ||||
| } | ||||
| 
 | ||||
| // devMgrCommandSimple is for commands with no return value.
 | ||||
| func (s *Site) devMgrCommandSimple(cmd *devMgrCmd) error { | ||||
| 	_, err := s.devMgrCommandReply(cmd) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // PowerCycle shuts off the PoE and turns it back on for a specific port.
 | ||||
| // Get a USW from the device list to call this.
 | ||||
| func (u *USW) PowerCycle(portIndex int) error { | ||||
| 	return u.site.devMgrCommandSimple(&devMgrCmd{ | ||||
| 		Cmd:  DevMgrPowerCycle, | ||||
| 		Mac:  u.Mac, | ||||
| 		Port: portIndex, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // ScanRF begins a spectrum scan on an access point.
 | ||||
| func (u *UAP) ScanRF() error { | ||||
| 	return u.site.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrSpectrumScan, Mac: u.Mac}) | ||||
| } | ||||
| 
 | ||||
| // Restart a device by MAC address on your site.
 | ||||
| func (s *Site) Restart(mac string) error { | ||||
| 	return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrRestart, Mac: mac}) | ||||
| } | ||||
| 
 | ||||
| // Restart an access point.
 | ||||
| func (u *UAP) Restart() error { | ||||
| 	return u.site.Restart(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Restart a switch.
 | ||||
| func (u *USW) Restart() error { | ||||
| 	return u.site.Restart(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Restart a security gateway.
 | ||||
| func (u *USG) Restart() error { | ||||
| 	return u.site.Restart(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Restart a dream machine.
 | ||||
| func (u *UDM) Restart() error { | ||||
| 	return u.site.Restart(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Restart a 10Gb security gateway.
 | ||||
| func (u *UXG) Restart() error { | ||||
| 	return u.site.Restart(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Locate a device by MAC address on your site. This makes it blink.
 | ||||
| func (s *Site) Locate(mac string) error { | ||||
| 	return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrSetLocate, Mac: mac}) | ||||
| } | ||||
| 
 | ||||
| // Locate an access point.
 | ||||
| func (u *UAP) Locate() error { | ||||
| 	return u.site.Locate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Locate a switch.
 | ||||
| func (u *USW) Locate() error { | ||||
| 	return u.site.Locate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Locate a security gateway.
 | ||||
| func (u *USG) Locate() error { | ||||
| 	return u.site.Locate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Locate a dream machine.
 | ||||
| func (u *UDM) Locate() error { | ||||
| 	return u.site.Locate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Locate a 10Gb security gateway.
 | ||||
| func (u *UXG) Locate() error { | ||||
| 	return u.site.Locate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Unlocate a device by MAC address on your site. This makes it stop blinking.
 | ||||
| func (s *Site) Unlocate(mac string) error { | ||||
| 	return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrUnsetLocate, Mac: mac}) | ||||
| } | ||||
| 
 | ||||
| // Unlocate an access point (stop blinking).
 | ||||
| func (u *UAP) Unlocate() error { | ||||
| 	return u.site.Unlocate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Unlocate a switch (stop blinking).
 | ||||
| func (u *USW) Unlocate() error { | ||||
| 	return u.site.Unlocate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Unlocate a security gateway (stop blinking).
 | ||||
| func (u *USG) Unlocate() error { | ||||
| 	return u.site.Unlocate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Unlocate a dream machine (stop blinking).
 | ||||
| func (u *UDM) Unlocate() error { | ||||
| 	return u.site.Unlocate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Unlocate a 10Gb security gateway (stop blinking).
 | ||||
| func (u *UXG) Unlocate() error { | ||||
| 	return u.site.Unlocate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Provision force provisions a device by MAC address on your site.
 | ||||
| func (s *Site) Provision(mac string) error { | ||||
| 	return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrForceProvision, Mac: mac}) | ||||
| } | ||||
| 
 | ||||
| // Provision an access point forcefully.
 | ||||
| func (u *UAP) Provision() error { | ||||
| 	return u.site.Provision(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Provision a switch forcefully.
 | ||||
| func (u *USW) Provision() error { | ||||
| 	return u.site.Provision(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Provision a security gateway forcefully.
 | ||||
| func (u *USG) Provision() error { | ||||
| 	return u.site.Provision(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Provision a dream machine forcefully.
 | ||||
| func (u *UDM) Provision() error { | ||||
| 	return u.site.Provision(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Provision a 10Gb security gateway forcefully.
 | ||||
| func (u *UXG) Provision() error { | ||||
| 	return u.site.Provision(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Upgrade starts a firmware upgrade on a device by MAC address on your site.
 | ||||
| // URL is optional. If URL is not "" an external upgrade is performed.
 | ||||
| func (s *Site) Upgrade(mac string, url string) error { | ||||
| 	if url == "" { | ||||
| 		return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrUpgrade, Mac: mac}) | ||||
| 	} | ||||
| 
 | ||||
| 	return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrUpgradeExternal, Mac: mac, URL: url}) | ||||
| } | ||||
| 
 | ||||
| // Upgrade firmware on an access point.
 | ||||
| // URL is optional. If URL is not "" an external upgrade is performed.
 | ||||
| func (u *UAP) Upgrade(url string) error { | ||||
| 	return u.site.Upgrade(u.Mac, url) | ||||
| } | ||||
| 
 | ||||
| // Upgrade firmware on a switch.
 | ||||
| // URL is optional. If URL is not "" an external upgrade is performed.
 | ||||
| func (u *USW) Upgrade(url string) error { | ||||
| 	return u.site.Upgrade(u.Mac, url) | ||||
| } | ||||
| 
 | ||||
| // Upgrade firmware on a security gateway.
 | ||||
| // URL is optional. If URL is not "" an external upgrade is performed.
 | ||||
| func (u *USG) Upgrade(url string) error { | ||||
| 	return u.site.Upgrade(u.Mac, url) | ||||
| } | ||||
| 
 | ||||
| // Upgrade firmware on a dream machine.
 | ||||
| // URL is optional. If URL is not "" an external upgrade is performed.
 | ||||
| func (u *UDM) Upgrade(url string) error { | ||||
| 	return u.site.Upgrade(u.Mac, url) | ||||
| } | ||||
| 
 | ||||
| // Upgrade formware on a 10Gb security gateway.
 | ||||
| // URL is optional. If URL is not "" an external upgrade is performed.
 | ||||
| func (u *UXG) Upgrade(url string) error { | ||||
| 	return u.site.Upgrade(u.Mac, url) | ||||
| } | ||||
| 
 | ||||
| // Migrate sends a device to another controller's URL.
 | ||||
| // Probably does not work on devices with built-in controllers like UDM & UXG.
 | ||||
| func (s *Site) Migrate(mac string, url string) error { | ||||
| 	return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrMigrate, Mac: mac, Inform: url}) | ||||
| } | ||||
| 
 | ||||
| // Migrate sends an access point to another controller's URL.
 | ||||
| func (u *UAP) Migrate(url string) error { | ||||
| 	return u.site.Migrate(u.Mac, url) | ||||
| } | ||||
| 
 | ||||
| // Migrate sends a switch to another controller's URL.
 | ||||
| func (u *USW) Migrate(url string) error { | ||||
| 	return u.site.Migrate(u.Mac, url) | ||||
| } | ||||
| 
 | ||||
| // Migrate sends a security gateway to another controller's URL.
 | ||||
| func (u *USG) Migrate(url string) error { | ||||
| 	return u.site.Migrate(u.Mac, url) | ||||
| } | ||||
| 
 | ||||
| // Migrate sends a 10Gb gateway to another controller's URL.
 | ||||
| func (u *UXG) Migrate(url string) error { | ||||
| 	return u.site.Migrate(u.Mac, url) | ||||
| } | ||||
| 
 | ||||
| // CancelMigrate stops a migration in progress.
 | ||||
| // Probably does not work on devices with built-in controllers like UDM & UXG.
 | ||||
| func (s *Site) CancelMigrate(mac string) error { | ||||
| 	return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrCancelMigrate, Mac: mac}) | ||||
| } | ||||
| 
 | ||||
| // CancelMigrate stops an access point migration in progress.
 | ||||
| func (u *UAP) CancelMigrate() error { | ||||
| 	return u.site.CancelMigrate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // CancelMigrate stops a switch migration in progress.
 | ||||
| func (u *USW) CancelMigrate() error { | ||||
| 	return u.site.CancelMigrate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // CancelMigrate stops a security gateway migration in progress.
 | ||||
| func (u *USG) CancelMigrate() error { | ||||
| 	return u.site.CancelMigrate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // CancelMigrate stops 10Gb gateway a migration in progress.
 | ||||
| func (u *UXG) CancelMigrate() error { | ||||
| 	return u.site.CancelMigrate(u.Mac) | ||||
| } | ||||
| 
 | ||||
| // Adopt a device by MAC address to your site.
 | ||||
| func (s *Site) Adopt(mac string) error { | ||||
| 	return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrAdopt, Mac: mac}) | ||||
| } | ||||
| 
 | ||||
| // SpeedTest begins a speed test on a site.
 | ||||
| func (s *Site) SpeedTest() error { | ||||
| 	return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrSpeedTest}) | ||||
| } | ||||
| 
 | ||||
| // SpeedTestStatus returns the raw response for the status of a speed test.
 | ||||
| // XXX: marshal the response into a data structure. This method will change!
 | ||||
| func (s *Site) SpeedTestStatus() ([]byte, error) { | ||||
| 	body, err := s.devMgrCommandReply(&devMgrCmd{Cmd: DevMgrSpeedTestStatus}) | ||||
| 	// marshal into struct here.
 | ||||
| 	return body, err | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,204 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	ErrNoSiteProvided   = fmt.Errorf("site must not be nil or empty") | ||||
| 	ErrInvalidTimeRange = fmt.Errorf("only 0, 1 or 2 times may be provided to timeRange") | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	eventLimit = 50000 | ||||
| ) | ||||
| 
 | ||||
| // GetEvents returns a response full of UniFi Events for the last 1 hour from multiple sites.
 | ||||
| func (u *Unifi) GetEvents(sites []*Site, hours time.Duration) ([]*Event, error) { | ||||
| 	data := make([]*Event, 0) | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		response, err := u.GetSiteEvents(site, hours) | ||||
| 		if err != nil { | ||||
| 			return data, err | ||||
| 		} | ||||
| 
 | ||||
| 		data = append(data, response...) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // GetSiteEvents retrieves the last 1 hour's worth of events from a single site.
 | ||||
| func (u *Unifi) GetSiteEvents(site *Site, hours time.Duration) ([]*Event, error) { | ||||
| 	if site == nil || site.Name == "" { | ||||
| 		return nil, ErrNoSiteProvided | ||||
| 	} | ||||
| 
 | ||||
| 	if hours < time.Hour { | ||||
| 		hours = time.Hour | ||||
| 	} | ||||
| 
 | ||||
| 	u.DebugLog("Polling Controller, retreiving UniFi Events, site %s", site.SiteName) | ||||
| 
 | ||||
| 	var ( | ||||
| 		path   = fmt.Sprintf(APIEventPath, site.Name) | ||||
| 		params = fmt.Sprintf(`{"_limit":%d,"within":%d,"_sort":"-time"}}`, | ||||
| 			eventLimit, int(hours.Round(time.Hour).Hours())) | ||||
| 		event struct { | ||||
| 			Data events `json:"data"` | ||||
| 		} | ||||
| 	) | ||||
| 
 | ||||
| 	if err := u.GetData(path, &event, params); err != nil { | ||||
| 		return event.Data, err | ||||
| 	} | ||||
| 
 | ||||
| 	for i := range event.Data { | ||||
| 		// Add special SourceName value.
 | ||||
| 		event.Data[i].SourceName = u.URL | ||||
| 		// Add the special "Site Name" to each event. This becomes a Grafana filter somewhere.
 | ||||
| 		event.Data[i].SiteName = site.SiteName | ||||
| 	} | ||||
| 
 | ||||
| 	sort.Sort(event.Data) | ||||
| 
 | ||||
| 	return event.Data, nil | ||||
| } | ||||
| 
 | ||||
| // Event describes a UniFi Event.
 | ||||
| // API Path: /api/s/default/stat/event.
 | ||||
| type Event struct { | ||||
| 	IsAdmin               FlexBool  `json:"is_admin"` | ||||
| 	DestPort              int       `json:"dest_port"` | ||||
| 	SrcPort               int       `json:"src_port"` | ||||
| 	Bytes                 FlexInt   `json:"bytes"` | ||||
| 	Duration              FlexInt   `json:"duration"` | ||||
| 	FlowID                FlexInt   `json:"flow_id"` | ||||
| 	InnerAlertGID         FlexInt   `json:"inner_alert_gid"` | ||||
| 	InnerAlertRev         FlexInt   `json:"inner_alert_rev"` | ||||
| 	InnerAlertSeverity    FlexInt   `json:"inner_alert_severity"` | ||||
| 	InnerAlertSignatureID FlexInt   `json:"inner_alert_signature_id"` | ||||
| 	Channel               FlexInt   `json:"channel"` | ||||
| 	ChannelFrom           FlexInt   `json:"channel_from"` | ||||
| 	ChannelTo             FlexInt   `json:"channel_to"` | ||||
| 	Time                  int64     `json:"time"` | ||||
| 	Timestamp             int64     `json:"timestamp"` | ||||
| 	Datetime              time.Time `json:"datetime"` | ||||
| 	Admin                 string    `json:"admin"` | ||||
| 	Ap                    string    `json:"ap"` | ||||
| 	ApFrom                string    `json:"ap_from"` | ||||
| 	ApName                string    `json:"ap_name"` | ||||
| 	ApTo                  string    `json:"ap_to"` | ||||
| 	AppProto              string    `json:"app_proto"` | ||||
| 	Catname               string    `json:"catname"` | ||||
| 	DestIP                string    `json:"dest_ip"` | ||||
| 	DstMAC                string    `json:"dst_mac"` | ||||
| 	EventType             string    `json:"event_type"` | ||||
| 	Guest                 string    `json:"guest"` | ||||
| 	Gw                    string    `json:"gw"` | ||||
| 	GwName                string    `json:"gw_name"` | ||||
| 	Host                  string    `json:"host"` | ||||
| 	Hostname              string    `json:"hostname"` | ||||
| 	ID                    string    `json:"_id"` | ||||
| 	IP                    string    `json:"ip"` | ||||
| 	InIface               string    `json:"in_iface"` | ||||
| 	InnerAlertAction      string    `json:"inner_alert_action"` | ||||
| 	InnerAlertCategory    string    `json:"inner_alert_category"` | ||||
| 	InnerAlertSignature   string    `json:"inner_alert_signature"` | ||||
| 	Key                   string    `json:"key"` | ||||
| 	Msg                   string    `json:"msg"` | ||||
| 	Network               string    `json:"network"` | ||||
| 	Proto                 string    `json:"proto"` | ||||
| 	Radio                 string    `json:"radio"` | ||||
| 	RadioFrom             string    `json:"radio_from"` | ||||
| 	RadioTo               string    `json:"radio_to"` | ||||
| 	SiteID                string    `json:"site_id"` | ||||
| 	SiteName              string    `json:"-"` | ||||
| 	SourceName            string    `json:"-"` | ||||
| 	SrcIP                 string    `json:"src_ip"` | ||||
| 	SrcMAC                string    `json:"src_mac"` | ||||
| 	SrcIPASN              string    `json:"srcipASN"` | ||||
| 	SrcIPCountry          string    `json:"srcipCountry"` | ||||
| 	SSID                  string    `json:"ssid"` | ||||
| 	Subsystem             string    `json:"subsystem"` | ||||
| 	Sw                    string    `json:"sw"` | ||||
| 	SwName                string    `json:"sw_name"` | ||||
| 	UniqueAlertID         string    `json:"unique_alertid"` | ||||
| 	User                  string    `json:"user"` | ||||
| 	USGIP                 string    `json:"usgip"` | ||||
| 	USGIPASN              string    `json:"usgipASN"` | ||||
| 	USGIPCountry          string    `json:"usgipCountry"` | ||||
| 	DestIPGeo             IPGeo     `json:"dstipGeo"` | ||||
| 	SourceIPGeo           IPGeo     `json:"srcipGeo"` | ||||
| 	USGIPGeo              IPGeo     `json:"usgipGeo"` | ||||
| } | ||||
| 
 | ||||
| // IPGeo is part of the UniFi Event data. Each event may have up to three of these.
 | ||||
| // One for source, one for dest and one for the USG location.
 | ||||
| type IPGeo struct { | ||||
| 	Asn           int64   `json:"asn"` | ||||
| 	Latitude      float64 `json:"latitude"` | ||||
| 	Longitude     float64 `json:"longitude"` | ||||
| 	City          string  `json:"city"` | ||||
| 	ContinentCode string  `json:"continent_code"` | ||||
| 	CountryCode   string  `json:"country_code"` | ||||
| 	CountryName   string  `json:"country_name"` | ||||
| 	Organization  string  `json:"organization"` | ||||
| } | ||||
| 
 | ||||
| // Events satisfied the sort.Interface.
 | ||||
| type events []*Event | ||||
| 
 | ||||
| // Len satisfies sort.Interface.
 | ||||
| func (e events) Len() int { | ||||
| 	return len(e) | ||||
| } | ||||
| 
 | ||||
| // Swap satisfies sort.Interface.
 | ||||
| func (e events) Swap(i, j int) { | ||||
| 	e[i], e[j] = e[j], e[i] | ||||
| } | ||||
| 
 | ||||
| // Less satisfies sort.Interface. Sort our list by date/time.
 | ||||
| func (e events) Less(i, j int) bool { | ||||
| 	return e[i].Datetime.Before(e[j].Datetime) | ||||
| } | ||||
| 
 | ||||
| // UnmarshalJSON is required because sometimes the unifi api returns
 | ||||
| // an empty array instead of a struct filled with data.
 | ||||
| func (v *IPGeo) UnmarshalJSON(data []byte) error { | ||||
| 	if string(data) == "[]" { | ||||
| 		return nil // it's empty
 | ||||
| 	} | ||||
| 
 | ||||
| 	g := struct { | ||||
| 		Asn           int64   `json:"asn"` | ||||
| 		Latitude      float64 `json:"latitude"` | ||||
| 		Longitude     float64 `json:"longitude"` | ||||
| 		City          string  `json:"city"` | ||||
| 		ContinentCode string  `json:"continent_code"` | ||||
| 		CountryCode   string  `json:"country_code"` | ||||
| 		CountryName   string  `json:"country_name"` | ||||
| 		Organization  string  `json:"organization"` | ||||
| 	}{} | ||||
| 
 | ||||
| 	err := json.Unmarshal(data, &g) | ||||
| 	v.Asn = g.Asn | ||||
| 	v.Latitude = g.Latitude | ||||
| 	v.Longitude = g.Longitude | ||||
| 	v.City = g.City | ||||
| 	v.ContinentCode = g.ContinentCode | ||||
| 	v.CountryCode = g.CountryCode | ||||
| 	v.CountryName = g.CountryName | ||||
| 	v.Organization = g.Organization | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("json unmarshal: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | @ -0,0 +1,20 @@ | |||
| package unifi_test | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unpoller/unifi" | ||||
| ) | ||||
| 
 | ||||
| func TestIPGeoUnmarshalJSON(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| 	a := assert.New(t) | ||||
| 	i := &unifi.IPGeo{} | ||||
| 
 | ||||
| 	a.Nil(i.UnmarshalJSON([]byte(`[]`))) | ||||
| 	a.EqualValues(0, i.Asn) | ||||
| 	a.Nil(i.UnmarshalJSON([]byte(`{"asn": 123}`))) | ||||
| 	a.EqualValues(123, i.Asn) | ||||
| } | ||||
|  | @ -0,0 +1,26 @@ | |||
| #!/bin/bash | ||||
| # Usage: | ||||
| # ./convert.sh <file> [prefix] | ||||
| # <file> should contain a go struct, like uap_type.go | ||||
| # It converts the go struct to an influx thing, like you see in uap_influx.go. | ||||
| # [prefix] is optional. I used it to do all the stat_ uap metrics. | ||||
| # Very crude, just helps skip a lot of copy/paste. | ||||
| # | ||||
| path=$1 | ||||
| pre=$2 | ||||
| 
 | ||||
| # Reads in the file one line at a time. | ||||
| while IFS='' read -r line; do | ||||
|   # Split each piece of the file out. | ||||
|   name=$(echo "${line}" | awk '{print $1}') | ||||
|   type=$(echo "${line}" | awk '{print $2}') | ||||
|   json=$(echo "${line}" | awk '{print $3}') | ||||
|   json=$(echo "${json}" | cut -d\" -f2) | ||||
| 
 | ||||
|   # Don't print junk lines. (it still prints some junk lines) | ||||
|   if [ "$json" != "" ]; then | ||||
|     # Add a .Val suffix if this is a FlexInt or FlexBool. | ||||
|     [[ "$type" = Flex* ]] && suf=.Val | ||||
|     echo "\"${pre}${json}\": u.Stat.${name}${suf}," | ||||
|   fi | ||||
| done < ${path} | ||||
|  | @ -0,0 +1,86 @@ | |||
| { | ||||
|   "_id": "5ee9e572453d4e00f3c04a7c", | ||||
|   "user": "d8:4c:90:9f:82:5a", | ||||
|   "ssid": "Extra Fast", | ||||
|   "ap": "b4:fb:e4:d2:74:39", | ||||
|   "radio": "na", | ||||
|   "channel": "36", | ||||
|   "key": "EVT_WU_Connected", | ||||
|   "subsystem": "wlan", | ||||
|   "site_id": "574e86994566ffb914a2683c", | ||||
|   "time": 1592386923851, | ||||
|   "datetime": "2020-06-17T09:42:03Z", | ||||
|   "msg": "User[d8:4c:90:9f:82:5a] has connected to AP[b4:fb:e4:d2:74:39] with SSID \"Extra Fast\" on \"channel 36(na)\"" | ||||
| }, | ||||
| { | ||||
|   "_id": "5ee9e56b453d4e00f3c04a7a", | ||||
|   "user": "d8:4c:90:9f:82:5a", | ||||
|   "ssid": "Extra Fast", | ||||
|   "hostname": "dns-ipp", | ||||
|   "ap": "74:83:c2:d4:11:3d", | ||||
|   "duration": 1084, | ||||
|   "bytes": 846171, | ||||
|   "key": "EVT_WU_Disconnected", | ||||
|   "subsystem": "wlan", | ||||
|   "site_id": "574e86994566ffb914a2683c", | ||||
|   "time": 1592386923000, | ||||
|   "datetime": "2020-06-17T09:42:03Z", | ||||
|   "msg": "User[d8:4c:90:9f:82:5a] disconnected from \"Extra Fast\" (18m 4s connected, 826.34K bytes, last AP[74:83:c2:d4:11:3d])" | ||||
| }, | ||||
| { | ||||
|   "_id": "5ee9f7ca453d4e00f3c04b57", | ||||
|   "timestamp": 1592391625, | ||||
|   "flow_id": 1510453960799559, | ||||
|   "in_iface": "eth0", | ||||
|   "event_type": "alert", | ||||
|   "src_ip": "192.168.1.199", | ||||
|   "src_mac": "00:50:b6:96:76:6e", | ||||
|   "src_port": 50447, | ||||
|   "dest_ip": "54.36.xxx.xxx", | ||||
|   "dst_mac": "74:83:c2:1a:35:39", | ||||
|   "dest_port": 80, | ||||
|   "proto": "TCP", | ||||
|   "tx_id": 0, | ||||
|   "app_proto": "http", | ||||
|   "host": "usg-sensor", | ||||
|   "usgip": "67.181.75.120", | ||||
|   "unique_alertid": "1603112333-2020-06-17T04:00:25.225809-0700", | ||||
|   "srcipGeo": [], | ||||
|   "dstipGeo": { | ||||
|     "continent_code": "EU", | ||||
|     "country_code": "FR", | ||||
|     "country_name": "France", | ||||
|     "latitude": 48.8582, | ||||
|     "longitude": 2.3387, | ||||
|     "asn": 16276, | ||||
|     "organization": "OVH SAS" | ||||
|   }, | ||||
|   "dstipCountry": "FR", | ||||
|   "dstipASN": "16276 OVH SAS", | ||||
|   "usgipGeo": { | ||||
|     "continent_code": "NA", | ||||
|     "country_code": "US", | ||||
|     "country_name": "United States", | ||||
|     "city": "Lodi", | ||||
|     "latitude": 38.1228, | ||||
|     "longitude": -121.2543, | ||||
|     "asn": 7922, | ||||
|     "organization": "COMCAST-7922" | ||||
|   }, | ||||
|   "usgipCountry": "US", | ||||
|   "usgipASN": "7922 COMCAST-7922", | ||||
|   "catname": "emerging-malware", | ||||
|   "inner_alert_action": "allowed", | ||||
|   "inner_alert_gid": 1, | ||||
|   "inner_alert_signature_id": 2003337, | ||||
|   "inner_alert_rev": 21, | ||||
|   "inner_alert_signature": "ET MALWARE Suspicious User Agent (Autoupdate)", | ||||
|   "inner_alert_category": "A Network Trojan was Detected", | ||||
|   "inner_alert_severity": 1, | ||||
|   "key": "EVT_IPS_IpsAlert", | ||||
|   "subsystem": "www", | ||||
|   "site_id": "574e86994566ffb914a2683c", | ||||
|   "time": 1592391625000, | ||||
|   "datetime": "2020-06-17T11:00:25Z", | ||||
|   "msg": "IPS Alert 1: A Network Trojan was Detected. Signature ET MALWARE Suspicious User Agent (Autoupdate). From: 192.168.1.199:50447, to: 54.36.xxx.xxx:80, protocol: TCP" | ||||
| }, | ||||
|  | @ -0,0 +1,66 @@ | |||
| { | ||||
|   "_id": "5d2416c78f0385ccf1c6df44", | ||||
|   "archived": false, | ||||
|   "timestamp": 1562646211, | ||||
|   "flow_id": 1591464006222389, | ||||
|   "in_iface": "eth1", | ||||
|   "event_type": "alert", | ||||
|   "src_ip": "196.196.244.84", | ||||
|   "src_mac": "f0:9f:c2:c4:bb:f1", | ||||
|   "src_port": 51413, | ||||
|   "dest_ip": "192.168.3.2", | ||||
|   "dst_mac": "40:a8:f0:68:c3:58", | ||||
|   "dest_port": 36881, | ||||
|   "proto": "UDP", | ||||
|   "app_proto": "failed", | ||||
|   "host": "f0:22:22:22:22:22", | ||||
|   "usgip": "11.22.33.44", | ||||
|   "unique_alertid": "1341902566-2019-07-08T21:23:31.229941-0700", | ||||
|   "srcipCountry": "SE", | ||||
|   "dstipCountry": false, | ||||
|   "usgipCountry": "US", | ||||
|   "srcipGeo": { | ||||
|     "continent_code": "EU", | ||||
|     "country_code": "SE", | ||||
|     "country_code3": "SWE", | ||||
|     "country_name": "Sweden", | ||||
|     "region": "26", | ||||
|     "city": "Stockholm", | ||||
|     "postal_code": "168 65", | ||||
|     "latitude": 59.349998474121094, | ||||
|     "longitude": 17.91670036315918, | ||||
|     "dma_code": 0, | ||||
|     "area_code": 0 | ||||
|   }, | ||||
|   "dstipGeo": false, | ||||
|   "usgipGeo": { | ||||
|     "continent_code": "NA", | ||||
|     "country_code": "US", | ||||
|     "country_code3": "USA", | ||||
|     "country_name": "United States", | ||||
|     "region": "CA", | ||||
|     "city": "Other", | ||||
|     "postal_code": "99999", | ||||
|     "latitude": 99.139400482177734, | ||||
|     "longitude": -99.39669799804688, | ||||
|     "dma_code": 862, | ||||
|     "area_code": 999 | ||||
|   }, | ||||
|   "srcipASN": "AS42607 Internet Carrier Limited", | ||||
|   "dstipASN": "", | ||||
|   "usgipASN": "AS7922 Comcast Cable Communications, LLC", | ||||
|   "catname": "spamhaus", | ||||
|   "inner_alert_action": "allowed", | ||||
|   "inner_alert_gid": 1, | ||||
|   "inner_alert_signature_id": 2400022, | ||||
|   "inner_alert_rev": 2673, | ||||
|   "inner_alert_signature": "ET DROP Spamhaus DROP Listed Traffic Inbound group 23", | ||||
|   "inner_alert_category": "Misc Attack", | ||||
|   "inner_alert_severity": 2, | ||||
|   "key": "EVT_IPS_IpsAlert", | ||||
|   "subsystem": "www", | ||||
|   "site_id": "574e86994566ffb914a2683c", | ||||
|   "time": 1562646211000, | ||||
|   "datetime": "2019-07-09T04:23:31Z", | ||||
|   "msg": "IPS Alert 2: Misc Attack. Signature ET DROP Spamhaus DROP Listed Traffic Inbound group 23. From: 196.196.244.84:51413, to: 192.168.3.2:36881, protocol: UDP" | ||||
| }, | ||||
|  | @ -0,0 +1,738 @@ | |||
| { | ||||
|   "_id": "574e8bde4566ffb914a26853", | ||||
|   "adopted": true, | ||||
|   "antenna_table": [ | ||||
|     { | ||||
|       "default": true, | ||||
|       "id": 4, | ||||
|       "name": "Combined", | ||||
|       "wifi0_gain": 3, | ||||
|       "wifi1_gain": 3 | ||||
|     } | ||||
|   ], | ||||
|   "bandsteering_mode": "prefer_5g", | ||||
|   "board_rev": 18, | ||||
|   "cfgversion": "08cd8d6b71ebe82f", | ||||
|   "config_network": { | ||||
|     "type": "dhcp", | ||||
|     "ip": "10.1.10.67" | ||||
|   }, | ||||
|   "countrycode_table": [ | ||||
|     840, | ||||
|     124, | ||||
|     630 | ||||
|   ], | ||||
|   "ethernet_table": [ | ||||
|     { | ||||
|       "mac": "80:22:a8:22:ae:22", | ||||
|       "num_port": 2, | ||||
|       "name": "eth0" | ||||
|     } | ||||
|   ], | ||||
|   "fw_caps": 4128063, | ||||
|   "has_eth1": false, | ||||
|   "has_speaker": false, | ||||
|   "inform_ip": "192.168.3.1", | ||||
|   "inform_url": "http://unifi:8080/inform", | ||||
|   "ip": "192.168.1.8", | ||||
|   "led_override": "default", | ||||
|   "mac": "80:22:22:22:22:22", | ||||
|   "mesh_sta_vap_enabled": true, | ||||
|   "model": "U7PG2", | ||||
|   "name": "wap-lower", | ||||
|   "outdoor_mode_override": "default", | ||||
|   "port_table": [ | ||||
|     { | ||||
|       "port_idx": 1, | ||||
|       "op_mode": "switch", | ||||
|       "portconf_id": "574e869d4566ffb914a26845", | ||||
|       "attr_no_edit": true, | ||||
|       "media": "GE", | ||||
|       "name": "Main", | ||||
|       "poe_caps": 0, | ||||
|       "port_poe": false, | ||||
|       "tx_bytes-r": 0, | ||||
|       "rx_bytes-r": 0, | ||||
|       "bytes-r": 0, | ||||
|       "port_delta": { | ||||
|         "time_delta": 21 | ||||
|       }, | ||||
|       "enable": true, | ||||
|       "masked": false, | ||||
|       "aggregated_by": false | ||||
|     }, | ||||
|     { | ||||
|       "port_idx": 2, | ||||
|       "op_mode": "switch", | ||||
|       "portconf_id": "574e869d4566ffb914a26845", | ||||
|       "media": "GE", | ||||
|       "name": "Secondary", | ||||
|       "poe_caps": 0, | ||||
|       "port_poe": false, | ||||
|       "tx_bytes-r": 0, | ||||
|       "rx_bytes-r": 0, | ||||
|       "bytes-r": 0, | ||||
|       "port_delta": { | ||||
|         "time_delta": 21 | ||||
|       }, | ||||
|       "enable": true, | ||||
|       "masked": false, | ||||
|       "aggregated_by": false | ||||
|     } | ||||
|   ], | ||||
|   "radio_table": [ | ||||
|     { | ||||
|       "radio": "ng", | ||||
|       "name": "wifi0", | ||||
|       "channel": "auto", | ||||
|       "ht": "40", | ||||
|       "tx_power_mode": "auto", | ||||
|       "tx_power": "0", | ||||
|       "max_txpower": 22, | ||||
|       "min_txpower": 6, | ||||
|       "nss": 3, | ||||
|       "min_rssi_enabled": false, | ||||
|       "builtin_antenna": true, | ||||
|       "builtin_ant_gain": 3, | ||||
|       "current_antenna_gain": 0, | ||||
|       "radio_caps": 16420, | ||||
|       "wlangroup_id": "574e869d4566ffb914a26843" | ||||
|     }, | ||||
|     { | ||||
|       "radio": "na", | ||||
|       "name": "wifi1", | ||||
|       "channel": "auto", | ||||
|       "ht": "80", | ||||
|       "tx_power_mode": "high", | ||||
|       "tx_power": "0", | ||||
|       "max_txpower": 22, | ||||
|       "min_txpower": 6, | ||||
|       "nss": 3, | ||||
|       "min_rssi_enabled": false, | ||||
|       "is_11ac": true, | ||||
|       "has_dfs": true, | ||||
|       "has_fccdfs": true, | ||||
|       "builtin_antenna": true, | ||||
|       "builtin_ant_gain": 3, | ||||
|       "current_antenna_gain": 0, | ||||
|       "radio_caps": 50479140, | ||||
|       "wlangroup_id": "574e869d4566ffb914a26843" | ||||
|     } | ||||
|   ], | ||||
|   "scan_radio_table": [], | ||||
|   "serial": "xxxyyyzzz", | ||||
|   "site_id": "574e86994566ffb914a2683c", | ||||
|   "type": "uap", | ||||
|   "version": "4.0.42.10433", | ||||
|   "vwire_table": [], | ||||
|   "wifi_caps": 49141, | ||||
|   "wlangroup_id_na": "574e869d4566ffb914a26843", | ||||
|   "wlangroup_id_ng": "574e869d4566ffb914a26843", | ||||
|   "required_version": "3.4.1", | ||||
|   "hw_caps": 0, | ||||
|   "unsupported": false, | ||||
|   "unsupported_reason": 0, | ||||
|   "sys_error_caps": 0, | ||||
|   "has_fan": false, | ||||
|   "has_temperature": false, | ||||
|   "device_id": "574e8bde4566ffb914a26853", | ||||
|   "state": 1, | ||||
|   "last_seen": 1562225381, | ||||
|   "upgradable": false, | ||||
|   "adoptable_when_upgraded": false, | ||||
|   "rollupgrade": false, | ||||
|   "known_cfgversion": "08cd8d6b71ebe82f", | ||||
|   "uptime": 3105845, | ||||
|   "_uptime": 3105845, | ||||
|   "locating": false, | ||||
|   "connect_request_ip": "192.168.1.8", | ||||
|   "connect_request_port": "43913", | ||||
|   "sys_stats": { | ||||
|     "loadavg_1": "0.04", | ||||
|     "loadavg_15": "0.14", | ||||
|     "loadavg_5": "0.10", | ||||
|     "mem_buffer": 0, | ||||
|     "mem_total": 128622592, | ||||
|     "mem_used": 67178496 | ||||
|   }, | ||||
|   "system-stats": { | ||||
|     "cpu": "24.7", | ||||
|     "mem": "52.2", | ||||
|     "uptime": "3105845" | ||||
|   }, | ||||
|   "ssh_session_table": [], | ||||
|   "scanning": false, | ||||
|   "spectrum_scanning": false, | ||||
|   "guest_token": "4D630D2A1AF84771FCBB8EEB4C47E030", | ||||
|   "meshv3_peer_mac": "", | ||||
|   "satisfaction": 95, | ||||
|   "isolated": false, | ||||
|   "radio_table_stats": [ | ||||
|     { | ||||
|       "name": "wifi0", | ||||
|       "channel": 6, | ||||
|       "radio": "ng", | ||||
|       "ast_txto": null, | ||||
|       "ast_cst": null, | ||||
|       "ast_be_xmit": 398, | ||||
|       "cu_total": 20, | ||||
|       "cu_self_rx": 14, | ||||
|       "cu_self_tx": 4, | ||||
|       "gain": 3, | ||||
|       "state": "RUN", | ||||
|       "extchannel": 1, | ||||
|       "tx_power": 22, | ||||
|       "tx_packets": 183, | ||||
|       "tx_retries": 108, | ||||
|       "num_sta": 2, | ||||
|       "guest-num_sta": 0, | ||||
|       "user-num_sta": 2 | ||||
|     }, | ||||
|     { | ||||
|       "name": "wifi1", | ||||
|       "channel": 36, | ||||
|       "radio": "na", | ||||
|       "ast_txto": null, | ||||
|       "ast_cst": null, | ||||
|       "ast_be_xmit": 398, | ||||
|       "cu_total": 12, | ||||
|       "cu_self_rx": 10, | ||||
|       "cu_self_tx": 2, | ||||
|       "gain": 3, | ||||
|       "state": "RUN", | ||||
|       "extchannel": 1, | ||||
|       "tx_power": 22, | ||||
|       "tx_packets": 22466, | ||||
|       "tx_retries": 858, | ||||
|       "num_sta": 4, | ||||
|       "guest-num_sta": 0, | ||||
|       "user-num_sta": 4 | ||||
|     } | ||||
|   ], | ||||
|   "uplink": { | ||||
|     "full_duplex": true, | ||||
|     "ip": "0.0.0.0", | ||||
|     "mac": "80:22:22:22:22:22", | ||||
|     "max_vlan": 96, | ||||
|     "name": "eth0", | ||||
|     "netmask": "0.0.0.0", | ||||
|     "num_port": 2, | ||||
|     "rx_bytes": 3752803309, | ||||
|     "rx_dropped": 102338, | ||||
|     "rx_errors": 0, | ||||
|     "rx_multicast": 0, | ||||
|     "rx_packets": 245302304, | ||||
|     "speed": 1000, | ||||
|     "tx_bytes": 1604707458, | ||||
|     "tx_dropped": 341, | ||||
|     "tx_errors": 0, | ||||
|     "tx_packets": 194278357, | ||||
|     "up": true, | ||||
|     "max_speed": 1000, | ||||
|     "type": "wire", | ||||
|     "tx_bytes-r": 9693222, | ||||
|     "rx_bytes-r": 92418, | ||||
|     "uplink_mac": "f0:22:22:22:22:22", | ||||
|     "uplink_remote_port": 15 | ||||
|   }, | ||||
|   "vap_table": [ | ||||
|     { | ||||
|       "avg_client_signal": 0, | ||||
|       "bssid": "82:22:22:22:22:22", | ||||
|       "ccq": 0, | ||||
|       "channel": 36, | ||||
|       "essid": "Extra Free", | ||||
|       "extchannel": 1, | ||||
|       "id": "574e96834566ffb914a26875", | ||||
|       "mac_filter_rejections": 0, | ||||
|       "name": "ath3", | ||||
|       "num_satisfaction_sta": 0, | ||||
|       "num_sta": 0, | ||||
|       "radio": "na", | ||||
|       "radio_name": "wifi1", | ||||
|       "rx_bytes": 61253, | ||||
|       "rx_crypts": 0, | ||||
|       "rx_dropped": 0, | ||||
|       "rx_errors": 0, | ||||
|       "rx_frags": 0, | ||||
|       "rx_nwids": 47658, | ||||
|       "rx_packets": 576, | ||||
|       "rx_tcp_stats": { | ||||
|         "goodbytes": 0, | ||||
|         "lat_avg": 0, | ||||
|         "lat_max": 0, | ||||
|         "lat_min": 4294967295, | ||||
|         "stalls": 0 | ||||
|       }, | ||||
|       "satisfaction": -1, | ||||
|       "satisfaction_now": -1, | ||||
|       "state": "RUN", | ||||
|       "tx_bytes": 922841, | ||||
|       "tx_combined_retries": 0, | ||||
|       "tx_data_mpdu_bytes": 0, | ||||
|       "tx_dropped": 18128, | ||||
|       "tx_errors": 7, | ||||
|       "tx_packets": 736, | ||||
|       "tx_power": 22, | ||||
|       "tx_retries": 0, | ||||
|       "tx_rts_retries": 0, | ||||
|       "tx_success": 0, | ||||
|       "tx_tcp_stats": { | ||||
|         "goodbytes": 0, | ||||
|         "lat_avg": 0, | ||||
|         "lat_max": 0, | ||||
|         "lat_min": 4294967295, | ||||
|         "stalls": 0 | ||||
|       }, | ||||
|       "tx_total": 0, | ||||
|       "up": true, | ||||
|       "usage": "guest", | ||||
|       "wifi_tx_attempts": 0, | ||||
|       "wifi_tx_dropped": 0, | ||||
|       "wifi_tx_latency_mov": { | ||||
|         "avg": 0, | ||||
|         "max": 0, | ||||
|         "min": 4294967295, | ||||
|         "total": 0, | ||||
|         "total_count": 0 | ||||
|       }, | ||||
|       "t": "vap", | ||||
|       "wlanconf_id": "574e96834566ffb914a26875", | ||||
|       "is_guest": true, | ||||
|       "is_wep": false, | ||||
|       "ap_mac": "80:22:a8:22:22:22", | ||||
|       "map_id": null, | ||||
|       "site_id": "574e86994566ffb914a2683c" | ||||
|     }, | ||||
|     { | ||||
|       "avg_client_signal": -65, | ||||
|       "bssid": "80:22:a8:22:22:22", | ||||
|       "ccq": 333, | ||||
|       "channel": 36, | ||||
|       "essid": "Extra Fast", | ||||
|       "extchannel": 1, | ||||
|       "id": "574e96614566ffb914a26874", | ||||
|       "mac_filter_rejections": 0, | ||||
|       "name": "ath2", | ||||
|       "num_satisfaction_sta": 4, | ||||
|       "num_sta": 4, | ||||
|       "radio": "na", | ||||
|       "radio_name": "wifi1", | ||||
|       "rx_bytes": 3358763522, | ||||
|       "rx_crypts": 161639, | ||||
|       "rx_dropped": 161639, | ||||
|       "rx_errors": 161639, | ||||
|       "rx_frags": 0, | ||||
|       "rx_nwids": 37605, | ||||
|       "rx_packets": 99128603, | ||||
|       "rx_tcp_stats": { | ||||
|         "goodbytes": 16243150352, | ||||
|         "lat_avg": 7, | ||||
|         "lat_max": 100, | ||||
|         "lat_min": 10, | ||||
|         "stalls": 0 | ||||
|       }, | ||||
|       "satisfaction": 96, | ||||
|       "satisfaction_now": 97, | ||||
|       "state": "RUN", | ||||
|       "tx_bytes": 834859686, | ||||
|       "tx_combined_retries": 9067488, | ||||
|       "tx_data_mpdu_bytes": 48117828355, | ||||
|       "tx_dropped": 109, | ||||
|       "tx_errors": 4076905, | ||||
|       "tx_packets": 111980588, | ||||
|       "tx_power": 22, | ||||
|       "tx_retries": 9067488, | ||||
|       "tx_rts_retries": 0, | ||||
|       "tx_success": 62855481, | ||||
|       "tx_tcp_stats": { | ||||
|         "goodbytes": 2154118473, | ||||
|         "lat_avg": 18, | ||||
|         "lat_max": 120, | ||||
|         "lat_min": 10, | ||||
|         "stalls": 0 | ||||
|       }, | ||||
|       "tx_total": 62855552, | ||||
|       "up": true, | ||||
|       "usage": "user", | ||||
|       "wifi_tx_attempts": 71923040, | ||||
|       "wifi_tx_dropped": 71, | ||||
|       "wifi_tx_latency_mov": { | ||||
|         "avg": 565, | ||||
|         "max": 31757, | ||||
|         "min": 98, | ||||
|         "total": 10183098, | ||||
|         "total_count": 18019 | ||||
|       }, | ||||
|       "t": "vap", | ||||
|       "wlanconf_id": "574e96614566ffb914a26874", | ||||
|       "is_guest": false, | ||||
|       "is_wep": false, | ||||
|       "ap_mac": "80:22:22:22:22:22", | ||||
|       "map_id": null, | ||||
|       "site_id": "574e86994566ffb914a2683c" | ||||
|     }, | ||||
|     { | ||||
|       "avg_client_signal": 0, | ||||
|       "bssid": "82:22:a8:22:22:22", | ||||
|       "ccq": 0, | ||||
|       "channel": 6, | ||||
|       "essid": "Extra Free", | ||||
|       "extchannel": 1, | ||||
|       "id": "574e96834566ffb914a26875", | ||||
|       "mac_filter_rejections": 0, | ||||
|       "name": "ath1", | ||||
|       "num_satisfaction_sta": 0, | ||||
|       "num_sta": 0, | ||||
|       "radio": "ng", | ||||
|       "radio_name": "wifi0", | ||||
|       "reasons_bar_chart": { | ||||
|         "phy_rate": 0, | ||||
|         "signal": 0, | ||||
|         "sleepy_client": 0, | ||||
|         "sta_arp_timeout": 0, | ||||
|         "sta_dns_timeout": 0, | ||||
|         "sta_ip_timeout": 0, | ||||
|         "stream_eff": 0, | ||||
|         "tcp_latency": 0, | ||||
|         "tcp_packet_loss": 0, | ||||
|         "wifi_latency": 0, | ||||
|         "wifi_retries": 0 | ||||
|       }, | ||||
|       "reasons_bar_chart_now": { | ||||
|         "phy_rate": 0, | ||||
|         "signal": 0, | ||||
|         "sleepy_client": 0, | ||||
|         "sta_arp_timeout": 0, | ||||
|         "sta_dns_timeout": 0, | ||||
|         "sta_ip_timeout": 0, | ||||
|         "stream_eff": 0, | ||||
|         "tcp_latency": 0, | ||||
|         "tcp_packet_loss": 0, | ||||
|         "wifi_latency": 0, | ||||
|         "wifi_retries": 0 | ||||
|       }, | ||||
|       "rx_bytes": 0, | ||||
|       "rx_crypts": 0, | ||||
|       "rx_dropped": 0, | ||||
|       "rx_errors": 0, | ||||
|       "rx_frags": 0, | ||||
|       "rx_nwids": 120385, | ||||
|       "rx_packets": 0, | ||||
|       "rx_tcp_stats": { | ||||
|         "goodbytes": 0, | ||||
|         "lat_avg": 0, | ||||
|         "lat_max": 0, | ||||
|         "lat_min": 4294967295, | ||||
|         "stalls": 0 | ||||
|       }, | ||||
|       "satisfaction": -1, | ||||
|       "satisfaction_now": -1, | ||||
|       "state": "RUN", | ||||
|       "tx_bytes": 0, | ||||
|       "tx_combined_retries": 0, | ||||
|       "tx_data_mpdu_bytes": 0, | ||||
|       "tx_dropped": 20262, | ||||
|       "tx_errors": 0, | ||||
|       "tx_packets": 0, | ||||
|       "tx_power": 22, | ||||
|       "tx_retries": 0, | ||||
|       "tx_rts_retries": 0, | ||||
|       "tx_success": 0, | ||||
|       "tx_tcp_stats": { | ||||
|         "goodbytes": 0, | ||||
|         "lat_avg": 0, | ||||
|         "lat_max": 0, | ||||
|         "lat_min": 4294967295, | ||||
|         "stalls": 0 | ||||
|       }, | ||||
|       "tx_total": 0, | ||||
|       "up": true, | ||||
|       "usage": "guest", | ||||
|       "wifi_tx_attempts": 0, | ||||
|       "wifi_tx_dropped": 0, | ||||
|       "wifi_tx_latency_mov": { | ||||
|         "avg": 0, | ||||
|         "max": 0, | ||||
|         "min": 4294967295, | ||||
|         "total": 0, | ||||
|         "total_count": 0 | ||||
|       }, | ||||
|       "t": "vap", | ||||
|       "wlanconf_id": "574e96834566ffb914a26875", | ||||
|       "is_guest": true, | ||||
|       "is_wep": false, | ||||
|       "ap_mac": "80:22:a8:22:22:22", | ||||
|       "map_id": null, | ||||
|       "site_id": "574e86994566ffb914a2683c" | ||||
|     }, | ||||
|     { | ||||
|       "avg_client_signal": -68, | ||||
|       "bssid": "80:22:22:22:22:22", | ||||
|       "ccq": 966, | ||||
|       "channel": 6, | ||||
|       "essid": "Extra Fast", | ||||
|       "extchannel": 1, | ||||
|       "id": "574e96614566ffb914a26874", | ||||
|       "mac_filter_rejections": 0, | ||||
|       "name": "ath0", | ||||
|       "num_satisfaction_sta": 2, | ||||
|       "num_sta": 2, | ||||
|       "radio": "ng", | ||||
|       "radio_name": "wifi0", | ||||
|       "reasons_bar_chart": { | ||||
|         "phy_rate": 0, | ||||
|         "signal": 0, | ||||
|         "sleepy_client": 0, | ||||
|         "sta_arp_timeout": 0, | ||||
|         "sta_dns_timeout": 0, | ||||
|         "sta_ip_timeout": 0, | ||||
|         "stream_eff": 0, | ||||
|         "tcp_latency": 0, | ||||
|         "tcp_packet_loss": 0, | ||||
|         "wifi_latency": 0, | ||||
|         "wifi_retries": 1 | ||||
|       }, | ||||
|       "reasons_bar_chart_now": { | ||||
|         "phy_rate": 0, | ||||
|         "signal": 0, | ||||
|         "sleepy_client": 0, | ||||
|         "sta_arp_timeout": 0, | ||||
|         "sta_dns_timeout": 0, | ||||
|         "sta_ip_timeout": 0, | ||||
|         "stream_eff": 0, | ||||
|         "tcp_latency": 0, | ||||
|         "tcp_packet_loss": 0, | ||||
|         "wifi_latency": 0, | ||||
|         "wifi_retries": 1 | ||||
|       }, | ||||
|       "rx_bytes": 1017366419, | ||||
|       "rx_crypts": 11013, | ||||
|       "rx_dropped": 12764, | ||||
|       "rx_errors": 12764, | ||||
|       "rx_frags": 0, | ||||
|       "rx_nwids": 177145, | ||||
|       "rx_packets": 22542668, | ||||
|       "rx_tcp_stats": { | ||||
|         "goodbytes": 114220296, | ||||
|         "lat_avg": 5, | ||||
|         "lat_max": 10, | ||||
|         "lat_min": 10, | ||||
|         "stalls": 0 | ||||
|       }, | ||||
|       "satisfaction": 93, | ||||
|       "satisfaction_now": 94, | ||||
|       "state": "RUN", | ||||
|       "tx_bytes": 965488630, | ||||
|       "tx_combined_retries": 8202639, | ||||
|       "tx_data_mpdu_bytes": 1145631754, | ||||
|       "tx_dropped": 43, | ||||
|       "tx_errors": 0, | ||||
|       "tx_packets": 22623798, | ||||
|       "tx_power": 22, | ||||
|       "tx_retries": 7194267, | ||||
|       "tx_rts_retries": 1008372, | ||||
|       "tx_success": 9545999, | ||||
|       "tx_tcp_stats": { | ||||
|         "goodbytes": 182912739, | ||||
|         "lat_avg": 5, | ||||
|         "lat_max": 10, | ||||
|         "lat_min": 10, | ||||
|         "stalls": 0 | ||||
|       }, | ||||
|       "tx_total": 9547096, | ||||
|       "up": true, | ||||
|       "usage": "user", | ||||
|       "wifi_tx_attempts": 16740276, | ||||
|       "wifi_tx_dropped": 1095, | ||||
|       "wifi_tx_latency_mov": { | ||||
|         "avg": 673, | ||||
|         "max": 13612, | ||||
|         "min": 0, | ||||
|         "total": 263176, | ||||
|         "total_count": 391 | ||||
|       }, | ||||
|       "t": "vap", | ||||
|       "wlanconf_id": "574e96614566ffb914a26874", | ||||
|       "is_guest": false, | ||||
|       "is_wep": false, | ||||
|       "ap_mac": "80:22:22:22:22:22", | ||||
|       "map_id": null, | ||||
|       "site_id": "574e86994566ffb914a2683c" | ||||
|     } | ||||
|   ], | ||||
|   "downlink_table": [], | ||||
|   "vwire_vap_table": [], | ||||
|   "bytes-d": 204913232, | ||||
|   "tx_bytes-d": 1921569, | ||||
|   "rx_bytes-d": 202991663, | ||||
|   "bytes-r": 9757772, | ||||
|   "last_uplink": { | ||||
|     "uplink_mac": "f0:22:22:22:22:22", | ||||
|     "uplink_remote_port": 15 | ||||
|   }, | ||||
|   "stat": { | ||||
|     "site_id": "574e86994566ffb914a2683c", | ||||
|     "o": "ap", | ||||
|     "oid": "80:22:a8:22:22:22", | ||||
|     "ap": "80:22:a8:22:22:22", | ||||
|     "time": 1562207100000, | ||||
|     "datetime": "2019-07-04T02:25:00Z", | ||||
|     "guest-wifi0-rx_packets": 0, | ||||
|     "guest-wifi1-rx_packets": 0, | ||||
|     "user-wifi1-rx_packets": 31373230, | ||||
|     "user-wifi0-rx_packets": 169790, | ||||
|     "user-rx_packets": 31543020, | ||||
|     "guest-rx_packets": 0, | ||||
|     "wifi0-rx_packets": 169790, | ||||
|     "wifi1-rx_packets": 31373230, | ||||
|     "rx_packets": 31543020, | ||||
|     "guest-wifi0-rx_bytes": 0, | ||||
|     "guest-wifi1-rx_bytes": 0, | ||||
|     "user-wifi1-rx_bytes": 42049645434, | ||||
|     "user-wifi0-rx_bytes": 16755639, | ||||
|     "user-rx_bytes": 42066401073, | ||||
|     "guest-rx_bytes": 0, | ||||
|     "wifi0-rx_bytes": 16755639, | ||||
|     "wifi1-rx_bytes": 42049645434, | ||||
|     "rx_bytes": 42066401073, | ||||
|     "guest-wifi0-rx_errors": 0, | ||||
|     "guest-wifi1-rx_errors": 0, | ||||
|     "user-wifi1-rx_errors": 150651, | ||||
|     "user-wifi0-rx_errors": 0, | ||||
|     "user-rx_errors": 150651, | ||||
|     "guest-rx_errors": 0, | ||||
|     "wifi0-rx_errors": 0, | ||||
|     "wifi1-rx_errors": 150651, | ||||
|     "rx_errors": 150651, | ||||
|     "guest-wifi0-rx_dropped": 0, | ||||
|     "guest-wifi1-rx_dropped": 0, | ||||
|     "user-wifi1-rx_dropped": 150651, | ||||
|     "user-wifi0-rx_dropped": 0, | ||||
|     "user-rx_dropped": 150651, | ||||
|     "guest-rx_dropped": 0, | ||||
|     "wifi0-rx_dropped": 0, | ||||
|     "wifi1-rx_dropped": 150651, | ||||
|     "rx_dropped": 150651, | ||||
|     "guest-wifi0-rx_crypts": 0, | ||||
|     "guest-wifi1-rx_crypts": 0, | ||||
|     "user-wifi1-rx_crypts": 150651, | ||||
|     "user-wifi0-rx_crypts": 0, | ||||
|     "user-rx_crypts": 150651, | ||||
|     "guest-rx_crypts": 0, | ||||
|     "wifi0-rx_crypts": 0, | ||||
|     "wifi1-rx_crypts": 150651, | ||||
|     "rx_crypts": 150651, | ||||
|     "guest-wifi0-rx_frags": 0, | ||||
|     "guest-wifi1-rx_frags": 0, | ||||
|     "user-wifi1-rx_frags": 0, | ||||
|     "user-wifi0-rx_frags": 0, | ||||
|     "user-rx_frags": 0, | ||||
|     "guest-rx_frags": 0, | ||||
|     "wifi0-rx_frags": 0, | ||||
|     "wifi1-rx_frags": 0, | ||||
|     "rx_frags": 0, | ||||
|     "guest-wifi0-tx_packets": 0, | ||||
|     "guest-wifi1-tx_packets": 0, | ||||
|     "user-wifi1-tx_packets": 7125589, | ||||
|     "user-wifi0-tx_packets": 210389, | ||||
|     "user-tx_packets": 7335978, | ||||
|     "guest-tx_packets": 0, | ||||
|     "wifi0-tx_packets": 210389, | ||||
|     "wifi1-tx_packets": 7125589, | ||||
|     "tx_packets": 7335978, | ||||
|     "guest-wifi0-tx_bytes": 0, | ||||
|     "guest-wifi1-tx_bytes": 0, | ||||
|     "user-wifi1-tx_bytes": 3011293823, | ||||
|     "user-wifi0-tx_bytes": 25966558, | ||||
|     "user-tx_bytes": 3037260381, | ||||
|     "guest-tx_bytes": 0, | ||||
|     "wifi0-tx_bytes": 25966558, | ||||
|     "wifi1-tx_bytes": 3011293823, | ||||
|     "tx_bytes": 3037260381, | ||||
|     "guest-wifi0-tx_errors": 0, | ||||
|     "guest-wifi1-tx_errors": 0, | ||||
|     "user-wifi1-tx_errors": 102193, | ||||
|     "user-wifi0-tx_errors": 0, | ||||
|     "user-tx_errors": 102193, | ||||
|     "guest-tx_errors": 0, | ||||
|     "wifi0-tx_errors": 0, | ||||
|     "wifi1-tx_errors": 102193, | ||||
|     "tx_errors": 102193, | ||||
|     "guest-wifi0-tx_dropped": 296, | ||||
|     "guest-wifi1-tx_dropped": 296, | ||||
|     "user-wifi1-tx_dropped": 0, | ||||
|     "user-wifi0-tx_dropped": 0, | ||||
|     "user-tx_dropped": 0, | ||||
|     "guest-tx_dropped": 592, | ||||
|     "wifi0-tx_dropped": 296, | ||||
|     "wifi1-tx_dropped": 296, | ||||
|     "tx_dropped": 592, | ||||
|     "guest-wifi0-tx_retries": 0, | ||||
|     "guest-wifi1-tx_retries": 0, | ||||
|     "user-wifi1-tx_retries": 519734, | ||||
|     "user-wifi0-tx_retries": 90225, | ||||
|     "user-tx_retries": 609959, | ||||
|     "guest-tx_retries": 0, | ||||
|     "wifi0-tx_retries": 90225, | ||||
|     "wifi1-tx_retries": 519734, | ||||
|     "tx_retries": 609959, | ||||
|     "guest-wifi0-mac_filter_rejections": 0, | ||||
|     "guest-wifi1-mac_filter_rejections": 0, | ||||
|     "user-wifi1-mac_filter_rejections": 0, | ||||
|     "user-wifi0-mac_filter_rejections": 0, | ||||
|     "user-mac_filter_rejections": 0, | ||||
|     "guest-mac_filter_rejections": 0, | ||||
|     "wifi0-mac_filter_rejections": 0, | ||||
|     "wifi1-mac_filter_rejections": 0, | ||||
|     "mac_filter_rejections": 0, | ||||
|     "guest-wifi0-wifi_tx_attempts": 0, | ||||
|     "guest-wifi1-wifi_tx_attempts": 0, | ||||
|     "user-wifi1-wifi_tx_attempts": 4419026, | ||||
|     "user-wifi0-wifi_tx_attempts": 255999, | ||||
|     "user-wifi_tx_attempts": 4675025, | ||||
|     "guest-wifi_tx_attempts": 0, | ||||
|     "wifi0-wifi_tx_attempts": 255999, | ||||
|     "wifi1-wifi_tx_attempts": 4419026, | ||||
|     "wifi_tx_attempts": 4675025, | ||||
|     "guest-wifi0-wifi_tx_dropped": 0, | ||||
|     "guest-wifi1-wifi_tx_dropped": 0, | ||||
|     "user-wifi1-wifi_tx_dropped": 25, | ||||
|     "user-wifi0-wifi_tx_dropped": 2, | ||||
|     "user-wifi_tx_dropped": 27, | ||||
|     "guest-wifi_tx_dropped": 0, | ||||
|     "wifi0-wifi_tx_dropped": 2, | ||||
|     "wifi1-wifi_tx_dropped": 25, | ||||
|     "wifi_tx_dropped": 27, | ||||
|     "bytes": 45103661454, | ||||
|     "duration": 17988000, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-rx_packets": 31373230, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-rx_bytes": 42049645434, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-tx_packets": 7125589, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-tx_bytes": 3011293823, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-tx_errors": 102193, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-tx_retries": 519734, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-wifi_tx_attempts": 4419026, | ||||
|     "user-wifi0-ath0-574e96614566ffb914a26874-rx_packets": 169790, | ||||
|     "user-wifi0-ath0-574e96614566ffb914a26874-rx_bytes": 16755639, | ||||
|     "user-wifi0-ath0-574e96614566ffb914a26874-tx_packets": 210389, | ||||
|     "user-wifi0-ath0-574e96614566ffb914a26874-tx_bytes": 25966558, | ||||
|     "user-wifi0-ath0-574e96614566ffb914a26874-tx_retries": 90225, | ||||
|     "user-wifi0-ath0-574e96614566ffb914a26874-wifi_tx_attempts": 255999, | ||||
|     "guest-wifi1-ath3-574e96834566ffb914a26875-tx_dropped": 296, | ||||
|     "guest-wifi0-ath1-574e96834566ffb914a26875-tx_dropped": 296, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-rx_errors": 150651, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-rx_dropped": 150651, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-rx_crypts": 150651, | ||||
|     "user-wifi0-ath0-574e96614566ffb914a26874-wifi_tx_dropped": 2, | ||||
|     "user-wifi1-ath2-574e96614566ffb914a26874-wifi_tx_dropped": 25 | ||||
|   }, | ||||
|   "tx_bytes": 3037260381, | ||||
|   "rx_bytes": 42066401073, | ||||
|   "bytes": 45103661454, | ||||
|   "vwireEnabled": false, | ||||
|   "uplink_table": [], | ||||
|   "num_sta": 6, | ||||
|   "user-num_sta": 6, | ||||
|   "guest-num_sta": 0 | ||||
| } | ||||
|  | @ -0,0 +1,371 @@ | |||
| { | ||||
|   "_id": "59a35da745663e6cc82600f6", | ||||
|   "adopted": true, | ||||
|   "cfgversion": "bf9f0335063fe6ea", | ||||
|   "config_network": { | ||||
|     "type": "dhcp", | ||||
|     "ip": "192.168.2.0" | ||||
|   }, | ||||
|   "ethernet_table": [ | ||||
|     { | ||||
|       "mac": "22:22:00:22:22:00", | ||||
|       "num_port": 1, | ||||
|       "name": "eth0" | ||||
|     }, | ||||
|     { | ||||
|       "mac": "22:22:00:22:22:00", | ||||
|       "num_port": 1, | ||||
|       "name": "eth1" | ||||
|     }, | ||||
|     { | ||||
|       "mac": "22:22:00:22:22:00", | ||||
|       "num_port": 1, | ||||
|       "name": "eth2" | ||||
|     } | ||||
|   ], | ||||
|   "fw_caps": 184323, | ||||
|   "inform_ip": "192.168.3.1", | ||||
|   "inform_url": "http://security:8080/inform", | ||||
|   "ip": "3.1.33.7", | ||||
|   "led_override": "default", | ||||
|   "license_state": "registered", | ||||
|   "mac": "22:22:00:22:22:00", | ||||
|   "model": "UGW3", | ||||
|   "name": "gateway", | ||||
|   "outdoor_mode_override": "default", | ||||
|   "serial": "xxxyyyzzz", | ||||
|   "site_id": "574e86994566ffb914a2683c", | ||||
|   "type": "ugw", | ||||
|   "usg_caps": 786431, | ||||
|   "version": "4.4.41.5193700", | ||||
|   "required_version": "4.0.0", | ||||
|   "ethernet_overrides": [ | ||||
|     { | ||||
|       "ifname": "eth1", | ||||
|       "networkgroup": "LAN" | ||||
|     }, | ||||
|     { | ||||
|       "ifname": "eth0", | ||||
|       "networkgroup": "WAN" | ||||
|     } | ||||
|   ], | ||||
|   "hw_caps": 0, | ||||
|   "board_rev": 16, | ||||
|   "unsupported": false, | ||||
|   "unsupported_reason": 0, | ||||
|   "device_id": "59a35da745663e6cc82600f6", | ||||
|   "state": 1, | ||||
|   "last_seen": 1562311857, | ||||
|   "upgradable": false, | ||||
|   "adoptable_when_upgraded": false, | ||||
|   "rollupgrade": false, | ||||
|   "known_cfgversion": "bf9f0335063fe6ea", | ||||
|   "uptime": 3191626, | ||||
|   "_uptime": 3191626, | ||||
|   "locating": false, | ||||
|   "connect_request_ip": "192.168.2.1", | ||||
|   "connect_request_port": "35615", | ||||
|   "sys_stats": { | ||||
|     "loadavg_1": "0.01", | ||||
|     "loadavg_15": "0.12", | ||||
|     "loadavg_5": "0.06", | ||||
|     "mem_buffer": 62406656, | ||||
|     "mem_total": 507412480, | ||||
|     "mem_used": 397500416 | ||||
|   }, | ||||
|   "system-stats": { | ||||
|     "cpu": "14", | ||||
|     "mem": "30", | ||||
|     "uptime": "3191066" | ||||
|   }, | ||||
|   "guest_token": "83342830AE9C0641DC39DD2759C122A1", | ||||
|   "speedtest-status": { | ||||
|     "latency": 14, | ||||
|     "rundate": 1562310531, | ||||
|     "runtime": 172, | ||||
|     "status_download": 2, | ||||
|     "status_ping": 2, | ||||
|     "status_summary": 2, | ||||
|     "status_upload": 2, | ||||
|     "xput_download": 157.36776733398438, | ||||
|     "xput_upload": 37.90521240234375 | ||||
|   }, | ||||
|   "speedtest-status-saved": true, | ||||
|   "wan1": { | ||||
|     "tx_bytes-r": 2852355, | ||||
|     "rx_bytes-r": 1224743, | ||||
|     "bytes-r": 4077098, | ||||
|     "max_speed": 1000, | ||||
|     "type": "wire", | ||||
|     "name": "wan", | ||||
|     "ifname": "eth0", | ||||
|     "ip": "3.1.33.7", | ||||
|     "netmask": "255.255.254.0", | ||||
|     "mac": "22:22:00:22:22:00", | ||||
|     "up": true, | ||||
|     "speed": 1000, | ||||
|     "full_duplex": true, | ||||
|     "rx_bytes": 2648236513108, | ||||
|     "rx_dropped": 34030, | ||||
|     "rx_errors": 0, | ||||
|     "rx_packets": 3068347172, | ||||
|     "tx_bytes": 3009601283006, | ||||
|     "tx_dropped": 0, | ||||
|     "tx_errors": 0, | ||||
|     "tx_packets": 2859713220, | ||||
|     "rx_multicast": 939111, | ||||
|     "enable": true, | ||||
|     "dns": [ | ||||
|       "1.1.1.1", | ||||
|       "8.8.8.8" | ||||
|     ], | ||||
|     "gateway": "3.1.33.8" | ||||
|   }, | ||||
|   "port_table": [ | ||||
|     { | ||||
|       "name": "wan", | ||||
|       "ifname": "eth0", | ||||
|       "ip": "3.1.33.7", | ||||
|       "netmask": "255.255.254.0", | ||||
|       "mac": "22:22:00:22:22:00", | ||||
|       "up": true, | ||||
|       "speed": 1000, | ||||
|       "full_duplex": true, | ||||
|       "rx_bytes": 2648236513108, | ||||
|       "rx_dropped": 34030, | ||||
|       "rx_errors": 0, | ||||
|       "rx_packets": 3068347172, | ||||
|       "tx_bytes": 3009601283006, | ||||
|       "tx_dropped": 0, | ||||
|       "tx_errors": 0, | ||||
|       "tx_packets": 2859713220, | ||||
|       "rx_multicast": 939111, | ||||
|       "enable": true, | ||||
|       "dns": [ | ||||
|         "216.146.35.35", | ||||
|         "216.146.36.36" | ||||
|       ], | ||||
|       "gateway": "3.1.33.8" | ||||
|     }, | ||||
|     { | ||||
|       "name": "lan", | ||||
|       "ifname": "eth1", | ||||
|       "ip": "192.168.2.1", | ||||
|       "netmask": "255.255.252.0", | ||||
|       "mac": "22:22:00:22:22:00", | ||||
|       "up": true, | ||||
|       "speed": 1000, | ||||
|       "full_duplex": true, | ||||
|       "rx_bytes": 2911311797255, | ||||
|       "rx_dropped": 3438, | ||||
|       "rx_errors": 0, | ||||
|       "rx_packets": 2659342049, | ||||
|       "tx_bytes": 2140222188895, | ||||
|       "tx_dropped": 0, | ||||
|       "tx_errors": 0, | ||||
|       "tx_packets": 2734245088, | ||||
|       "rx_multicast": 11929365, | ||||
|       "enable": true | ||||
|     }, | ||||
|     { | ||||
|       "name": "lan2", | ||||
|       "ifname": "eth2", | ||||
|       "ip": "0.0.0.0", | ||||
|       "netmask": "0.0.0.0", | ||||
|       "mac": "22:22:00:22:22:00", | ||||
|       "up": false, | ||||
|       "speed": 0, | ||||
|       "full_duplex": false, | ||||
|       "rx_bytes": 0, | ||||
|       "rx_dropped": 0, | ||||
|       "rx_errors": 0, | ||||
|       "rx_packets": 0, | ||||
|       "tx_bytes": 0, | ||||
|       "tx_dropped": 0, | ||||
|       "tx_errors": 0, | ||||
|       "tx_packets": 0, | ||||
|       "rx_multicast": 0, | ||||
|       "enable": false | ||||
|     } | ||||
|   ], | ||||
|   "network_table": [ | ||||
|     { | ||||
|       "_id": "574e8de34566ffb914a26862", | ||||
|       "is_nat": true, | ||||
|       "dhcpd_dns_enabled": false, | ||||
|       "purpose": "guest", | ||||
|       "dhcpd_leasetime": "86400", | ||||
|       "igmp_snooping": true, | ||||
|       "dhcpguard_enabled": false, | ||||
|       "dhcpd_start": "192.168.5.1", | ||||
|       "enabled": true, | ||||
|       "dhcpd_stop": "192.168.5.254", | ||||
|       "dhcpd_wins_enabled": false, | ||||
|       "domain_name": "guest.lan", | ||||
|       "dhcpd_enabled": true, | ||||
|       "ip_subnet": "192.168.5.0/23", | ||||
|       "vlan": "5", | ||||
|       "networkgroup": "LAN", | ||||
|       "name": "Public Wireless", | ||||
|       "site_id": "574e86994566ffb914a2683c", | ||||
|       "dhcpd_ip_1": "", | ||||
|       "vlan_enabled": true, | ||||
|       "dhcpd_gateway_enabled": false, | ||||
|       "dhcpd_time_offset_enabled": false, | ||||
|       "ipv6_interface_type": "none", | ||||
|       "dhcp_relay_enabled": false, | ||||
|       "mac": "22:22:00:22:22:00", | ||||
|       "is_guest": true, | ||||
|       "ip": "192.168.5.0", | ||||
|       "up": "true", | ||||
|       "num_sta": 1, | ||||
|       "rx_bytes": 578602537, | ||||
|       "rx_packets": 471151, | ||||
|       "tx_bytes": 182318948, | ||||
|       "tx_packets": 239651 | ||||
|     }, | ||||
|     { | ||||
|       "_id": "59a362f645663e6cc8260133", | ||||
|       "is_nat": true, | ||||
|       "dhcpd_dns_enabled": false, | ||||
|       "purpose": "corporate", | ||||
|       "dhcpd_leasetime": 86400, | ||||
|       "dhcpd_start": "192.168.68.2", | ||||
|       "dhcpd_stop": "192.168.68.250", | ||||
|       "enabled": true, | ||||
|       "domain_name": "secure.lan", | ||||
|       "dhcpd_enabled": true, | ||||
|       "vlan": "69", | ||||
|       "ip_subnet": "192.168.69.1/23", | ||||
|       "networkgroup": "LAN", | ||||
|       "name": "Security Network", | ||||
|       "site_id": "574e86994566ffb914a2683c", | ||||
|       "vlan_enabled": true, | ||||
|       "dhcpd_ntp_1": "192.168.69.1", | ||||
|       "dhcpd_gateway_enabled": false, | ||||
|       "dhcpd_time_offset_enabled": false, | ||||
|       "dhcp_relay_enabled": false, | ||||
|       "dhcpd_ntp_enabled": true, | ||||
|       "ipv6_interface_type": "none", | ||||
|       "dhcpd_unifi_controller": "192.168.3.1", | ||||
|       "igmp_snooping": true, | ||||
|       "mac": "22:22:00:22:22:00", | ||||
|       "is_guest": false, | ||||
|       "ip": "192.168.69.1", | ||||
|       "up": "true", | ||||
|       "num_sta": 11, | ||||
|       "rx_bytes": 5221725, | ||||
|       "rx_packets": 70663, | ||||
|       "tx_bytes": 0, | ||||
|       "tx_packets": 0 | ||||
|     }, | ||||
|     { | ||||
|       "_id": "574e869d4566ffb914a26841", | ||||
|       "purpose": "corporate", | ||||
|       "dhcpd_leasetime": "86400", | ||||
|       "igmp_snooping": false, | ||||
|       "dhcpd_ntp_1": "192.168.2.1", | ||||
|       "dhcpguard_enabled": false, | ||||
|       "dhcpd_gateway_enabled": false, | ||||
|       "dhcpd_time_offset_enabled": false, | ||||
|       "dhcpd_start": "192.168.1.1", | ||||
|       "dhcpd_unifi_controller": "192.168.3.1", | ||||
|       "dhcpd_stop": "192.168.1.254", | ||||
|       "enabled": true, | ||||
|       "domain_name": "home.lan", | ||||
|       "dhcpd_enabled": true, | ||||
|       "ip_subnet": "192.168.2.1/22", | ||||
|       "networkgroup": "LAN", | ||||
|       "dhcpd_ip_1": "", | ||||
|       "vlan_enabled": false, | ||||
|       "is_nat": true, | ||||
|       "dhcpd_dns_enabled": false, | ||||
|       "dhcp_relay_enabled": false, | ||||
|       "dhcpd_wins_enabled": false, | ||||
|       "upnp_lan_enabled": true, | ||||
|       "dhcpd_ntp_enabled": true, | ||||
|       "name": "Home Network", | ||||
|       "site_id": "574e86994566ffb914a2683c", | ||||
|       "attr_no_delete": true, | ||||
|       "attr_hidden_id": "LAN", | ||||
|       "ipv6_interface_type": "none", | ||||
|       "mac": "22:22:00:22:22:00", | ||||
|       "is_guest": false, | ||||
|       "ip": "192.168.2.1", | ||||
|       "up": "true", | ||||
|       "num_sta": 30, | ||||
|       "rx_bytes": 2099754971983, | ||||
|       "rx_packets": 2689749160, | ||||
|       "tx_bytes": 2877873632166, | ||||
|       "tx_packets": 2579198457 | ||||
|     } | ||||
|   ], | ||||
|   "uplink": { | ||||
|     "drops": 40, | ||||
|     "enable": true, | ||||
|     "full_duplex": true, | ||||
|     "gateways": [ | ||||
|       "3.1.33.8" | ||||
|     ], | ||||
|     "ip": "3.1.33.7", | ||||
|     "latency": 103, | ||||
|     "mac": "22:22:00:22:22:00", | ||||
|     "name": "eth0", | ||||
|     "nameservers": [ | ||||
|       "1.1.1.1", | ||||
|       "8.8.8.8" | ||||
|     ], | ||||
|     "netmask": "255.255.254.0", | ||||
|     "num_port": 1, | ||||
|     "rx_bytes": 2648236513108, | ||||
|     "rx_dropped": 34030, | ||||
|     "rx_errors": 0, | ||||
|     "rx_multicast": 939111, | ||||
|     "rx_packets": 3068347172, | ||||
|     "speed": 1000, | ||||
|     "speedtest_lastrun": 1562310531, | ||||
|     "speedtest_ping": 14, | ||||
|     "speedtest_status": "Success", | ||||
|     "tx_bytes": 3009601283006, | ||||
|     "tx_dropped": 0, | ||||
|     "tx_errors": 0, | ||||
|     "tx_packets": 2859713220, | ||||
|     "up": true, | ||||
|     "uptime": 559088, | ||||
|     "xput_down": 157.368, | ||||
|     "xput_up": 37.905, | ||||
|     "tx_bytes-r": 2852355, | ||||
|     "rx_bytes-r": 1224743, | ||||
|     "bytes-r": 4077098, | ||||
|     "max_speed": 1000, | ||||
|     "type": "wire" | ||||
|   }, | ||||
|   "stat": { | ||||
|     "site_id": "574e86994566ffb914a2683c", | ||||
|     "o": "gw", | ||||
|     "oid": "22:22:00:22:22:00", | ||||
|     "gw": "22:22:00:22:22:00", | ||||
|     "time": 1562207100000, | ||||
|     "datetime": "2019-07-04T02:25:00Z", | ||||
|     "duration": 104466000, | ||||
|     "wan-rx_packets": 151387924, | ||||
|     "wan-rx_bytes": 111251311739, | ||||
|     "wan-tx_packets": 182985900, | ||||
|     "wan-tx_bytes": 230372237709, | ||||
|     "lan-rx_packets": 173953163, | ||||
|     "lan-rx_bytes": 226862410885, | ||||
|     "lan-tx_packets": 137029474, | ||||
|     "lan-tx_bytes": 89478206254, | ||||
|     "wan-rx_dropped": 561, | ||||
|     "lan-rx_dropped": 29 | ||||
|   }, | ||||
|   "tx_bytes": 2648236513108, | ||||
|   "rx_bytes": 3009601283006, | ||||
|   "bytes": 5657837796114, | ||||
|   "num_sta": 41, | ||||
|   "user-num_sta": 41, | ||||
|   "guest-num_sta": 0, | ||||
|   "num_desktop": 7, | ||||
|   "num_mobile": 2, | ||||
|   "num_handheld": 8 | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,9 @@ | |||
| module github.com/unpoller/unifi | ||||
| 
 | ||||
| go 1.16 | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||
| 	github.com/stretchr/testify v1.4.0 | ||||
| 	golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d | ||||
| ) | ||||
|  | @ -0,0 +1,19 @@ | |||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= | ||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||
| golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d h1:BgJvlyh+UqCUaPlscHJ+PN8GcpfrFdr7NHjd1JL0+Gs= | ||||
| golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= | ||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | ||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
|  | @ -0,0 +1,155 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // IDS holds an Intrusion Prevention System Event.
 | ||||
| type IDS struct { | ||||
| 	Archived              FlexBool  `json:"archived"` | ||||
| 	DestPort              int       `json:"dest_port,omitempty"` | ||||
| 	SrcPort               int       `json:"src_port,omitempty"` | ||||
| 	FlowID                int64     `json:"flow_id"` | ||||
| 	InnerAlertRev         int64     `json:"inner_alert_rev"` | ||||
| 	InnerAlertSeverity    int64     `json:"inner_alert_severity"` | ||||
| 	InnerAlertGID         int64     `json:"inner_alert_gid"` | ||||
| 	InnerAlertSignatureID int64     `json:"inner_alert_signature_id"` | ||||
| 	Time                  int64     `json:"time"` | ||||
| 	Timestamp             int64     `json:"timestamp"` | ||||
| 	Datetime              time.Time `json:"datetime"` | ||||
| 	AppProto              string    `json:"app_proto,omitempty"` | ||||
| 	Catname               string    `json:"catname"` | ||||
| 	DestIP                string    `json:"dest_ip"` | ||||
| 	DstMAC                string    `json:"dst_mac"` | ||||
| 	DstIPASN              string    `json:"dstipASN"` | ||||
| 	DstIPCountry          string    `json:"dstipCountry"` | ||||
| 	EventType             string    `json:"event_type"` | ||||
| 	Host                  string    `json:"host"` | ||||
| 	ID                    string    `json:"_id"` | ||||
| 	InIface               string    `json:"in_iface"` | ||||
| 	InnerAlertAction      string    `json:"inner_alert_action"` | ||||
| 	InnerAlertCategory    string    `json:"inner_alert_category"` | ||||
| 	InnerAlertSignature   string    `json:"inner_alert_signature"` | ||||
| 	Key                   string    `json:"key"` | ||||
| 	Msg                   string    `json:"msg"` | ||||
| 	Proto                 string    `json:"proto"` | ||||
| 	SiteID                string    `json:"site_id"` | ||||
| 	SiteName              string    `json:"-"` | ||||
| 	SourceName            string    `json:"-"` | ||||
| 	SrcIP                 string    `json:"src_ip"` | ||||
| 	SrcIPASN              string    `json:"srcipASN"` | ||||
| 	SrcIPCountry          string    `json:"srcipCountry"` | ||||
| 	SrcMAC                string    `json:"src_mac"` | ||||
| 	Subsystem             string    `json:"subsystem"` | ||||
| 	UniqueAlertID         string    `json:"unique_alertid"` | ||||
| 	USGIP                 string    `json:"usgip"` | ||||
| 	USGIPASN              string    `json:"usgipASN"` | ||||
| 	USGIPCountry          string    `json:"usgipCountry"` | ||||
| 	DestIPGeo             IPGeo     `json:"dstipGeo"` | ||||
| 	SourceIPGeo           IPGeo     `json:"srcipGeo"` | ||||
| 	USGIPGeo              IPGeo     `json:"usgipGeo"` | ||||
| } | ||||
| 
 | ||||
| // GetIDS returns Intrusion Detection Systems events for a list of Sites.
 | ||||
| // timeRange may have a length of 0, 1 or 2. The first time is Start, the second is End.
 | ||||
| // Events between start and end are returned. End defaults to time.Now().
 | ||||
| func (u *Unifi) GetIDS(sites []*Site, timeRange ...time.Time) ([]*IDS, error) { | ||||
| 	data := []*IDS{} | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		response, err := u.GetIDSSite(site, timeRange...) | ||||
| 		if err != nil { | ||||
| 			return data, err | ||||
| 		} | ||||
| 
 | ||||
| 		data = append(data, response...) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // GetIDSSite retreives the Intrusion Detection System Data for a single Site.
 | ||||
| // timeRange may have a length of 0, 1 or 2. The first time is Start, the second is End.
 | ||||
| // Events between start and end are returned. End defaults to time.Now().
 | ||||
| func (u *Unifi) GetIDSSite(site *Site, timeRange ...time.Time) ([]*IDS, error) { | ||||
| 	if site == nil || site.Name == "" { | ||||
| 		return nil, ErrNoSiteProvided | ||||
| 	} | ||||
| 
 | ||||
| 	u.DebugLog("Polling Controller for IDS Events, site %s", site.SiteName) | ||||
| 
 | ||||
| 	var ( | ||||
| 		path = fmt.Sprintf(APIEventPathIDS, site.Name) | ||||
| 		ids  struct { | ||||
| 			Data idsList `json:"data"` | ||||
| 		} | ||||
| 	) | ||||
| 
 | ||||
| 	if params, err := makeEventParams(timeRange...); err != nil { | ||||
| 		return ids.Data, err | ||||
| 	} else if err = u.GetData(path, &ids, params); err != nil { | ||||
| 		return ids.Data, err | ||||
| 	} | ||||
| 
 | ||||
| 	for i := range ids.Data { | ||||
| 		// Add special SourceName value.
 | ||||
| 		ids.Data[i].SourceName = u.URL | ||||
| 		// Add the special "Site Name" to each event. This becomes a Grafana filter somewhere.
 | ||||
| 		ids.Data[i].SiteName = site.SiteName | ||||
| 	} | ||||
| 
 | ||||
| 	sort.Sort(ids.Data) | ||||
| 
 | ||||
| 	return ids.Data, nil | ||||
| } | ||||
| 
 | ||||
| func makeEventParams(timeRange ...time.Time) (string, error) { | ||||
| 	type eventReq struct { | ||||
| 		Start int64  `json:"start,omitempty"` | ||||
| 		End   int64  `json:"end,omitempty"` | ||||
| 		Limit int    `json:"_limit,omitempty"` | ||||
| 		Sort  string `json:"_sort"` | ||||
| 	} | ||||
| 
 | ||||
| 	rp := eventReq{Limit: eventLimit, Sort: "-time"} | ||||
| 
 | ||||
| 	switch len(timeRange) { | ||||
| 	case 0: | ||||
| 		rp.End = time.Now().Unix() * int64(time.Microsecond) | ||||
| 	case 1: | ||||
| 		rp.Start = timeRange[0].Unix() * int64(time.Microsecond) | ||||
| 		rp.End = time.Now().Unix() * int64(time.Microsecond) | ||||
| 	case 2: // nolint: gomnd
 | ||||
| 		rp.Start = timeRange[0].Unix() * int64(time.Microsecond) | ||||
| 		rp.End = timeRange[1].Unix() * int64(time.Microsecond) | ||||
| 	default: | ||||
| 		return "", ErrInvalidTimeRange | ||||
| 	} | ||||
| 
 | ||||
| 	params, err := json.Marshal(&rp) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("json marshal: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return string(params), nil | ||||
| } | ||||
| 
 | ||||
| type idsList []*IDS | ||||
| 
 | ||||
| // Len satisfies sort.Interface.
 | ||||
| func (e idsList) Len() int { | ||||
| 	return len(e) | ||||
| } | ||||
| 
 | ||||
| // Swap satisfies sort.Interface.
 | ||||
| func (e idsList) Swap(i, j int) { | ||||
| 	e[i], e[j] = e[j], e[i] | ||||
| } | ||||
| 
 | ||||
| // Less satisfies sort.Interface. Sort our list by Datetime.
 | ||||
| func (e idsList) Less(i, j int) bool { | ||||
| 	return e[i].Datetime.Before(e[j].Datetime) | ||||
| } | ||||
|  | @ -0,0 +1,62 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // GetNetworks returns a response full of network data from the UniFi Controller.
 | ||||
| func (u *Unifi) GetNetworks(sites []*Site) ([]Network, error) { | ||||
| 	networks := make([]Network, 0) | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		var response struct { | ||||
| 			Data []json.RawMessage `json:"data"` | ||||
| 		} | ||||
| 
 | ||||
| 		networkPath := fmt.Sprintf(APINetworkPath, site.Name) | ||||
| 		if err := u.GetData(networkPath, &response); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		for _, data := range response.Data { | ||||
| 			network, err := u.parseNetwork(data, site.SiteName) | ||||
| 			if err != nil { | ||||
| 				return networks, err | ||||
| 			} | ||||
| 
 | ||||
| 			networks = append(networks, *network) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return networks, nil | ||||
| } | ||||
| 
 | ||||
| // parseNetwork parses the raw JSON from the Unifi Controller into network structures.
 | ||||
| func (u *Unifi) parseNetwork(data json.RawMessage, siteName string) (*Network, error) { | ||||
| 	network := new(Network) | ||||
| 	return network, u.unmarshalDevice(siteName, data, network) | ||||
| } | ||||
| 
 | ||||
| // Network is metadata about a network managed by a UniFi controller.
 | ||||
| type Network struct { | ||||
| 	DhcpdDNSEnabled        FlexBool `json:"dhcpd_dns_enabled"` | ||||
| 	DhcpdEnabled           FlexBool `json:"dhcpd_enabled"` | ||||
| 	DhcpdGatewayEnabled    FlexBool `json:"dhcpd_gateway_enabled"` | ||||
| 	DhcpdIP1               string   `json:"dhcpd_ip_1"` | ||||
| 	DhcpdLeasetime         FlexInt  `json:"dhcpd_leasetime"` | ||||
| 	DhcpRelayEnabled       FlexBool `json:"dhcp_relay_enabled"` | ||||
| 	DhcpdTimeOffsetEnabled FlexBool `json:"dhcpd_time_offset_enabled"` | ||||
| 	DhcpGuardEnabled       FlexBool `json:"dhcpguard_enabled"` | ||||
| 	DomainName             string   `json:"domain_name"` | ||||
| 	Enabled                FlexBool `json:"enabled"` | ||||
| 	ID                     string   `json:"_id"` | ||||
| 	IPSubnet               string   `json:"ip_subnet"` | ||||
| 	IsNat                  FlexBool `json:"is_nat"` | ||||
| 	Name                   string   `json:"name"` | ||||
| 	Networkgroup           string   `json:"networkgroup"` | ||||
| 	Purpose                string   `json:"purpose"` | ||||
| 	SiteID                 string   `json:"site_id"` | ||||
| 	Vlan                   FlexInt  `json:"vlan"` | ||||
| 	VlanEnabled            FlexBool `json:"vlan_enabled"` | ||||
| } | ||||
|  | @ -0,0 +1,127 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| var ErrDPIDataBug = fmt.Errorf("dpi data table contains more than 1 item; please open a bug report") | ||||
| 
 | ||||
| // GetSites returns a list of configured sites on the UniFi controller.
 | ||||
| func (u *Unifi) GetSites() ([]*Site, error) { | ||||
| 	var response struct { | ||||
| 		Data []*Site `json:"data"` | ||||
| 	} | ||||
| 
 | ||||
| 	if err := u.GetData(APISiteList, &response); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	sites := []string{} // used for debug log only
 | ||||
| 
 | ||||
| 	for i, d := range response.Data { | ||||
| 		// Add the unifi struct to the site.
 | ||||
| 		response.Data[i].controller = u | ||||
| 		// Add special SourceName value.
 | ||||
| 		response.Data[i].SourceName = u.URL | ||||
| 		// If the human name is missing (description), set it to the cryptic name.
 | ||||
| 		response.Data[i].Desc = strings.TrimSpace(pick(d.Desc, d.Name)) | ||||
| 		// Add the custom site name to each site. used as a Grafana filter somewhere.
 | ||||
| 		response.Data[i].SiteName = d.Desc + " (" + d.Name + ")" | ||||
| 		sites = append(sites, d.Name) // used for debug log only
 | ||||
| 	} | ||||
| 
 | ||||
| 	u.DebugLog("Found %d site(s): %s", len(sites), strings.Join(sites, ",")) | ||||
| 
 | ||||
| 	return response.Data, nil | ||||
| } | ||||
| 
 | ||||
| // GetSiteDPI garners dpi data for sites.
 | ||||
| func (u *Unifi) GetSiteDPI(sites []*Site) ([]*DPITable, error) { | ||||
| 	data := []*DPITable{} | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		u.DebugLog("Polling Controller, retreiving Site DPI data, site %s", site.SiteName) | ||||
| 
 | ||||
| 		var response struct { | ||||
| 			Data []*DPITable `json:"data"` | ||||
| 		} | ||||
| 
 | ||||
| 		siteDPIpath := fmt.Sprintf(APISiteDPI, site.Name) | ||||
| 		if err := u.GetData(siteDPIpath, &response, `{"type":"by_app"}`); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		if l := len(response.Data); l > 1 { | ||||
| 			return nil, ErrDPIDataBug | ||||
| 		} else if l == 0 { | ||||
| 			u.DebugLog("Site DPI data missing! Is DPI enabled in UniFi controller? Site %s", site.SiteName) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		response.Data[0].SourceName = site.SourceName | ||||
| 		response.Data[0].SiteName = site.SiteName | ||||
| 		data = append(data, response.Data[0]) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // Site represents a site's data.
 | ||||
| type Site struct { | ||||
| 	controller   *Unifi | ||||
| 	SourceName   string   `json:"-"` | ||||
| 	ID           string   `json:"_id"` | ||||
| 	Name         string   `json:"name"` | ||||
| 	Desc         string   `json:"desc"` | ||||
| 	SiteName     string   `json:"-"` | ||||
| 	AttrHiddenID string   `json:"attr_hidden_id"` | ||||
| 	AttrNoDelete FlexBool `json:"attr_no_delete"` | ||||
| 	Health       []struct { | ||||
| 		Subsystem       string   `json:"subsystem"` | ||||
| 		NumUser         FlexInt  `json:"num_user,omitempty"` | ||||
| 		NumGuest        FlexInt  `json:"num_guest,omitempty"` | ||||
| 		NumIot          FlexInt  `json:"num_iot,omitempty"` | ||||
| 		TxBytesR        FlexInt  `json:"tx_bytes-r,omitempty"` | ||||
| 		RxBytesR        FlexInt  `json:"rx_bytes-r,omitempty"` | ||||
| 		Status          string   `json:"status"` | ||||
| 		NumAp           FlexInt  `json:"num_ap,omitempty"` | ||||
| 		NumAdopted      FlexInt  `json:"num_adopted,omitempty"` | ||||
| 		NumDisabled     FlexInt  `json:"num_disabled,omitempty"` | ||||
| 		NumDisconnected FlexInt  `json:"num_disconnected,omitempty"` | ||||
| 		NumPending      FlexInt  `json:"num_pending,omitempty"` | ||||
| 		NumGw           FlexInt  `json:"num_gw,omitempty"` | ||||
| 		WanIP           string   `json:"wan_ip,omitempty"` | ||||
| 		Gateways        []string `json:"gateways,omitempty"` | ||||
| 		Netmask         string   `json:"netmask,omitempty"` | ||||
| 		Nameservers     []string `json:"nameservers,omitempty"` | ||||
| 		NumSta          FlexInt  `json:"num_sta,omitempty"` | ||||
| 		GwMac           string   `json:"gw_mac,omitempty"` | ||||
| 		GwName          string   `json:"gw_name,omitempty"` | ||||
| 		GwSystemStats   struct { | ||||
| 			CPU    FlexInt `json:"cpu"` | ||||
| 			Mem    FlexInt `json:"mem"` | ||||
| 			Uptime FlexInt `json:"uptime"` | ||||
| 		} `json:"gw_system-stats,omitempty"` | ||||
| 		GwVersion             string   `json:"gw_version,omitempty"` | ||||
| 		Latency               FlexInt  `json:"latency,omitempty"` | ||||
| 		Uptime                FlexInt  `json:"uptime,omitempty"` | ||||
| 		Drops                 FlexInt  `json:"drops,omitempty"` | ||||
| 		XputUp                FlexInt  `json:"xput_up,omitempty"` | ||||
| 		XputDown              FlexInt  `json:"xput_down,omitempty"` | ||||
| 		SpeedtestStatus       string   `json:"speedtest_status,omitempty"` | ||||
| 		SpeedtestLastrun      FlexInt  `json:"speedtest_lastrun,omitempty"` | ||||
| 		SpeedtestPing         FlexInt  `json:"speedtest_ping,omitempty"` | ||||
| 		LanIP                 string   `json:"lan_ip,omitempty"` | ||||
| 		NumSw                 FlexInt  `json:"num_sw,omitempty"` | ||||
| 		RemoteUserEnabled     FlexBool `json:"remote_user_enabled,omitempty"` | ||||
| 		RemoteUserNumActive   FlexInt  `json:"remote_user_num_active,omitempty"` | ||||
| 		RemoteUserNumInactive FlexInt  `json:"remote_user_num_inactive,omitempty"` | ||||
| 		RemoteUserRxBytes     FlexInt  `json:"remote_user_rx_bytes,omitempty"` | ||||
| 		RemoteUserTxBytes     FlexInt  `json:"remote_user_tx_bytes,omitempty"` | ||||
| 		RemoteUserRxPackets   FlexInt  `json:"remote_user_rx_packets,omitempty"` | ||||
| 		RemoteUserTxPackets   FlexInt  `json:"remote_user_tx_packets,omitempty"` | ||||
| 		SiteToSiteEnabled     FlexBool `json:"site_to_site_enabled,omitempty"` | ||||
| 	} `json:"health"` | ||||
| 	NumNewAlarms FlexInt `json:"num_new_alarms"` | ||||
| } | ||||
|  | @ -0,0 +1,195 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| var ErrCannotUnmarshalFlexInt = fmt.Errorf("cannot unmarshal to FlexInt") | ||||
| 
 | ||||
| // This is a list of unifi API paths.
 | ||||
| // The %s in each string must be replaced with a Site.Name.
 | ||||
| const ( | ||||
| 	// APIRogueAP shows your neighbors' wifis.
 | ||||
| 	APIRogueAP string = "/api/s/%s/stat/rogueap" | ||||
| 	// APIStatusPath shows Controller version.
 | ||||
| 	APIStatusPath string = "/status" | ||||
| 	// APIEventPath contains UniFi Event data.
 | ||||
| 	APIEventPath string = "/api/s/%s/stat/event" | ||||
| 	// APISiteList is the path to the api site list.
 | ||||
| 	APISiteList string = "/api/stat/sites" | ||||
| 	// APISiteDPI is site DPI data.
 | ||||
| 	APISiteDPI string = "/api/s/%s/stat/sitedpi" | ||||
| 	// APISiteDPI is site DPI data.
 | ||||
| 	APIClientDPI string = "/api/s/%s/stat/stadpi" | ||||
| 	// APIClientPath is Unifi Clients API Path.
 | ||||
| 	APIClientPath string = "/api/s/%s/stat/sta" | ||||
| 	// APIAllUserPath is Unifi Insight all previous Clients API Path.
 | ||||
| 	APIAllUserPath string = "/api/s/%s/stat/alluser" | ||||
| 	// APINetworkPath is where we get data about Unifi networks.
 | ||||
| 	APINetworkPath string = "/api/s/%s/rest/networkconf" | ||||
| 	// APIDevicePath is where we get data about Unifi devices.
 | ||||
| 	APIDevicePath string = "/api/s/%s/stat/device" | ||||
| 	// APILoginPath is Unifi Controller Login API Path.
 | ||||
| 	APILoginPath string = "/api/login" | ||||
| 	// APILoginPathNew is how we log into UDM 5.12.55+.
 | ||||
| 	APILoginPathNew string = "/api/auth/login" | ||||
| 	// APILogoutPath is how we logout from UDM.
 | ||||
| 	APILogoutPath string = "/api/logout" | ||||
| 	// APIEventPathIDS returns Intrusion Detection/Prevention Systems Events.
 | ||||
| 	APIEventPathIDS string = "/api/s/%s/stat/ips/event" | ||||
| 	// APIEventPathAlarms contains the site alarms.
 | ||||
| 	APIEventPathAlarms string = "/api/s/%s/list/alarm" | ||||
| 	// APIPrefixNew is the prefix added to the new API paths; except login. duh.
 | ||||
| 	APIPrefixNew string = "/proxy/network" | ||||
| 	// APIAnomaliesPath returns site anomalies.
 | ||||
| 	APIAnomaliesPath string = "/api/s/%s/stat/anomalies" | ||||
| 	APICommandPath   string = "/api/s/%s/cmd" | ||||
| 	APIDevMgrPath    string = APICommandPath + "/devmgr" | ||||
| ) | ||||
| 
 | ||||
| // path returns the correct api path based on the new variable.
 | ||||
| // new is based on the unifi-controller output. is it new or old output?
 | ||||
| func (u *Unifi) path(path string) string { | ||||
| 	if u.new { | ||||
| 		if path == APILoginPath { | ||||
| 			return APILoginPathNew | ||||
| 		} | ||||
| 
 | ||||
| 		if !strings.HasPrefix(path, APIPrefixNew) && path != APILoginPathNew { | ||||
| 			return APIPrefixNew + path | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return path | ||||
| } | ||||
| 
 | ||||
| // Logger is a base type to deal with changing log outputs. Create a logger
 | ||||
| // that matches this interface to capture debug and error logs.
 | ||||
| type Logger func(msg string, fmt ...interface{}) | ||||
| 
 | ||||
| // discardLogs is the default debug logger.
 | ||||
| func discardLogs(msg string, v ...interface{}) { | ||||
| 	// do nothing.
 | ||||
| } | ||||
| 
 | ||||
| // Devices contains a list of all the unifi devices from a controller.
 | ||||
| // Contains Access points, security gateways and switches.
 | ||||
| type Devices struct { | ||||
| 	UAPs []*UAP | ||||
| 	USGs []*USG | ||||
| 	USWs []*USW | ||||
| 	UDMs []*UDM | ||||
| 	UXGs []*UXG | ||||
| } | ||||
| 
 | ||||
| // Config is the data passed into our library. This configures things and allows
 | ||||
| // us to connect to a controller and write log messages. Optional SSLCert is used
 | ||||
| // for ssl cert pinning; provide the content of a PEM to validate the server's cert.
 | ||||
| type Config struct { | ||||
| 	User      string | ||||
| 	Pass      string | ||||
| 	URL       string | ||||
| 	SSLCert   [][]byte | ||||
| 	ErrorLog  Logger | ||||
| 	DebugLog  Logger | ||||
| 	Timeout   time.Duration // how long to wait for replies, default: forever.
 | ||||
| 	VerifySSL bool | ||||
| } | ||||
| 
 | ||||
| // Unifi is what you get in return for providing a password! Unifi represents
 | ||||
| // a controller that you can make authenticated requests to. Use this to make
 | ||||
| // additional requests for devices, clients or other custom data. Do not set
 | ||||
| // the loggers to nil. Set them to DiscardLogs if you want no logs.
 | ||||
| type Unifi struct { | ||||
| 	*http.Client | ||||
| 	*Config | ||||
| 	*server | ||||
| 	csrf         string | ||||
| 	fingerprints fingerprints | ||||
| 	new          bool | ||||
| } | ||||
| 
 | ||||
| type fingerprints []string | ||||
| 
 | ||||
| // Contains returns true if the fingerprint is in the list.
 | ||||
| func (f fingerprints) Contains(s string) bool { | ||||
| 	for i := range f { | ||||
| 		if s == f[i] { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // server is the /status endpoint from the Unifi controller.
 | ||||
| type server struct { | ||||
| 	Up            FlexBool `json:"up"` | ||||
| 	ServerVersion string   `json:"server_version"` | ||||
| 	UUID          string   `json:"uuid"` | ||||
| } | ||||
| 
 | ||||
| // FlexInt provides a container and unmarshalling for fields that may be
 | ||||
| // numbers or strings in the Unifi API.
 | ||||
| type FlexInt struct { | ||||
| 	Val float64 | ||||
| 	Txt string | ||||
| } | ||||
| 
 | ||||
| // UnmarshalJSON converts a string or number to an integer.
 | ||||
| // Generally, do call this directly, it's used in the json interface.
 | ||||
| func (f *FlexInt) UnmarshalJSON(b []byte) error { | ||||
| 	var unk interface{} | ||||
| 
 | ||||
| 	if err := json.Unmarshal(b, &unk); err != nil { | ||||
| 		return fmt.Errorf("json unmarshal: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	switch i := unk.(type) { | ||||
| 	case float64: | ||||
| 		f.Val = i | ||||
| 		f.Txt = strconv.FormatFloat(i, 'f', -1, 64) | ||||
| 	case string: | ||||
| 		f.Txt = i | ||||
| 		f.Val, _ = strconv.ParseFloat(i, 64) | ||||
| 	case nil: | ||||
| 		f.Txt = "0" | ||||
| 		f.Val = 0 | ||||
| 	default: | ||||
| 		return fmt.Errorf("%v: %w", b, ErrCannotUnmarshalFlexInt) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (f *FlexInt) String() string { | ||||
| 	return f.Txt | ||||
| } | ||||
| 
 | ||||
| // FlexBool provides a container and unmarshalling for fields that may be
 | ||||
| // boolean or strings in the Unifi API.
 | ||||
| type FlexBool struct { | ||||
| 	Val bool | ||||
| 	Txt string | ||||
| } | ||||
| 
 | ||||
| // UnmarshalJSON method converts armed/disarmed, yes/no, active/inactive or 0/1 to true/false.
 | ||||
| // Really it converts ready, ok, up, t, armed, yes, active, enabled, 1, true to true. Anything else is false.
 | ||||
| func (f *FlexBool) UnmarshalJSON(b []byte) error { | ||||
| 	f.Txt = strings.Trim(string(b), `"`) | ||||
| 	f.Val = f.Txt == "1" || strings.EqualFold(f.Txt, "true") || strings.EqualFold(f.Txt, "yes") || | ||||
| 		strings.EqualFold(f.Txt, "t") || strings.EqualFold(f.Txt, "armed") || strings.EqualFold(f.Txt, "active") || | ||||
| 		strings.EqualFold(f.Txt, "enabled") || strings.EqualFold(f.Txt, "ready") || strings.EqualFold(f.Txt, "up") || | ||||
| 		strings.EqualFold(f.Txt, "ok") | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (f *FlexBool) String() string { | ||||
| 	return f.Txt | ||||
| } | ||||
|  | @ -0,0 +1,42 @@ | |||
| package unifi_test | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unpoller/unifi" | ||||
| ) | ||||
| 
 | ||||
| func TestFlexInt(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	a := assert.New(t) | ||||
| 	five, seven := 5, 7 | ||||
| 
 | ||||
| 	var r struct { | ||||
| 		Five    unifi.FlexInt `json:"five"` | ||||
| 		Seven   unifi.FlexInt `json:"seven"` | ||||
| 		Auto    unifi.FlexInt `json:"auto"` | ||||
| 		Channel unifi.FlexInt `json:"channel"` | ||||
| 		Nil     unifi.FlexInt `json:"nil"` | ||||
| 	} | ||||
| 
 | ||||
| 	// test unmarshalling the custom type three times with different values.
 | ||||
| 	a.Nil(json.Unmarshal([]byte(`{"five": "5", "seven": 7, "auto": "auto", "nil": null}`), &r)) | ||||
| 	// test number in string.
 | ||||
| 	a.EqualValues(five, r.Five.Val) | ||||
| 	a.EqualValues("5", r.Five.Txt) | ||||
| 	// test number.
 | ||||
| 	a.EqualValues(seven, r.Seven.Val) | ||||
| 	a.EqualValues("7", r.Seven.Txt) | ||||
| 	// test string.
 | ||||
| 	a.EqualValues(0, r.Auto.Val) | ||||
| 	a.EqualValues("auto", r.Auto.Txt) | ||||
| 	// test (error) struct.
 | ||||
| 	a.NotNil(json.Unmarshal([]byte(`{"channel": {}}`), &r), | ||||
| 		"a non-string and non-number must produce an error.") | ||||
| 	a.EqualValues(0, r.Channel.Val) | ||||
| 	// test null.
 | ||||
| 	a.EqualValues(0, r.Nil.Val) | ||||
| 	a.EqualValues("0", r.Nil.Txt) | ||||
| } | ||||
|  | @ -0,0 +1,644 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // UAP represents all the data from the Ubiquiti Controller for a Unifi Access Point.
 | ||||
| // This was auto generated then edited by hand to get all the data types right.
 | ||||
| type UAP struct { | ||||
| 	site         *Site | ||||
| 	SourceName   string   `json:"-"` | ||||
| 	ID           string   `json:"_id"` | ||||
| 	Adopted      FlexBool `json:"adopted"` | ||||
| 	AntennaTable []struct { | ||||
| 		Default   FlexBool `json:"default"` | ||||
| 		ID        FlexInt  `json:"id"` | ||||
| 		Name      string   `json:"name"` | ||||
| 		Wifi0Gain FlexInt  `json:"wifi0_gain"` | ||||
| 		Wifi1Gain FlexInt  `json:"wifi1_gain"` | ||||
| 	} `json:"antenna_table"` | ||||
| 	BandsteeringMode string `json:"bandsteering_mode,omitempty"` | ||||
| 	BoardRev         int    `json:"board_rev"` | ||||
| 	Cfgversion       string `json:"cfgversion"` | ||||
| 	ConfigNetwork    struct { | ||||
| 		Type string `json:"type"` | ||||
| 		IP   string `json:"ip"` | ||||
| 	} `json:"config_network"` | ||||
| 	CountrycodeTable []int `json:"countrycode_table"` | ||||
| 	EthernetTable    []struct { | ||||
| 		Mac     string  `json:"mac"` | ||||
| 		NumPort FlexInt `json:"num_port"` | ||||
| 		Name    string  `json:"name"` | ||||
| 	} `json:"ethernet_table"` | ||||
| 	FwCaps                int             `json:"fw_caps"` | ||||
| 	HasEth1               FlexBool        `json:"has_eth1"` | ||||
| 	HasSpeaker            FlexBool        `json:"has_speaker"` | ||||
| 	InformIP              string          `json:"inform_ip"` | ||||
| 	InformURL             string          `json:"inform_url"` | ||||
| 	IP                    string          `json:"ip"` | ||||
| 	LedOverride           string          `json:"led_override"` | ||||
| 	Mac                   string          `json:"mac"` | ||||
| 	MeshStaVapEnabled     FlexBool        `json:"mesh_sta_vap_enabled"` | ||||
| 	Model                 string          `json:"model"` | ||||
| 	Name                  string          `json:"name"` | ||||
| 	OutdoorModeOverride   string          `json:"outdoor_mode_override"` | ||||
| 	PortTable             []Port          `json:"port_table"` | ||||
| 	RadioTable            RadioTable      `json:"radio_table"` | ||||
| 	ScanRadioTable        []interface{}   `json:"scan_radio_table"` | ||||
| 	Serial                string          `json:"serial"` | ||||
| 	SiteID                string          `json:"site_id"` | ||||
| 	SiteName              string          `json:"-"` | ||||
| 	Type                  string          `json:"type"` | ||||
| 	Version               string          `json:"version"` | ||||
| 	VwireTable            []interface{}   `json:"vwire_table"` | ||||
| 	WifiCaps              int             `json:"wifi_caps"` | ||||
| 	WlangroupIDNa         string          `json:"wlangroup_id_na"` | ||||
| 	WlangroupIDNg         string          `json:"wlangroup_id_ng"` | ||||
| 	RequiredVersion       string          `json:"required_version"` | ||||
| 	HwCaps                int             `json:"hw_caps"` | ||||
| 	Unsupported           FlexBool        `json:"unsupported"` | ||||
| 	UnsupportedReason     FlexInt         `json:"unsupported_reason"` | ||||
| 	SysErrorCaps          int             `json:"sys_error_caps"` | ||||
| 	HasFan                FlexBool        `json:"has_fan"` | ||||
| 	HasTemperature        FlexBool        `json:"has_temperature"` | ||||
| 	DeviceID              string          `json:"device_id"` | ||||
| 	State                 FlexInt         `json:"state"` | ||||
| 	LastSeen              FlexInt         `json:"last_seen"` | ||||
| 	Upgradable            FlexBool        `json:"upgradable"` | ||||
| 	AdoptableWhenUpgraded FlexBool        `json:"adoptable_when_upgraded"` | ||||
| 	Rollupgrade           FlexBool        `json:"rollupgrade"` | ||||
| 	KnownCfgversion       string          `json:"known_cfgversion"` | ||||
| 	Uptime                FlexInt         `json:"uptime"` | ||||
| 	UUptime               FlexInt         `json:"_uptime"` | ||||
| 	Locating              FlexBool        `json:"locating"` | ||||
| 	ConnectRequestIP      string          `json:"connect_request_ip"` | ||||
| 	ConnectRequestPort    string          `json:"connect_request_port"` | ||||
| 	SysStats              SysStats        `json:"sys_stats"` | ||||
| 	SystemStats           SystemStats     `json:"system-stats"` | ||||
| 	SSHSessionTable       []interface{}   `json:"ssh_session_table"` | ||||
| 	Scanning              FlexBool        `json:"scanning"` | ||||
| 	SpectrumScanning      FlexBool        `json:"spectrum_scanning"` | ||||
| 	GuestToken            string          `json:"guest_token"` | ||||
| 	Meshv3PeerMac         string          `json:"meshv3_peer_mac"` | ||||
| 	Satisfaction          FlexInt         `json:"satisfaction"` | ||||
| 	Isolated              FlexBool        `json:"isolated"` | ||||
| 	RadioTableStats       RadioTableStats `json:"radio_table_stats"` | ||||
| 	Uplink                struct { | ||||
| 		FullDuplex       FlexBool `json:"full_duplex"` | ||||
| 		IP               string   `json:"ip"` | ||||
| 		Mac              string   `json:"mac"` | ||||
| 		MaxVlan          int      `json:"max_vlan"` | ||||
| 		Name             string   `json:"name"` | ||||
| 		Netmask          string   `json:"netmask"` | ||||
| 		NumPort          int      `json:"num_port"` | ||||
| 		RxBytes          FlexInt  `json:"rx_bytes"` | ||||
| 		RxDropped        FlexInt  `json:"rx_dropped"` | ||||
| 		RxErrors         FlexInt  `json:"rx_errors"` | ||||
| 		RxMulticast      FlexInt  `json:"rx_multicast"` | ||||
| 		RxPackets        FlexInt  `json:"rx_packets"` | ||||
| 		Speed            FlexInt  `json:"speed"` | ||||
| 		TxBytes          FlexInt  `json:"tx_bytes"` | ||||
| 		TxDropped        FlexInt  `json:"tx_dropped"` | ||||
| 		TxErrors         FlexInt  `json:"tx_errors"` | ||||
| 		TxPackets        FlexInt  `json:"tx_packets"` | ||||
| 		Up               FlexBool `json:"up"` | ||||
| 		MaxSpeed         FlexInt  `json:"max_speed"` | ||||
| 		Type             string   `json:"type"` | ||||
| 		TxBytesR         FlexInt  `json:"tx_bytes-r"` | ||||
| 		RxBytesR         FlexInt  `json:"rx_bytes-r"` | ||||
| 		UplinkMac        string   `json:"uplink_mac"` | ||||
| 		UplinkRemotePort int      `json:"uplink_remote_port"` | ||||
| 	} `json:"uplink"` | ||||
| 	VapTable      VapTable `json:"vap_table"` | ||||
| 	DownlinkTable []struct { | ||||
| 		PortIdx    int    `json:"port_idx"` | ||||
| 		Speed      int    `json:"speed"` | ||||
| 		FullDuplex bool   `json:"full_duplex"` | ||||
| 		Mac        string `json:"mac"` | ||||
| 	} `json:"downlink_table,omitempty"` | ||||
| 	VwireVapTable []interface{} `json:"vwire_vap_table"` | ||||
| 	BytesD        FlexInt       `json:"bytes-d"` | ||||
| 	TxBytesD      FlexInt       `json:"tx_bytes-d"` | ||||
| 	RxBytesD      FlexInt       `json:"rx_bytes-d"` | ||||
| 	BytesR        FlexInt       `json:"bytes-r"` | ||||
| 	LastUplink    struct { | ||||
| 		UplinkMac        string `json:"uplink_mac"` | ||||
| 		UplinkRemotePort int    `json:"uplink_remote_port"` | ||||
| 	} `json:"last_uplink"` | ||||
| 	Stat          UAPStat       `json:"stat"` | ||||
| 	TxBytes       FlexInt       `json:"tx_bytes"` | ||||
| 	RxBytes       FlexInt       `json:"rx_bytes"` | ||||
| 	Bytes         FlexInt       `json:"bytes"` | ||||
| 	VwireEnabled  FlexBool      `json:"vwireEnabled"` | ||||
| 	UplinkTable   []interface{} `json:"uplink_table"` | ||||
| 	NumSta        FlexInt       `json:"num_sta"` | ||||
| 	UserNumSta    FlexInt       `json:"user-num_sta"` | ||||
| 	GuestNumSta   FlexInt       `json:"guest-num_sta"` | ||||
| 	TwoPhaseAdopt FlexBool      `json:"two_phase_adopt,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // UAPStat holds the "stat" data for an access point.
 | ||||
| // This is split out because of a JSON data format change from 5.10 to 5.11.
 | ||||
| type UAPStat struct { | ||||
| 	*Ap | ||||
| } | ||||
| 
 | ||||
| // Ap is a subtype of UAPStat to make unmarshalling of different controller versions possible.
 | ||||
| type Ap struct { | ||||
| 	SiteID                   string    `json:"site_id"` | ||||
| 	O                        string    `json:"o"` | ||||
| 	Oid                      string    `json:"oid"` | ||||
| 	Ap                       string    `json:"ap"` | ||||
| 	Time                     FlexInt   `json:"time"` | ||||
| 	Datetime                 time.Time `json:"datetime"` | ||||
| 	Bytes                    FlexInt   `json:"bytes"` | ||||
| 	Duration                 FlexInt   `json:"duration"` | ||||
| 	WifiTxDropped            FlexInt   `json:"wifi_tx_dropped"` | ||||
| 	RxErrors                 FlexInt   `json:"rx_errors"` | ||||
| 	RxDropped                FlexInt   `json:"rx_dropped"` | ||||
| 	RxFrags                  FlexInt   `json:"rx_frags"` | ||||
| 	RxCrypts                 FlexInt   `json:"rx_crypts"` | ||||
| 	TxPackets                FlexInt   `json:"tx_packets"` | ||||
| 	TxBytes                  FlexInt   `json:"tx_bytes"` | ||||
| 	TxErrors                 FlexInt   `json:"tx_errors"` | ||||
| 	TxDropped                FlexInt   `json:"tx_dropped"` | ||||
| 	TxRetries                FlexInt   `json:"tx_retries"` | ||||
| 	RxPackets                FlexInt   `json:"rx_packets"` | ||||
| 	RxBytes                  FlexInt   `json:"rx_bytes"` | ||||
| 	UserRxDropped            FlexInt   `json:"user-rx_dropped"` | ||||
| 	GuestRxDropped           FlexInt   `json:"guest-rx_dropped"` | ||||
| 	UserRxErrors             FlexInt   `json:"user-rx_errors"` | ||||
| 	GuestRxErrors            FlexInt   `json:"guest-rx_errors"` | ||||
| 	UserRxPackets            FlexInt   `json:"user-rx_packets"` | ||||
| 	GuestRxPackets           FlexInt   `json:"guest-rx_packets"` | ||||
| 	UserRxBytes              FlexInt   `json:"user-rx_bytes"` | ||||
| 	GuestRxBytes             FlexInt   `json:"guest-rx_bytes"` | ||||
| 	UserRxCrypts             FlexInt   `json:"user-rx_crypts"` | ||||
| 	GuestRxCrypts            FlexInt   `json:"guest-rx_crypts"` | ||||
| 	UserRxFrags              FlexInt   `json:"user-rx_frags"` | ||||
| 	GuestRxFrags             FlexInt   `json:"guest-rx_frags"` | ||||
| 	UserTxPackets            FlexInt   `json:"user-tx_packets"` | ||||
| 	GuestTxPackets           FlexInt   `json:"guest-tx_packets"` | ||||
| 	UserTxBytes              FlexInt   `json:"user-tx_bytes"` | ||||
| 	GuestTxBytes             FlexInt   `json:"guest-tx_bytes"` | ||||
| 	UserTxErrors             FlexInt   `json:"user-tx_errors"` | ||||
| 	GuestTxErrors            FlexInt   `json:"guest-tx_errors"` | ||||
| 	UserTxDropped            FlexInt   `json:"user-tx_dropped"` | ||||
| 	GuestTxDropped           FlexInt   `json:"guest-tx_dropped"` | ||||
| 	UserTxRetries            FlexInt   `json:"user-tx_retries"` | ||||
| 	GuestTxRetries           FlexInt   `json:"guest-tx_retries"` | ||||
| 	MacFilterRejections      FlexInt   `json:"mac_filter_rejections"` | ||||
| 	UserMacFilterRejections  FlexInt   `json:"user-mac_filter_rejections"` | ||||
| 	GuestMacFilterRejections FlexInt   `json:"guest-mac_filter_rejections"` | ||||
| 	WifiTxAttempts           FlexInt   `json:"wifi_tx_attempts"` | ||||
| 	UserWifiTxDropped        FlexInt   `json:"user-wifi_tx_dropped"` | ||||
| 	GuestWifiTxDropped       FlexInt   `json:"guest-wifi_tx_dropped"` | ||||
| 	UserWifiTxAttempts       FlexInt   `json:"user-wifi_tx_attempts"` | ||||
| 	GuestWifiTxAttempts      FlexInt   `json:"guest-wifi_tx_attempts"` | ||||
| 
 | ||||
| 	// UAP-AC-PRO names, others may differ.
 | ||||
| 	/* These are all in VAP TABLE */ | ||||
| 	/* | ||||
| 		GuestWifi0RxPackets           FlexInt `json:"guest-wifi0-rx_packets"` | ||||
| 		GuestWifi1RxPackets           FlexInt `json:"guest-wifi1-rx_packets"` | ||||
| 		UserWifi1RxPackets            FlexInt `json:"user-wifi1-rx_packets"` | ||||
| 		UserWifi0RxPackets            FlexInt `json:"user-wifi0-rx_packets"` | ||||
| 		Wifi0RxPackets                FlexInt `json:"wifi0-rx_packets"` | ||||
| 		Wifi1RxPackets                FlexInt `json:"wifi1-rx_packets"` | ||||
| 		GuestWifi0RxBytes             FlexInt `json:"guest-wifi0-rx_bytes"` | ||||
| 		GuestWifi1RxBytes             FlexInt `json:"guest-wifi1-rx_bytes"` | ||||
| 		UserWifi1RxBytes              FlexInt `json:"user-wifi1-rx_bytes"` | ||||
| 		UserWifi0RxBytes              FlexInt `json:"user-wifi0-rx_bytes"` | ||||
| 		Wifi0RxBytes                  FlexInt `json:"wifi0-rx_bytes"` | ||||
| 		Wifi1RxBytes                  FlexInt `json:"wifi1-rx_bytes"` | ||||
| 		GuestWifi0RxErrors            FlexInt `json:"guest-wifi0-rx_errors"` | ||||
| 		GuestWifi1RxErrors            FlexInt `json:"guest-wifi1-rx_errors"` | ||||
| 		UserWifi1RxErrors             FlexInt `json:"user-wifi1-rx_errors"` | ||||
| 		UserWifi0RxErrors             FlexInt `json:"user-wifi0-rx_errors"` | ||||
| 		Wifi0RxErrors                 FlexInt `json:"wifi0-rx_errors"` | ||||
| 		Wifi1RxErrors                 FlexInt `json:"wifi1-rx_errors"` | ||||
| 		GuestWifi0RxDropped           FlexInt `json:"guest-wifi0-rx_dropped"` | ||||
| 		GuestWifi1RxDropped           FlexInt `json:"guest-wifi1-rx_dropped"` | ||||
| 		UserWifi1RxDropped            FlexInt `json:"user-wifi1-rx_dropped"` | ||||
| 		UserWifi0RxDropped            FlexInt `json:"user-wifi0-rx_dropped"` | ||||
| 		Wifi0RxDropped                FlexInt `json:"wifi0-rx_dropped"` | ||||
| 		Wifi1RxDropped                FlexInt `json:"wifi1-rx_dropped"` | ||||
| 		GuestWifi0RxCrypts            FlexInt `json:"guest-wifi0-rx_crypts"` | ||||
| 		GuestWifi1RxCrypts            FlexInt `json:"guest-wifi1-rx_crypts"` | ||||
| 		UserWifi1RxCrypts             FlexInt `json:"user-wifi1-rx_crypts"` | ||||
| 		UserWifi0RxCrypts             FlexInt `json:"user-wifi0-rx_crypts"` | ||||
| 		Wifi0RxCrypts                 FlexInt `json:"wifi0-rx_crypts"` | ||||
| 		Wifi1RxCrypts                 FlexInt `json:"wifi1-rx_crypts"` | ||||
| 		GuestWifi0RxFrags             FlexInt `json:"guest-wifi0-rx_frags"` | ||||
| 		GuestWifi1RxFrags             FlexInt `json:"guest-wifi1-rx_frags"` | ||||
| 		UserWifi1RxFrags              FlexInt `json:"user-wifi1-rx_frags"` | ||||
| 		UserWifi0RxFrags              FlexInt `json:"user-wifi0-rx_frags"` | ||||
| 		Wifi0RxFrags                  FlexInt `json:"wifi0-rx_frags"` | ||||
| 		Wifi1RxFrags                  FlexInt `json:"wifi1-rx_frags"` | ||||
| 		GuestWifi0TxPackets           FlexInt `json:"guest-wifi0-tx_packets"` | ||||
| 		GuestWifi1TxPackets           FlexInt `json:"guest-wifi1-tx_packets"` | ||||
| 		UserWifi1TxPackets            FlexInt `json:"user-wifi1-tx_packets"` | ||||
| 		UserWifi0TxPackets            FlexInt `json:"user-wifi0-tx_packets"` | ||||
| 		Wifi0TxPackets                FlexInt `json:"wifi0-tx_packets"` | ||||
| 		Wifi1TxPackets                FlexInt `json:"wifi1-tx_packets"` | ||||
| 		GuestWifi0TxBytes             FlexInt `json:"guest-wifi0-tx_bytes"` | ||||
| 		GuestWifi1TxBytes             FlexInt `json:"guest-wifi1-tx_bytes"` | ||||
| 		UserWifi1TxBytes              FlexInt `json:"user-wifi1-tx_bytes"` | ||||
| 		UserWifi0TxBytes              FlexInt `json:"user-wifi0-tx_bytes"` | ||||
| 		Wifi0TxBytes                  FlexInt `json:"wifi0-tx_bytes"` | ||||
| 		Wifi1TxBytes                  FlexInt `json:"wifi1-tx_bytes"` | ||||
| 		GuestWifi0TxErrors            FlexInt `json:"guest-wifi0-tx_errors"` | ||||
| 		GuestWifi1TxErrors            FlexInt `json:"guest-wifi1-tx_errors"` | ||||
| 		UserWifi1TxErrors             FlexInt `json:"user-wifi1-tx_errors"` | ||||
| 		UserWifi0TxErrors             FlexInt `json:"user-wifi0-tx_errors"` | ||||
| 		Wifi0TxErrors                 FlexInt `json:"wifi0-tx_errors"` | ||||
| 		Wifi1TxErrors                 FlexInt `json:"wifi1-tx_errors"` | ||||
| 		GuestWifi0TxDropped           FlexInt `json:"guest-wifi0-tx_dropped"` | ||||
| 		GuestWifi1TxDropped           FlexInt `json:"guest-wifi1-tx_dropped"` | ||||
| 		UserWifi1TxDropped            FlexInt `json:"user-wifi1-tx_dropped"` | ||||
| 		UserWifi0TxDropped            FlexInt `json:"user-wifi0-tx_dropped"` | ||||
| 		Wifi0TxDropped                FlexInt `json:"wifi0-tx_dropped"` | ||||
| 		Wifi1TxDropped                FlexInt `json:"wifi1-tx_dropped"` | ||||
| 		GuestWifi0TxRetries           FlexInt `json:"guest-wifi0-tx_retries"` | ||||
| 		GuestWifi1TxRetries           FlexInt `json:"guest-wifi1-tx_retries"` | ||||
| 		UserWifi1TxRetries            FlexInt `json:"user-wifi1-tx_retries"` | ||||
| 		UserWifi0TxRetries            FlexInt `json:"user-wifi0-tx_retries"` | ||||
| 		Wifi0TxRetries                FlexInt `json:"wifi0-tx_retries"` | ||||
| 		Wifi1TxRetries                FlexInt `json:"wifi1-tx_retries"` | ||||
| 		GuestWifi0MacFilterRejections FlexInt `json:"guest-wifi0-mac_filter_rejections"` | ||||
| 		GuestWifi1MacFilterRejections FlexInt `json:"guest-wifi1-mac_filter_rejections"` | ||||
| 		UserWifi1MacFilterRejections  FlexInt `json:"user-wifi1-mac_filter_rejections"` | ||||
| 		UserWifi0MacFilterRejections  FlexInt `json:"user-wifi0-mac_filter_rejections"` | ||||
| 		Wifi0MacFilterRejections      FlexInt `json:"wifi0-mac_filter_rejections"` | ||||
| 		Wifi1MacFilterRejections      FlexInt `json:"wifi1-mac_filter_rejections"` | ||||
| 		GuestWifi0WifiTxAttempts      FlexInt `json:"guest-wifi0-wifi_tx_attempts"` | ||||
| 		GuestWifi1WifiTxAttempts      FlexInt `json:"guest-wifi1-wifi_tx_attempts"` | ||||
| 		UserWifi1WifiTxAttempts       FlexInt `json:"user-wifi1-wifi_tx_attempts"` | ||||
| 		UserWifi0WifiTxAttempts       FlexInt `json:"user-wifi0-wifi_tx_attempts"` | ||||
| 		Wifi0WifiTxAttempts           FlexInt `json:"wifi0-wifi_tx_attempts"` | ||||
| 		Wifi1WifiTxAttempts           FlexInt `json:"wifi1-wifi_tx_attempts"` | ||||
| 		GuestWifi0WifiTxDropped       FlexInt `json:"guest-wifi0-wifi_tx_dropped"` | ||||
| 		GuestWifi1WifiTxDropped       FlexInt `json:"guest-wifi1-wifi_tx_dropped"` | ||||
| 		UserWifi1WifiTxDropped        FlexInt `json:"user-wifi1-wifi_tx_dropped"` | ||||
| 		UserWifi0WifiTxDropped        FlexInt `json:"user-wifi0-wifi_tx_dropped"` | ||||
| 		Wifi0WifiTxDropped            FlexInt `json:"wifi0-wifi_tx_dropped"` | ||||
| 		Wifi1WifiTxDropped            FlexInt `json:"wifi1-wifi_tx_dropped"` | ||||
| 		// UDM Names
 | ||||
| 		GuestRa0RxPackets            FlexInt `json:"guest-ra0-rx_packets"` | ||||
| 		UserRa0RxPackets             FlexInt `json:"user-ra0-rx_packets"` | ||||
| 		Ra0RxPackets                 FlexInt `json:"ra0-rx_packets"` | ||||
| 		GuestRa0RxBytes              FlexInt `json:"guest-ra0-rx_bytes"` | ||||
| 		UserRa0RxBytes               FlexInt `json:"user-ra0-rx_bytes"` | ||||
| 		Ra0RxBytes                   FlexInt `json:"ra0-rx_bytes"` | ||||
| 		GuestRa0RxErrors             FlexInt `json:"guest-ra0-rx_errors"` | ||||
| 		UserRa0RxErrors              FlexInt `json:"user-ra0-rx_errors"` | ||||
| 		Ra0RxErrors                  FlexInt `json:"ra0-rx_errors"` | ||||
| 		GuestRa0RxDropped            FlexInt `json:"guest-ra0-rx_dropped"` | ||||
| 		UserRa0RxDropped             FlexInt `json:"user-ra0-rx_dropped"` | ||||
| 		Ra0RxDropped                 FlexInt `json:"ra0-rx_dropped"` | ||||
| 		GuestRa0RxCrypts             FlexInt `json:"guest-ra0-rx_crypts"` | ||||
| 		UserRa0RxCrypts              FlexInt `json:"user-ra0-rx_crypts"` | ||||
| 		Ra0RxCrypts                  FlexInt `json:"ra0-rx_crypts"` | ||||
| 		GuestRa0RxFrags              FlexInt `json:"guest-ra0-rx_frags"` | ||||
| 		UserRa0RxFrags               FlexInt `json:"user-ra0-rx_frags"` | ||||
| 		Ra0RxFrags                   FlexInt `json:"ra0-rx_frags"` | ||||
| 		GuestRa0TxPackets            FlexInt `json:"guest-ra0-tx_packets"` | ||||
| 		UserRa0TxPackets             FlexInt `json:"user-ra0-tx_packets"` | ||||
| 		Ra0TxPackets                 FlexInt `json:"ra0-tx_packets"` | ||||
| 		GuestRa0TxBytes              FlexInt `json:"guest-ra0-tx_bytes"` | ||||
| 		UserRa0TxBytes               FlexInt `json:"user-ra0-tx_bytes"` | ||||
| 		Ra0TxBytes                   FlexInt `json:"ra0-tx_bytes"` | ||||
| 		GuestRa0TxErrors             FlexInt `json:"guest-ra0-tx_errors"` | ||||
| 		UserRa0TxErrors              FlexInt `json:"user-ra0-tx_errors"` | ||||
| 		Ra0TxErrors                  FlexInt `json:"ra0-tx_errors"` | ||||
| 		GuestRa0TxDropped            FlexInt `json:"guest-ra0-tx_dropped"` | ||||
| 		UserRa0TxDropped             FlexInt `json:"user-ra0-tx_dropped"` | ||||
| 		Ra0TxDropped                 FlexInt `json:"ra0-tx_dropped"` | ||||
| 		GuestRa0TxRetries            FlexInt `json:"guest-ra0-tx_retries"` | ||||
| 		UserRa0TxRetries             FlexInt `json:"user-ra0-tx_retries"` | ||||
| 		Ra0TxRetries                 FlexInt `json:"ra0-tx_retries"` | ||||
| 		GuestRa0MacFilterRejections  FlexInt `json:"guest-ra0-mac_filter_rejections"` | ||||
| 		UserRa0MacFilterRejections   FlexInt `json:"user-ra0-mac_filter_rejections"` | ||||
| 		Ra0MacFilterRejections       FlexInt `json:"ra0-mac_filter_rejections"` | ||||
| 		GuestRa0WifiTxAttempts       FlexInt `json:"guest-ra0-wifi_tx_attempts"` | ||||
| 		UserRa0WifiTxAttempts        FlexInt `json:"user-ra0-wifi_tx_attempts"` | ||||
| 		Ra0WifiTxAttempts            FlexInt `json:"ra0-wifi_tx_attempts"` | ||||
| 		GuestRa0WifiTxDropped        FlexInt `json:"guest-ra0-wifi_tx_dropped"` | ||||
| 		UserRa0WifiTxDropped         FlexInt `json:"user-ra0-wifi_tx_dropped"` | ||||
| 		Ra0WifiTxDropped             FlexInt `json:"ra0-wifi_tx_dropped"` | ||||
| 		GuestRai0RxPackets           FlexInt `json:"guest-rai0-rx_packets"` | ||||
| 		UserRai0RxPackets            FlexInt `json:"user-rai0-rx_packets"` | ||||
| 		Rai0RxPackets                FlexInt `json:"rai0-rx_packets"` | ||||
| 		GuestRai0RxBytes             FlexInt `json:"guest-rai0-rx_bytes"` | ||||
| 		UserRai0RxBytes              FlexInt `json:"user-rai0-rx_bytes"` | ||||
| 		Rai0RxBytes                  FlexInt `json:"rai0-rx_bytes"` | ||||
| 		GuestRai0RxErrors            FlexInt `json:"guest-rai0-rx_errors"` | ||||
| 		UserRai0RxErrors             FlexInt `json:"user-rai0-rx_errors"` | ||||
| 		Rai0RxErrors                 FlexInt `json:"rai0-rx_errors"` | ||||
| 		GuestRai0RxDropped           FlexInt `json:"guest-rai0-rx_dropped"` | ||||
| 		UserRai0RxDropped            FlexInt `json:"user-rai0-rx_dropped"` | ||||
| 		Rai0RxDropped                FlexInt `json:"rai0-rx_dropped"` | ||||
| 		GuestRai0RxCrypts            FlexInt `json:"guest-rai0-rx_crypts"` | ||||
| 		UserRai0RxCrypts             FlexInt `json:"user-rai0-rx_crypts"` | ||||
| 		Rai0RxCrypts                 FlexInt `json:"rai0-rx_crypts"` | ||||
| 		GuestRai0RxFrags             FlexInt `json:"guest-rai0-rx_frags"` | ||||
| 		UserRai0RxFrags              FlexInt `json:"user-rai0-rx_frags"` | ||||
| 		Rai0RxFrags                  FlexInt `json:"rai0-rx_frags"` | ||||
| 		GuestRai0TxPackets           FlexInt `json:"guest-rai0-tx_packets"` | ||||
| 		UserRai0TxPackets            FlexInt `json:"user-rai0-tx_packets"` | ||||
| 		Rai0TxPackets                FlexInt `json:"rai0-tx_packets"` | ||||
| 		GuestRai0TxBytes             FlexInt `json:"guest-rai0-tx_bytes"` | ||||
| 		UserRai0TxBytes              FlexInt `json:"user-rai0-tx_bytes"` | ||||
| 		Rai0TxBytes                  FlexInt `json:"rai0-tx_bytes"` | ||||
| 		GuestRai0TxErrors            FlexInt `json:"guest-rai0-tx_errors"` | ||||
| 		UserRai0TxErrors             FlexInt `json:"user-rai0-tx_errors"` | ||||
| 		Rai0TxErrors                 FlexInt `json:"rai0-tx_errors"` | ||||
| 		GuestRai0TxDropped           FlexInt `json:"guest-rai0-tx_dropped"` | ||||
| 		UserRai0TxDropped            FlexInt `json:"user-rai0-tx_dropped"` | ||||
| 		Rai0TxDropped                FlexInt `json:"rai0-tx_dropped"` | ||||
| 		GuestRai0TxRetries           FlexInt `json:"guest-rai0-tx_retries"` | ||||
| 		UserRai0TxRetries            FlexInt `json:"user-rai0-tx_retries"` | ||||
| 		Rai0TxRetries                FlexInt `json:"rai0-tx_retries"` | ||||
| 		GuestRai0MacFilterRejections FlexInt `json:"guest-rai0-mac_filter_rejections"` | ||||
| 		UserRai0MacFilterRejections  FlexInt `json:"user-rai0-mac_filter_rejections"` | ||||
| 		Rai0MacFilterRejections      FlexInt `json:"rai0-mac_filter_rejections"` | ||||
| 		GuestRai0WifiTxAttempts      FlexInt `json:"guest-rai0-wifi_tx_attempts"` | ||||
| 		UserRai0WifiTxAttempts       FlexInt `json:"user-rai0-wifi_tx_attempts"` | ||||
| 		Rai0WifiTxAttempts           FlexInt `json:"rai0-wifi_tx_attempts"` | ||||
| 		GuestRai0WifiTxDropped       FlexInt `json:"guest-rai0-wifi_tx_dropped"` | ||||
| 		UserRai0WifiTxDropped        FlexInt `json:"user-rai0-wifi_tx_dropped"` | ||||
| 		Rai0WifiTxDropped            FlexInt `json:"rai0-wifi_tx_dropped"` | ||||
| 	*/ | ||||
| } | ||||
| 
 | ||||
| // RadioTable is part of the data for UAPs and UDMs.
 | ||||
| type RadioTable []struct { | ||||
| 	AntennaGain        FlexInt  `json:"antenna_gain"` | ||||
| 	BuiltinAntGain     FlexInt  `json:"builtin_ant_gain"` | ||||
| 	BuiltinAntenna     FlexBool `json:"builtin_antenna"` | ||||
| 	Channel            FlexInt  `json:"channel"` | ||||
| 	CurrentAntennaGain FlexInt  `json:"current_antenna_gain"` | ||||
| 	HasDfs             FlexBool `json:"has_dfs"` | ||||
| 	HasFccdfs          FlexBool `json:"has_fccdfs"` | ||||
| 	HasHt160           FlexBool `json:"has_ht160"` | ||||
| 	Ht                 FlexInt  `json:"ht"` | ||||
| 	Is11Ac             FlexBool `json:"is_11ac"` | ||||
| 	MaxTxpower         FlexInt  `json:"max_txpower"` | ||||
| 	MinRssi            FlexInt  `json:"min_rssi,omitempty"` | ||||
| 	MinRssiEnabled     FlexBool `json:"min_rssi_enabled"` | ||||
| 	MinTxpower         FlexInt  `json:"min_txpower"` | ||||
| 	Name               string   `json:"name"` | ||||
| 	Nss                FlexInt  `json:"nss"` | ||||
| 	Radio              string   `json:"radio"` | ||||
| 	RadioCaps          FlexInt  `json:"radio_caps"` | ||||
| 	SensLevelEnabled   FlexBool `json:"sens_level_enabled"` | ||||
| 	TxPower            FlexInt  `json:"tx_power"` | ||||
| 	TxPowerMode        string   `json:"tx_power_mode"` | ||||
| 	VwireEnabled       FlexBool `json:"vwire_enabled"` | ||||
| 	WlangroupID        string   `json:"wlangroup_id"` | ||||
| } | ||||
| 
 | ||||
| // RadioTableStats is part of the data shared between UAP and UDM.
 | ||||
| type RadioTableStats []struct { | ||||
| 	Name         string      `json:"name"` | ||||
| 	Channel      FlexInt     `json:"channel"` | ||||
| 	Radio        string      `json:"radio"` | ||||
| 	AstTxto      interface{} `json:"ast_txto"` | ||||
| 	AstCst       interface{} `json:"ast_cst"` | ||||
| 	AstBeXmit    FlexInt     `json:"ast_be_xmit"` | ||||
| 	CuTotal      FlexInt     `json:"cu_total"` | ||||
| 	CuSelfRx     FlexInt     `json:"cu_self_rx"` | ||||
| 	CuSelfTx     FlexInt     `json:"cu_self_tx"` | ||||
| 	Gain         FlexInt     `json:"gain"` | ||||
| 	Satisfaction FlexInt     `json:"satisfaction"` | ||||
| 	State        string      `json:"state"` | ||||
| 	Extchannel   FlexInt     `json:"extchannel"` | ||||
| 	TxPower      FlexInt     `json:"tx_power"` | ||||
| 	TxPackets    FlexInt     `json:"tx_packets"` | ||||
| 	TxRetries    FlexInt     `json:"tx_retries"` | ||||
| 	NumSta       FlexInt     `json:"num_sta"` | ||||
| 	GuestNumSta  FlexInt     `json:"guest-num_sta"` | ||||
| 	UserNumSta   FlexInt     `json:"user-num_sta"` | ||||
| } | ||||
| 
 | ||||
| // VapTable holds much of the UAP wireless data. Shared by UDM.
 | ||||
| type VapTable []struct { | ||||
| 	AnomaliesBarChart struct { | ||||
| 		HighDNSLatency    FlexInt `json:"high_dns_latency"` | ||||
| 		HighTCPLatency    FlexInt `json:"high_tcp_latency"` | ||||
| 		HighTCPPacketLoss FlexInt `json:"high_tcp_packet_loss"` | ||||
| 		HighWifiLatency   FlexInt `json:"high_wifi_latency"` | ||||
| 		HighWifiRetries   FlexInt `json:"high_wifi_retries"` | ||||
| 		LowPhyRate        FlexInt `json:"low_phy_rate"` | ||||
| 		PoorStreamEff     FlexInt `json:"poor_stream_eff"` | ||||
| 		SleepyClient      FlexInt `json:"sleepy_client"` | ||||
| 		StaArpTimeout     FlexInt `json:"sta_arp_timeout"` | ||||
| 		StaDNSTimeout     FlexInt `json:"sta_dns_timeout"` | ||||
| 		StaIPTimeout      FlexInt `json:"sta_ip_timeout"` | ||||
| 		WeakSignal        FlexInt `json:"weak_signal"` | ||||
| 	} `json:"anomalies_bar_chart"` | ||||
| 	AnomaliesBarChartNow struct { | ||||
| 		HighDNSLatency    FlexInt `json:"high_dns_latency"` | ||||
| 		HighTCPLatency    FlexInt `json:"high_tcp_latency"` | ||||
| 		HighTCPPacketLoss FlexInt `json:"high_tcp_packet_loss"` | ||||
| 		HighWifiLatency   FlexInt `json:"high_wifi_latency"` | ||||
| 		HighWifiRetries   FlexInt `json:"high_wifi_retries"` | ||||
| 		LowPhyRate        FlexInt `json:"low_phy_rate"` | ||||
| 		PoorStreamEff     FlexInt `json:"poor_stream_eff"` | ||||
| 		SleepyClient      FlexInt `json:"sleepy_client"` | ||||
| 		StaArpTimeout     FlexInt `json:"sta_arp_timeout"` | ||||
| 		StaDNSTimeout     FlexInt `json:"sta_dns_timeout"` | ||||
| 		StaIPTimeout      FlexInt `json:"sta_ip_timeout"` | ||||
| 		WeakSignal        FlexInt `json:"weak_signal"` | ||||
| 	} `json:"anomalies_bar_chart_now"` | ||||
| 	ReasonsBarChart struct { | ||||
| 		PhyRate       FlexInt `json:"phy_rate"` | ||||
| 		Signal        FlexInt `json:"signal"` | ||||
| 		SleepyClient  FlexInt `json:"sleepy_client"` | ||||
| 		StaArpTimeout FlexInt `json:"sta_arp_timeout"` | ||||
| 		StaDNSLatency FlexInt `json:"sta_dns_latency"` | ||||
| 		StaDNSTimeout FlexInt `json:"sta_dns_timeout"` | ||||
| 		StaIPTimeout  FlexInt `json:"sta_ip_timeout"` | ||||
| 		StreamEff     FlexInt `json:"stream_eff"` | ||||
| 		TCPLatency    FlexInt `json:"tcp_latency"` | ||||
| 		TCPPacketLoss FlexInt `json:"tcp_packet_loss"` | ||||
| 		WifiLatency   FlexInt `json:"wifi_latency"` | ||||
| 		WifiRetries   FlexInt `json:"wifi_retries"` | ||||
| 	} `json:"reasons_bar_chart"` | ||||
| 	ReasonsBarChartNow struct { | ||||
| 		PhyRate       FlexInt `json:"phy_rate"` | ||||
| 		Signal        FlexInt `json:"signal"` | ||||
| 		SleepyClient  FlexInt `json:"sleepy_client"` | ||||
| 		StaArpTimeout FlexInt `json:"sta_arp_timeout"` | ||||
| 		StaDNSLatency FlexInt `json:"sta_dns_latency"` | ||||
| 		StaDNSTimeout FlexInt `json:"sta_dns_timeout"` | ||||
| 		StaIPTimeout  FlexInt `json:"sta_ip_timeout"` | ||||
| 		StreamEff     FlexInt `json:"stream_eff"` | ||||
| 		TCPLatency    FlexInt `json:"tcp_latency"` | ||||
| 		TCPPacketLoss FlexInt `json:"tcp_packet_loss"` | ||||
| 		WifiLatency   FlexInt `json:"wifi_latency"` | ||||
| 		WifiRetries   FlexInt `json:"wifi_retries"` | ||||
| 	} `json:"reasons_bar_chart_now"` | ||||
| 	RxTCPStats struct { | ||||
| 		Goodbytes FlexInt `json:"goodbytes"` | ||||
| 		LatAvg    FlexInt `json:"lat_avg"` | ||||
| 		LatMax    FlexInt `json:"lat_max"` | ||||
| 		LatMin    FlexInt `json:"lat_min"` | ||||
| 		Stalls    FlexInt `json:"stalls"` | ||||
| 	} `json:"rx_tcp_stats"` | ||||
| 	TxTCPStats struct { | ||||
| 		Goodbytes FlexInt `json:"goodbytes"` | ||||
| 		LatAvg    FlexInt `json:"lat_avg"` | ||||
| 		LatMax    FlexInt `json:"lat_max"` | ||||
| 		LatMin    FlexInt `json:"lat_min"` | ||||
| 		Stalls    FlexInt `json:"stalls"` | ||||
| 	} `json:"tx_tcp_stats"` | ||||
| 	WifiTxLatencyMov struct { | ||||
| 		Avg        FlexInt `json:"avg"` | ||||
| 		Max        FlexInt `json:"max"` | ||||
| 		Min        FlexInt `json:"min"` | ||||
| 		Total      FlexInt `json:"total"` | ||||
| 		TotalCount FlexInt `json:"total_count"` | ||||
| 	} `json:"wifi_tx_latency_mov"` | ||||
| 	ApMac               string      `json:"ap_mac"` | ||||
| 	AvgClientSignal     FlexInt     `json:"avg_client_signal"` | ||||
| 	Bssid               string      `json:"bssid"` | ||||
| 	Ccq                 int         `json:"ccq"` | ||||
| 	Channel             FlexInt     `json:"channel"` | ||||
| 	DNSAvgLatency       FlexInt     `json:"dns_avg_latency"` | ||||
| 	Essid               string      `json:"essid"` | ||||
| 	Extchannel          int         `json:"extchannel"` | ||||
| 	ID                  string      `json:"id"` | ||||
| 	IsGuest             FlexBool    `json:"is_guest"` | ||||
| 	IsWep               FlexBool    `json:"is_wep"` | ||||
| 	MacFilterRejections int         `json:"mac_filter_rejections"` | ||||
| 	MapID               interface{} `json:"map_id"` | ||||
| 	Name                string      `json:"name"` | ||||
| 	NumSatisfactionSta  FlexInt     `json:"num_satisfaction_sta"` | ||||
| 	NumSta              int         `json:"num_sta"` | ||||
| 	Radio               string      `json:"radio"` | ||||
| 	RadioName           string      `json:"radio_name"` | ||||
| 	RxBytes             FlexInt     `json:"rx_bytes"` | ||||
| 	RxCrypts            FlexInt     `json:"rx_crypts"` | ||||
| 	RxDropped           FlexInt     `json:"rx_dropped"` | ||||
| 	RxErrors            FlexInt     `json:"rx_errors"` | ||||
| 	RxFrags             FlexInt     `json:"rx_frags"` | ||||
| 	RxNwids             FlexInt     `json:"rx_nwids"` | ||||
| 	RxPackets           FlexInt     `json:"rx_packets"` | ||||
| 	Satisfaction        FlexInt     `json:"satisfaction"` | ||||
| 	SatisfactionNow     FlexInt     `json:"satisfaction_now"` | ||||
| 	SiteID              string      `json:"site_id"` | ||||
| 	State               string      `json:"state"` | ||||
| 	T                   string      `json:"t"` | ||||
| 	TxBytes             FlexInt     `json:"tx_bytes"` | ||||
| 	TxCombinedRetries   FlexInt     `json:"tx_combined_retries"` | ||||
| 	TxDataMpduBytes     FlexInt     `json:"tx_data_mpdu_bytes"` | ||||
| 	TxDropped           FlexInt     `json:"tx_dropped"` | ||||
| 	TxErrors            FlexInt     `json:"tx_errors"` | ||||
| 	TxPackets           FlexInt     `json:"tx_packets"` | ||||
| 	TxPower             FlexInt     `json:"tx_power"` | ||||
| 	TxRetries           FlexInt     `json:"tx_retries"` | ||||
| 	TxRtsRetries        FlexInt     `json:"tx_rts_retries"` | ||||
| 	TxSuccess           FlexInt     `json:"tx_success"` | ||||
| 	TxTotal             FlexInt     `json:"tx_total"` | ||||
| 	Up                  FlexBool    `json:"up"` | ||||
| 	Usage               string      `json:"usage"` | ||||
| 	WifiTxAttempts      FlexInt     `json:"wifi_tx_attempts"` | ||||
| 	WifiTxDropped       FlexInt     `json:"wifi_tx_dropped"` | ||||
| 	WlanconfID          string      `json:"wlanconf_id"` | ||||
| } | ||||
| 
 | ||||
| // RogueAP are your neighbors access points.
 | ||||
| type RogueAP struct { | ||||
| 	SourceName string   `json:"-"` | ||||
| 	SiteName   string   `json:"-"` | ||||
| 	ID         string   `json:"_id"` | ||||
| 	ApMac      string   `json:"ap_mac"` | ||||
| 	Bssid      string   `json:"bssid"` | ||||
| 	SiteID     string   `json:"site_id"` | ||||
| 	Age        FlexInt  `json:"age"` | ||||
| 	Band       string   `json:"band"` | ||||
| 	Bw         FlexInt  `json:"bw"` | ||||
| 	CenterFreq FlexInt  `json:"center_freq"` | ||||
| 	Channel    int      `json:"channel"` | ||||
| 	Essid      string   `json:"essid"` | ||||
| 	Freq       FlexInt  `json:"freq"` | ||||
| 	IsAdhoc    FlexBool `json:"is_adhoc"` | ||||
| 	IsRogue    FlexBool `json:"is_rogue"` | ||||
| 	IsUbnt     FlexBool `json:"is_ubnt"` | ||||
| 	LastSeen   FlexInt  `json:"last_seen"` | ||||
| 	Noise      FlexInt  `json:"noise"` | ||||
| 	Radio      string   `json:"radio"` | ||||
| 	RadioName  string   `json:"radio_name"` | ||||
| 	ReportTime FlexInt  `json:"report_time"` | ||||
| 	Rssi       FlexInt  `json:"rssi"` | ||||
| 	RssiAge    FlexInt  `json:"rssi_age"` | ||||
| 	Security   string   `json:"security"` | ||||
| 	Signal     FlexInt  `json:"signal"` | ||||
| 	Oui        string   `json:"oui"` | ||||
| } | ||||
| 
 | ||||
| // GetRogueAPs returns RogueAPs for a list of Sites.
 | ||||
| // Use GetRogueAPsSite if you want more control.
 | ||||
| func (u *Unifi) GetRogueAPs(sites []*Site) ([]*RogueAP, error) { | ||||
| 	data := []*RogueAP{} | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		response, err := u.GetRogueAPsSite(site) | ||||
| 		if err != nil { | ||||
| 			return data, err | ||||
| 		} | ||||
| 
 | ||||
| 		data = append(data, response...) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // GetRogueAPsSite returns RogueAPs for a single Site.
 | ||||
| func (u *Unifi) GetRogueAPsSite(site *Site) ([]*RogueAP, error) { | ||||
| 	if site == nil || site.Name == "" { | ||||
| 		return nil, ErrNoSiteProvided | ||||
| 	} | ||||
| 
 | ||||
| 	u.DebugLog("Polling Controller for RogueAPs, site %s (%s)", site.SiteName, site.Desc) | ||||
| 
 | ||||
| 	var ( | ||||
| 		path     = fmt.Sprintf(APIRogueAP, site.Name) | ||||
| 		rogueaps struct { | ||||
| 			Data []*RogueAP `json:"data"` | ||||
| 		} | ||||
| 	) | ||||
| 
 | ||||
| 	if err := u.GetData(path, &rogueaps, ""); err != nil { | ||||
| 		return rogueaps.Data, err | ||||
| 	} | ||||
| 
 | ||||
| 	for i := range rogueaps.Data { | ||||
| 		// Add special SourceName value.
 | ||||
| 		rogueaps.Data[i].SourceName = u.URL | ||||
| 		// Add the special "Site Name" to each event. This becomes a Grafana filter somewhere.
 | ||||
| 		rogueaps.Data[i].SiteName = site.SiteName | ||||
| 	} | ||||
| 
 | ||||
| 	return rogueaps.Data, nil | ||||
| } | ||||
| 
 | ||||
| // UnmarshalJSON unmarshalls 5.10 or 5.11 formatted Access Point Stat data.
 | ||||
| func (v *UAPStat) UnmarshalJSON(data []byte) error { | ||||
| 	var n struct { | ||||
| 		Ap `json:"ap"` | ||||
| 	} | ||||
| 
 | ||||
| 	v.Ap = &n.Ap | ||||
| 
 | ||||
| 	err := json.Unmarshal(data, v.Ap) // controller version 5.10.
 | ||||
| 	if err != nil { | ||||
| 		return json.Unmarshal(data, &n) // controller version 5.11.
 | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | @ -0,0 +1,53 @@ | |||
| package unifi // nolint: testpackage
 | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
| 
 | ||||
| func TestUAPUnmarshalJSON(t *testing.T) { | ||||
| 	testcontroller511 := `{ | ||||
| 	  "ap": { | ||||
| 	    "site_id": "mySite", | ||||
| 	    "o": "ap", | ||||
| 	    "oid": "00:00:00:00:00:00", | ||||
| 	    "ap": "00:00:00:00:00:00", | ||||
| 			"time": 1577742600000, | ||||
|       "datetime": "2019-12-30T09:50:00Z", | ||||
| 	    "user-wifi1-rx_packets": 6596670, | ||||
| 	    "user-wifi0-rx_packets": 42659527, | ||||
| 	    "user-rx_packets": 49294197, | ||||
| 	    "guest-rx_packets": 0, | ||||
| 	    "wifi0-rx_packets": 42639527, | ||||
| 	    "wifi1-rx_packets": 6591670, | ||||
| 	    "rx_packets": 49299197}}` | ||||
| 
 | ||||
| 	testcontroller510 := `{ | ||||
| 		"site_id": "mySite", | ||||
| 		"o": "ap", | ||||
| 		"oid": "00:00:00:00:00:00", | ||||
| 		"ap": "00:00:00:00:00:00", | ||||
| 		"time": 1577742600000, | ||||
| 		"datetime": "2019-12-30T09:50:00Z", | ||||
| 		"user-wifi1-rx_packets": 6596670, | ||||
| 		"user-wifi0-rx_packets": 42659527, | ||||
| 		"user-rx_packets": 49294197, | ||||
| 		"guest-rx_packets": 0, | ||||
| 		"wifi0-rx_packets": 42639527, | ||||
| 		"wifi1-rx_packets": 6591670, | ||||
| 		"rx_packets": 49299197}` | ||||
| 
 | ||||
| 	t.Parallel() | ||||
| 	a := assert.New(t) | ||||
| 	rxPakcets := 49299197 | ||||
| 	u := &UAPStat{} | ||||
| 	err := u.UnmarshalJSON([]byte(testcontroller510)) | ||||
| 	a.Nil(err, "must be no error unmarshaling test strings") | ||||
| 	a.Equal(float64(rxPakcets), u.RxPackets.Val, "data was not properly unmarshaled") | ||||
| 
 | ||||
| 	u = &UAPStat{} // reset
 | ||||
| 	err = u.UnmarshalJSON([]byte(testcontroller511)) | ||||
| 	a.Nil(err, "must be no error unmarshaling test strings") | ||||
| 	a.Equal(float64(rxPakcets), u.RxPackets.Val, "data was not properly unmarshaled") | ||||
| } | ||||
|  | @ -0,0 +1,206 @@ | |||
| package unifi | ||||
| 
 | ||||
| // UDM represents all the data from the Ubiquiti Controller for a Unifi Dream Machine.
 | ||||
| // The UDM shares several structs/type-data with USW and USG.
 | ||||
| type UDM struct { | ||||
| 	site                   *Site | ||||
| 	SourceName             string               `json:"-"` | ||||
| 	SiteID                 string               `json:"site_id"` | ||||
| 	SiteName               string               `json:"-"` | ||||
| 	Mac                    string               `json:"mac"` | ||||
| 	Adopted                FlexBool             `json:"adopted"` | ||||
| 	Serial                 string               `json:"serial"` | ||||
| 	IP                     string               `json:"ip"` | ||||
| 	Uptime                 FlexInt              `json:"uptime"` | ||||
| 	Model                  string               `json:"model"` | ||||
| 	Version                string               `json:"version"` | ||||
| 	Name                   string               `json:"name"` | ||||
| 	Default                FlexBool             `json:"default"` | ||||
| 	Locating               FlexBool             `json:"locating"` | ||||
| 	Type                   string               `json:"type"` | ||||
| 	Unsupported            FlexBool             `json:"unsupported"` | ||||
| 	UnsupportedReason      FlexInt              `json:"unsupported_reason"` | ||||
| 	DiscoveredVia          string               `json:"discovered_via"` | ||||
| 	AdoptIP                string               `json:"adopt_ip"` | ||||
| 	AdoptURL               string               `json:"adopt_url"` | ||||
| 	State                  FlexInt              `json:"state"` | ||||
| 	AdoptStatus            FlexInt              `json:"adopt_status"` | ||||
| 	UpgradeState           FlexInt              `json:"upgrade_state"` | ||||
| 	LastSeen               FlexInt              `json:"last_seen"` | ||||
| 	AdoptableWhenUpgraded  FlexBool             `json:"adoptable_when_upgraded"` | ||||
| 	Cfgversion             string               `json:"cfgversion"` | ||||
| 	ConfigNetwork          *ConfigNetwork       `json:"config_network"` | ||||
| 	VwireTable             []interface{}        `json:"vwire_table"` | ||||
| 	Dot1XPortctrlEnabled   FlexBool             `json:"dot1x_portctrl_enabled"` | ||||
| 	JumboframeEnabled      FlexBool             `json:"jumboframe_enabled"` | ||||
| 	FlowctrlEnabled        FlexBool             `json:"flowctrl_enabled"` | ||||
| 	StpVersion             string               `json:"stp_version"` | ||||
| 	StpPriority            FlexInt              `json:"stp_priority"` | ||||
| 	PowerSourceCtrlEnabled FlexBool             `json:"power_source_ctrl_enabled"` | ||||
| 	LicenseState           string               `json:"license_state"` | ||||
| 	ID                     string               `json:"_id"` | ||||
| 	DeviceID               string               `json:"device_id"` | ||||
| 	AdoptState             FlexInt              `json:"adopt_state"` | ||||
| 	AdoptTries             FlexInt              `json:"adopt_tries"` | ||||
| 	AdoptManual            FlexBool             `json:"adopt_manual"` | ||||
| 	InformURL              string               `json:"inform_url"` | ||||
| 	InformIP               string               `json:"inform_ip"` | ||||
| 	RequiredVersion        string               `json:"required_version"` | ||||
| 	BoardRev               FlexInt              `json:"board_rev"` | ||||
| 	EthernetTable          []*EthernetTable     `json:"ethernet_table"` | ||||
| 	PortTable              []Port               `json:"port_table"` | ||||
| 	EthernetOverrides      []*EthernetOverrides `json:"ethernet_overrides"` | ||||
| 	UsgCaps                FlexInt              `json:"usg_caps"` | ||||
| 	HasSpeaker             FlexBool             `json:"has_speaker"` | ||||
| 	HasEth1                FlexBool             `json:"has_eth1"` | ||||
| 	FwCaps                 FlexInt              `json:"fw_caps"` | ||||
| 	HwCaps                 FlexInt              `json:"hw_caps"` | ||||
| 	WifiCaps               FlexInt              `json:"wifi_caps"` | ||||
| 	SwitchCaps             struct { | ||||
| 		MaxMirrorSessions    FlexInt `json:"max_mirror_sessions"` | ||||
| 		MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"` | ||||
| 	} `json:"switch_caps"` | ||||
| 	HasFan            FlexBool      `json:"has_fan"` | ||||
| 	Temperatures      []Temperature `json:"temperatures,omitempty"` | ||||
| 	RulesetInterfaces interface{}   `json:"ruleset_interfaces"` | ||||
| 	/* struct { | ||||
| 		Br0  string `json:"br0"` | ||||
| 		Eth0 string `json:"eth0"` | ||||
| 		Eth1 string `json:"eth1"` | ||||
| 		Eth2 string `json:"eth2"` | ||||
| 		Eth3 string `json:"eth3"` | ||||
| 		Eth4 string `json:"eth4"` | ||||
| 		Eth5 string `json:"eth5"` | ||||
| 		Eth6 string `json:"eth6"` | ||||
| 		Eth7 string `json:"eth7"` | ||||
| 		Eth8 string `json:"eth8"` | ||||
| 	} */ | ||||
| 	KnownCfgversion      string           `json:"known_cfgversion"` | ||||
| 	SysStats             SysStats         `json:"sys_stats"` | ||||
| 	SystemStats          SystemStats      `json:"system-stats"` | ||||
| 	GuestToken           string           `json:"guest_token"` | ||||
| 	Overheating          FlexBool         `json:"overheating"` | ||||
| 	SpeedtestStatus      SpeedtestStatus  `json:"speedtest-status"` | ||||
| 	SpeedtestStatusSaved FlexBool         `json:"speedtest-status-saved"` | ||||
| 	Wan1                 Wan              `json:"wan1"` | ||||
| 	Wan2                 Wan              `json:"wan2"` | ||||
| 	Uplink               Uplink           `json:"uplink"` | ||||
| 	ConnectRequestIP     string           `json:"connect_request_ip"` | ||||
| 	ConnectRequestPort   string           `json:"connect_request_port"` | ||||
| 	DownlinkTable        []*DownlinkTable `json:"downlink_table"` | ||||
| 	WlangroupIDNa        string           `json:"wlangroup_id_na"` | ||||
| 	WlangroupIDNg        string           `json:"wlangroup_id_ng"` | ||||
| 	BandsteeringMode     string           `json:"bandsteering_mode"` | ||||
| 	RadioTable           *RadioTable      `json:"radio_table,omitempty"` | ||||
| 	RadioTableStats      *RadioTableStats `json:"radio_table_stats,omitempty"` | ||||
| 	VapTable             *VapTable        `json:"vap_table"` | ||||
| 	XInformAuthkey       string           `json:"x_inform_authkey"` | ||||
| 	NetworkTable         NetworkTable     `json:"network_table"` | ||||
| 	PortOverrides        []struct { | ||||
| 		PortIdx    FlexInt `json:"port_idx"` | ||||
| 		PortconfID string  `json:"portconf_id"` | ||||
| 	} `json:"port_overrides"` | ||||
| 	Stat            UDMStat    `json:"stat"` | ||||
| 	Storage         []*Storage `json:"storage"` | ||||
| 	TxBytes         FlexInt    `json:"tx_bytes"` | ||||
| 	RxBytes         FlexInt    `json:"rx_bytes"` | ||||
| 	Bytes           FlexInt    `json:"bytes"` | ||||
| 	BytesD          FlexInt    `json:"bytes-d"` | ||||
| 	TxBytesD        FlexInt    `json:"tx_bytes-d"` | ||||
| 	RxBytesD        FlexInt    `json:"rx_bytes-d"` | ||||
| 	BytesR          FlexInt    `json:"bytes-r"` | ||||
| 	NumSta          FlexInt    `json:"num_sta"`            // USG
 | ||||
| 	WlanNumSta      FlexInt    `json:"wlan-num_sta"`       // UAP
 | ||||
| 	LanNumSta       FlexInt    `json:"lan-num_sta"`        // USW
 | ||||
| 	UserWlanNumSta  FlexInt    `json:"user-wlan-num_sta"`  // UAP
 | ||||
| 	UserLanNumSta   FlexInt    `json:"user-lan-num_sta"`   // USW
 | ||||
| 	UserNumSta      FlexInt    `json:"user-num_sta"`       // USG
 | ||||
| 	GuestWlanNumSta FlexInt    `json:"guest-wlan-num_sta"` // UAP
 | ||||
| 	GuestLanNumSta  FlexInt    `json:"guest-lan-num_sta"`  // USW
 | ||||
| 	GuestNumSta     FlexInt    `json:"guest-num_sta"`      // USG
 | ||||
| 	NumDesktop      FlexInt    `json:"num_desktop"`        // USG
 | ||||
| 	NumMobile       FlexInt    `json:"num_mobile"`         // USG
 | ||||
| 	NumHandheld     FlexInt    `json:"num_handheld"`       // USG
 | ||||
| } | ||||
| 
 | ||||
| type EthernetOverrides struct { | ||||
| 	Ifname       string `json:"ifname"` | ||||
| 	Networkgroup string `json:"networkgroup"` | ||||
| } | ||||
| 
 | ||||
| type EthernetTable struct { | ||||
| 	Mac     string  `json:"mac"` | ||||
| 	NumPort FlexInt `json:"num_port"` | ||||
| 	Name    string  `json:"name"` | ||||
| } | ||||
| 
 | ||||
| // NetworkTable is the list of networks on a gateway.
 | ||||
| // Not all gateways have all features.
 | ||||
| type NetworkTable []struct { | ||||
| 	ID                     string    `json:"_id"` | ||||
| 	AttrNoDelete           FlexBool  `json:"attr_no_delete"` | ||||
| 	AttrHiddenID           string    `json:"attr_hidden_id"` | ||||
| 	Name                   string    `json:"name"` | ||||
| 	SiteID                 string    `json:"site_id"` | ||||
| 	VlanEnabled            FlexBool  `json:"vlan_enabled"` | ||||
| 	Purpose                string    `json:"purpose"` | ||||
| 	IPSubnet               string    `json:"ip_subnet"` | ||||
| 	Ipv6InterfaceType      string    `json:"ipv6_interface_type"` | ||||
| 	DomainName             string    `json:"domain_name"` | ||||
| 	IsNat                  FlexBool  `json:"is_nat"` | ||||
| 	DhcpdEnabled           FlexBool  `json:"dhcpd_enabled"` | ||||
| 	DhcpdStart             string    `json:"dhcpd_start"` | ||||
| 	DhcpdStop              string    `json:"dhcpd_stop"` | ||||
| 	Dhcpdv6Enabled         FlexBool  `json:"dhcpdv6_enabled"` | ||||
| 	Ipv6RaEnabled          FlexBool  `json:"ipv6_ra_enabled"` | ||||
| 	LteLanEnabled          FlexBool  `json:"lte_lan_enabled"` | ||||
| 	AutoScaleEnabled       FlexBool  `json:"auto_scale_enabled"` | ||||
| 	Networkgroup           string    `json:"networkgroup"` | ||||
| 	DhcpdLeasetime         FlexInt   `json:"dhcpd_leasetime"` | ||||
| 	DhcpdDNSEnabled        FlexBool  `json:"dhcpd_dns_enabled"` | ||||
| 	DhcpdGatewayEnabled    FlexBool  `json:"dhcpd_gateway_enabled"` | ||||
| 	DhcpdTimeOffsetEnabled FlexBool  `json:"dhcpd_time_offset_enabled"` | ||||
| 	Ipv6PdStart            string    `json:"ipv6_pd_start"` | ||||
| 	Ipv6PdStop             string    `json:"ipv6_pd_stop"` | ||||
| 	DhcpdDNS1              string    `json:"dhcpd_dns_1"` | ||||
| 	DhcpdDNS2              string    `json:"dhcpd_dns_2"` | ||||
| 	DhcpdDNS3              string    `json:"dhcpd_dns_3"` | ||||
| 	DhcpdDNS4              string    `json:"dhcpd_dns_4"` | ||||
| 	Enabled                FlexBool  `json:"enabled"` | ||||
| 	DhcpRelayEnabled       FlexBool  `json:"dhcp_relay_enabled"` | ||||
| 	Mac                    string    `json:"mac"` | ||||
| 	IsGuest                FlexBool  `json:"is_guest"` | ||||
| 	IP                     string    `json:"ip"` | ||||
| 	Up                     FlexBool  `json:"up"` | ||||
| 	ActiveDhcpLeaseCount   int       `json:"active_dhcp_lease_count"` | ||||
| 	GatewayInterfaceName   string    `json:"gateway_interface_name"` | ||||
| 	DPIStatsTable          *DPITable `json:"dpistats_table"` | ||||
| 	NumSta                 FlexInt   `json:"num_sta"` | ||||
| 	RxBytes                FlexInt   `json:"rx_bytes"` | ||||
| 	RxPackets              FlexInt   `json:"rx_packets"` | ||||
| 	TxBytes                FlexInt   `json:"tx_bytes"` | ||||
| 	TxPackets              FlexInt   `json:"tx_packets"` | ||||
| } | ||||
| 
 | ||||
| // Storage is hard drive into for a device with storage.
 | ||||
| type Storage struct { | ||||
| 	MountPoint string  `json:"mount_point"` | ||||
| 	Name       string  `json:"name"` | ||||
| 	Size       FlexInt `json:"size"` | ||||
| 	Type       string  `json:"type"` | ||||
| 	Used       FlexInt `json:"used"` | ||||
| } | ||||
| 
 | ||||
| type Temperature struct { | ||||
| 	Name  string  `json:"name"` | ||||
| 	Type  string  `json:"type"` | ||||
| 	Value float64 `json:"value"` | ||||
| } | ||||
| 
 | ||||
| // UDMStat holds the "stat" data for a dream machine.
 | ||||
| // A dream machine is a USG + USW + Controller.
 | ||||
| type UDMStat struct { | ||||
| 	*Gw `json:"gw"` | ||||
| 	*Sw `json:"sw"` | ||||
| 	*Ap `json:"ap,omitempty"` | ||||
| } | ||||
|  | @ -0,0 +1,382 @@ | |||
| // Package unifi provides a set of types to unload (unmarshal) Ubiquiti UniFi
 | ||||
| // controller data. Also provided are methods to easily get data for devices -
 | ||||
| // things like access points and switches, and for clients - the things
 | ||||
| // connected to those access points and switches. As a bonus, each device and
 | ||||
| // client type provided has an attached method to create InfluxDB datapoints.
 | ||||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"crypto/sha256" | ||||
| 	"crypto/tls" | ||||
| 	"crypto/x509" | ||||
| 	"encoding/json" | ||||
| 	"encoding/pem" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/http/cookiejar" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"golang.org/x/net/publicsuffix" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	ErrAuthenticationFailed = fmt.Errorf("authentication failed") | ||||
| 	ErrInvalidStatusCode    = fmt.Errorf("invalid status code from server") | ||||
| 	ErrNoParams             = fmt.Errorf("requested PUT with no parameters") | ||||
| 	ErrInvalidSignature     = fmt.Errorf("certificate signature does not match") | ||||
| ) | ||||
| 
 | ||||
| // NewUnifi creates a http.Client with authenticated cookies.
 | ||||
| // Used to make additional, authenticated requests to the APIs.
 | ||||
| // Start here.
 | ||||
| func NewUnifi(config *Config) (*Unifi, error) { | ||||
| 	jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("creating cookiejar: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	u := newUnifi(config, jar) | ||||
| 
 | ||||
| 	for i, cert := range config.SSLCert { | ||||
| 		p, _ := pem.Decode(cert) | ||||
| 		u.fingerprints[i] = fmt.Sprintf("%x", sha256.Sum256(p.Bytes)) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := u.checkNewStyleAPI(); err != nil { | ||||
| 		return u, err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := u.Login(); err != nil { | ||||
| 		return u, err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := u.GetServerData(); err != nil { | ||||
| 		return u, fmt.Errorf("unable to get server version: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return u, nil | ||||
| } | ||||
| 
 | ||||
| func newUnifi(config *Config, jar http.CookieJar) *Unifi { | ||||
| 	config.URL = strings.TrimRight(config.URL, "/") | ||||
| 
 | ||||
| 	if config.ErrorLog == nil { | ||||
| 		config.ErrorLog = discardLogs | ||||
| 	} | ||||
| 
 | ||||
| 	if config.DebugLog == nil { | ||||
| 		config.DebugLog = discardLogs | ||||
| 	} | ||||
| 
 | ||||
| 	u := &Unifi{ | ||||
| 		Config: config, | ||||
| 		Client: &http.Client{ | ||||
| 			Timeout: config.Timeout, | ||||
| 			Jar:     jar, | ||||
| 			Transport: &http.Transport{ | ||||
| 				TLSClientConfig: &tls.Config{ | ||||
| 					InsecureSkipVerify: !config.VerifySSL, // nolint: gosec
 | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	if len(config.SSLCert) > 0 { | ||||
| 		u.fingerprints = make(fingerprints, len(config.SSLCert)) | ||||
| 		u.Client.Transport = &http.Transport{ | ||||
| 			TLSClientConfig: &tls.Config{ | ||||
| 				InsecureSkipVerify:    true, // nolint: gosec
 | ||||
| 				VerifyPeerCertificate: u.verifyPeerCertificate, | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return u | ||||
| } | ||||
| 
 | ||||
| func (u *Unifi) verifyPeerCertificate(certs [][]byte, chains [][]*x509.Certificate) error { | ||||
| 	if len(u.fingerprints) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	for _, cert := range certs { | ||||
| 		if u.fingerprints.Contains(fmt.Sprintf("%x", sha256.Sum256(cert))) { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ErrInvalidSignature | ||||
| } | ||||
| 
 | ||||
| // Login is a helper method. It can be called to grab a new authentication cookie.
 | ||||
| func (u *Unifi) Login() error { | ||||
| 	start := time.Now() | ||||
| 
 | ||||
| 	// magic login.
 | ||||
| 	req, err := u.UniReq(APILoginPath, fmt.Sprintf(`{"username":"%s","password":"%s"}`, u.User, u.Pass)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := u.Do(req) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("making request: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	defer resp.Body.Close()                   // we need no data here.
 | ||||
| 	_, _ = io.Copy(ioutil.Discard, resp.Body) // avoid leaking.
 | ||||
| 	u.DebugLog("Requested %s: elapsed %v, returned %d bytes", | ||||
| 		req.URL, time.Since(start).Round(time.Millisecond), resp.ContentLength) | ||||
| 
 | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		return fmt.Errorf("(user: %s): %s (status: %s): %w", | ||||
| 			u.User, req.URL, resp.Status, ErrAuthenticationFailed) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Logout closes the current session.
 | ||||
| func (u *Unifi) Logout() error { | ||||
| 	// a post is needed for logout
 | ||||
| 	_, err := u.PostJSON(APILogoutPath) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // with the release of controller version 5.12.55 on UDM in Jan 2020 the api paths
 | ||||
| // changed and broke this library. This function runs when `NewUnifi()` is called to
 | ||||
| // check if this is a newer controller or not. If it is, we set new to true.
 | ||||
| // Setting new to true makes the path() method return different (new) paths.
 | ||||
| func (u *Unifi) checkNewStyleAPI() error { | ||||
| 	var ( | ||||
| 		ctx    = context.Background() | ||||
| 		cancel func() | ||||
| 	) | ||||
| 
 | ||||
| 	if u.Config.Timeout != 0 { | ||||
| 		ctx, cancel = context.WithTimeout(ctx, u.Config.Timeout) | ||||
| 		defer cancel() | ||||
| 	} | ||||
| 
 | ||||
| 	u.DebugLog("Requesting %s/ to determine API paths", u.URL) | ||||
| 
 | ||||
| 	req, err := http.NewRequestWithContext(ctx, "GET", u.URL+"/", nil) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("creating request: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// We can't share these cookies with other requests, so make a new client.
 | ||||
| 	// Checking the return code on the first request so don't follow a redirect.
 | ||||
| 	client := &http.Client{ | ||||
| 		CheckRedirect: func(req *http.Request, via []*http.Request) error { | ||||
| 			return http.ErrUseLastResponse | ||||
| 		}, | ||||
| 		Transport: &http.Transport{ | ||||
| 			TLSClientConfig: &tls.Config{InsecureSkipVerify: !u.VerifySSL}, // nolint: gosec
 | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := client.Do(req) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("making request: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	defer resp.Body.Close()                   // we need no data here.
 | ||||
| 	_, _ = io.Copy(ioutil.Discard, resp.Body) // avoid leaking.
 | ||||
| 
 | ||||
| 	if resp.StatusCode == http.StatusOK { | ||||
| 		// The new version returns a "200" for a / request.
 | ||||
| 		u.new = true | ||||
| 		u.DebugLog("Using NEW UniFi controller API paths for %s", req.URL) | ||||
| 	} | ||||
| 
 | ||||
| 	// The old version returns a "302" (to /manage) for a / request
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GetServerData sets the controller's version and UUID. Only call this if you
 | ||||
| // previously called Login and suspect the controller version has changed.
 | ||||
| func (u *Unifi) GetServerData() error { | ||||
| 	var response struct { | ||||
| 		Data server `json:"meta"` | ||||
| 	} | ||||
| 
 | ||||
| 	u.server = &response.Data | ||||
| 
 | ||||
| 	return u.GetData(APIStatusPath, &response) | ||||
| } | ||||
| 
 | ||||
| // GetData makes a unifi request and unmarshals the response into a provided pointer.
 | ||||
| func (u *Unifi) GetData(apiPath string, v interface{}, params ...string) error { | ||||
| 	start := time.Now() | ||||
| 
 | ||||
| 	body, err := u.GetJSON(apiPath, params...) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	u.DebugLog("Requested %s: elapsed %v, returned %d bytes", | ||||
| 		u.URL+u.path(apiPath), time.Since(start).Round(time.Millisecond), len(body)) | ||||
| 
 | ||||
| 	return json.Unmarshal(body, v) | ||||
| } | ||||
| 
 | ||||
| // PutData makes a unifi request and unmarshals the response into a provided pointer.
 | ||||
| func (u *Unifi) PutData(apiPath string, v interface{}, params ...string) error { | ||||
| 	start := time.Now() | ||||
| 
 | ||||
| 	body, err := u.PutJSON(apiPath, params...) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	u.DebugLog("Requested %s: elapsed %v, returned %d bytes", | ||||
| 		u.URL+u.path(apiPath), time.Since(start).Round(time.Millisecond), len(body)) | ||||
| 
 | ||||
| 	return json.Unmarshal(body, v) | ||||
| } | ||||
| 
 | ||||
| // UniReq is a small helper function that adds an Accept header.
 | ||||
| // Use this if you're unmarshalling UniFi data into custom types.
 | ||||
| // And if you're doing that... sumbut a pull request with your new struct. :)
 | ||||
| // This is a helper method that is exposed for convenience.
 | ||||
| func (u *Unifi) UniReq(apiPath string, params string) (*http.Request, error) { | ||||
| 	var ( | ||||
| 		req *http.Request | ||||
| 		err error | ||||
| 	) | ||||
| 
 | ||||
| 	switch apiPath = u.path(apiPath); params { | ||||
| 	case "": | ||||
| 		req, err = http.NewRequest(http.MethodGet, u.URL+apiPath, nil) | ||||
| 	default: | ||||
| 		req, err = http.NewRequest(http.MethodPost, u.URL+apiPath, bytes.NewBufferString(params)) | ||||
| 	} | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("creating request: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	u.setHeaders(req, params) | ||||
| 
 | ||||
| 	return req, nil | ||||
| } | ||||
| 
 | ||||
| // UniReqPut is the Put call equivalent to UniReq.
 | ||||
| func (u *Unifi) UniReqPut(apiPath string, params string) (*http.Request, error) { | ||||
| 	if params == "" { | ||||
| 		return nil, ErrNoParams | ||||
| 	} | ||||
| 
 | ||||
| 	apiPath = u.path(apiPath) | ||||
| 
 | ||||
| 	req, err := http.NewRequest(http.MethodPut, u.URL+apiPath, bytes.NewBufferString(params)) //nolint:noctx
 | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("creating request: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	u.setHeaders(req, params) | ||||
| 
 | ||||
| 	return req, nil | ||||
| } | ||||
| 
 | ||||
| // UniReqPost is the Post call equivalent to UniReq.
 | ||||
| func (u *Unifi) UniReqPost(apiPath string, params string) (*http.Request, error) { | ||||
| 	apiPath = u.path(apiPath) | ||||
| 
 | ||||
| 	req, err := http.NewRequest(http.MethodPost, u.URL+apiPath, bytes.NewBufferString(params)) //nolint:noctx
 | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("creating request: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	u.setHeaders(req, params) | ||||
| 
 | ||||
| 	return req, nil | ||||
| } | ||||
| 
 | ||||
| // GetJSON returns the raw JSON from a path. This is useful for debugging.
 | ||||
| func (u *Unifi) GetJSON(apiPath string, params ...string) ([]byte, error) { | ||||
| 	req, err := u.UniReq(apiPath, strings.Join(params, " ")) | ||||
| 	if err != nil { | ||||
| 		return []byte{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return u.do(req) | ||||
| } | ||||
| 
 | ||||
| // PutJSON uses a PUT call and returns the raw JSON in the same way as GetData
 | ||||
| // Use this if you want to change data via the REST API.
 | ||||
| func (u *Unifi) PutJSON(apiPath string, params ...string) ([]byte, error) { | ||||
| 	req, err := u.UniReqPut(apiPath, strings.Join(params, " ")) | ||||
| 	if err != nil { | ||||
| 		return []byte{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return u.do(req) | ||||
| } | ||||
| 
 | ||||
| // PostJSON uses a POST call and returns the raw JSON in the same way as GetData
 | ||||
| // Use this if you want to change data via the REST API.
 | ||||
| func (u *Unifi) PostJSON(apiPath string, params ...string) ([]byte, error) { | ||||
| 	req, err := u.UniReqPost(apiPath, strings.Join(params, " ")) | ||||
| 	if err != nil { | ||||
| 		return []byte{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return u.do(req) | ||||
| } | ||||
| 
 | ||||
| func (u *Unifi) do(req *http.Request) ([]byte, error) { | ||||
| 	var ( | ||||
| 		cancel func() | ||||
| 		ctx    = context.Background() | ||||
| 	) | ||||
| 
 | ||||
| 	if u.Config.Timeout != 0 { | ||||
| 		ctx, cancel = context.WithTimeout(ctx, u.Config.Timeout) | ||||
| 		defer cancel() | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := u.Do(req.WithContext(ctx)) | ||||
| 	if err != nil { | ||||
| 		return []byte{}, fmt.Errorf("making request: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	defer resp.Body.Close() | ||||
| 
 | ||||
| 	body, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return body, fmt.Errorf("reading response: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Save the returned CSRF header.
 | ||||
| 	if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { | ||||
| 		u.csrf = resp.Header.Get("x-csrf-token") | ||||
| 	} | ||||
| 
 | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		err = fmt.Errorf("%s: %s: %w", req.URL, resp.Status, ErrInvalidStatusCode) | ||||
| 	} | ||||
| 
 | ||||
| 	return body, err | ||||
| } | ||||
| 
 | ||||
| func (u *Unifi) setHeaders(req *http.Request, params string) { | ||||
| 	// Add the saved CSRF header.
 | ||||
| 	req.Header.Set("X-CSRF-Token", u.csrf) | ||||
| 	req.Header.Add("Accept", "application/json") | ||||
| 	req.Header.Add("Content-Type", "application/json; charset=utf-8") | ||||
| 
 | ||||
| 	if u.Client.Jar != nil { | ||||
| 		parsedURL, _ := url.Parse(req.URL.String()) | ||||
| 		u.DebugLog("Requesting %s, with params: %v, cookies: %d", req.URL, params != "", len(u.Client.Jar.Cookies(parsedURL))) | ||||
| 	} else { | ||||
| 		u.DebugLog("Requesting %s, with params: %v,", req.URL, params != "") | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,95 @@ | |||
| package unifi // nolint: testpackage
 | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
| 
 | ||||
| func TestNewUnifi(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	a := assert.New(t) | ||||
| 	u := "http://127.0.0.1:64431" | ||||
| 	c := &Config{ | ||||
| 		User:      "user1", | ||||
| 		Pass:      "pass2", | ||||
| 		URL:       u, | ||||
| 		VerifySSL: false, | ||||
| 		DebugLog:  discardLogs, | ||||
| 	} | ||||
| 	authReq, err := NewUnifi(c) | ||||
| 	a.NotNil(err) | ||||
| 	a.EqualValues(u, authReq.URL) | ||||
| 	a.Contains(err.Error(), "connection refused", "an invalid destination should produce a connection error.") | ||||
| } | ||||
| 
 | ||||
| func TestUniReq(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	a := assert.New(t) | ||||
| 	p := "/test/path" | ||||
| 	u := "http://some.url:8443" | ||||
| 
 | ||||
| 	// Test empty parameters.
 | ||||
| 	authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}} | ||||
| 	r, err := authReq.UniReq(p, "") | ||||
| 
 | ||||
| 	a.Nil(err, "newrequest must not produce an error") | ||||
| 	a.EqualValues(p, r.URL.Path, | ||||
| 		"the provided apiPath was not added to http request") | ||||
| 	a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") | ||||
| 	a.EqualValues("GET", r.Method, "without parameters the method must be GET") | ||||
| 	a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json") | ||||
| 
 | ||||
| 	// Test with parameters
 | ||||
| 	k := "key1=value9&key2=value7" | ||||
| 	authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}} | ||||
| 	r, err = authReq.UniReq(p, k) | ||||
| 	a.Nil(err, "newrequest must not produce an error") | ||||
| 
 | ||||
| 	a.EqualValues(p, r.URL.Path, | ||||
| 		"the provided apiPath was not added to http request") | ||||
| 	a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") | ||||
| 	a.EqualValues("POST", r.Method, "with parameters the method must be POST") | ||||
| 	a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json") | ||||
| 
 | ||||
| 	// Check the parameters.
 | ||||
| 	d, err := ioutil.ReadAll(r.Body) | ||||
| 	a.Nil(err, "problem reading request body, POST parameters may be malformed") | ||||
| 	a.EqualValues(k, string(d), "POST parameters improperly encoded") | ||||
| } | ||||
| 
 | ||||
| func TestUniReqPut(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	a := assert.New(t) | ||||
| 	p := "/test/path" | ||||
| 	u := "http://some.url:8443" | ||||
| 
 | ||||
| 	// Test empty parameters.
 | ||||
| 	authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}} | ||||
| 	_, err := authReq.UniReqPut(p, "") | ||||
| 	a.NotNil(err, "empty params must produce an error") | ||||
| 
 | ||||
| 	// Test with parameters
 | ||||
| 	k := "key1=value9&key2=value7" | ||||
| 	authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}} | ||||
| 	r, err := authReq.UniReqPut(p, k) | ||||
| 	a.Nil(err, "newrequest must not produce an error") | ||||
| 
 | ||||
| 	a.EqualValues(p, r.URL.Path, | ||||
| 		"the provided apiPath was not added to http request") | ||||
| 	a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") | ||||
| 	a.EqualValues("PUT", r.Method, "with parameters the method must be POST") | ||||
| 	a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json") | ||||
| 
 | ||||
| 	// Check the parameters.
 | ||||
| 	d, err := ioutil.ReadAll(r.Body) | ||||
| 	a.Nil(err, "problem reading request body, PUT parameters may be malformed") | ||||
| 	a.EqualValues(k, string(d), "PUT parameters improperly encoded") | ||||
| } | ||||
| 
 | ||||
| /* NOT DONE: OPEN web server, check parameters posted, more. These tests are incomplete. | ||||
| a.EqualValues(`{"username": "user1","password": "pass2"}`, string(post_params), | ||||
| 	"user/pass json parameters improperly encoded") | ||||
| */ | ||||
|  | @ -0,0 +1,71 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // GetUsers returns a response full of clients that connected to the UDM within the provided amount of time
 | ||||
| // using the insight historical connection data set.
 | ||||
| func (u *Unifi) GetUsers(sites []*Site, hours int) ([]*User, error) { | ||||
| 	data := make([]*User, 0) | ||||
| 
 | ||||
| 	for _, site := range sites { | ||||
| 		var ( | ||||
| 			response struct { | ||||
| 				Data []*User `json:"data"` | ||||
| 			} | ||||
| 			params = fmt.Sprintf(`{ "type": "all:", "conn": "all", "within":%d }`, hours) | ||||
| 		) | ||||
| 
 | ||||
| 		u.DebugLog("Polling Controller, retrieving UniFi Users, site %s ", site.SiteName) | ||||
| 
 | ||||
| 		clientPath := fmt.Sprintf(APIAllUserPath, site.Name) | ||||
| 		if err := u.GetData(clientPath, &response, params); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		for i, d := range response.Data { | ||||
| 			// Add special SourceName value.
 | ||||
| 			response.Data[i].SourceName = u.URL | ||||
| 			// Add the special "Site Name" to each client. This becomes a Grafana filter somewhere.
 | ||||
| 			response.Data[i].SiteName = site.SiteName | ||||
| 			// Fix name and hostname fields. Sometimes one or the other is blank.
 | ||||
| 			response.Data[i].Hostname = strings.TrimSpace(pick(d.Hostname, d.Name, d.Mac)) | ||||
| 			response.Data[i].Name = strings.TrimSpace(pick(d.Name, d.Hostname)) | ||||
| 		} | ||||
| 
 | ||||
| 		data = append(data, response.Data...) | ||||
| 	} | ||||
| 
 | ||||
| 	return data, nil | ||||
| } | ||||
| 
 | ||||
| // User defines the metadata available for previously connected clients.
 | ||||
| type User struct { | ||||
| 	SourceName          string   `json:"-"` | ||||
| 	SiteName            string   `json:"-"` | ||||
| 	ID                  string   `json:"_id"` | ||||
| 	Mac                 string   `json:"mac"` | ||||
| 	SiteID              string   `json:"site_id"` | ||||
| 	Oui                 string   `json:"oui,omitempty"` | ||||
| 	IsGuest             bool     `json:"is_guest"` | ||||
| 	FirstSeen           FlexInt  `json:"first_seen,omitempty"` | ||||
| 	LastSeen            FlexInt  `json:"last_seen,omitempty"` | ||||
| 	IsWired             bool     `json:"is_wired,omitempty"` | ||||
| 	Hostname            string   `json:"hostname,omitempty"` | ||||
| 	Duration            FlexInt  `json:"duration,omitempty"` | ||||
| 	TxBytes             FlexInt  `json:"tx_bytes,omitempty"` | ||||
| 	TxPackets           FlexInt  `json:"tx_packets,omitempty"` | ||||
| 	RxBytes             FlexInt  `json:"rx_bytes,omitempty"` | ||||
| 	RxPackets           FlexInt  `json:"rx_packets,omitempty"` | ||||
| 	WifiTxAttempts      FlexInt  `json:"wifi_tx_attempts,omitempty"` | ||||
| 	TxRetries           FlexInt  `json:"tx_retries,omitempty"` | ||||
| 	UsergroupID         string   `json:"usergroup_id,omitempty"` | ||||
| 	Name                string   `json:"name,omitempty"` | ||||
| 	Note                string   `json:"note,omitempty"` | ||||
| 	Noted               FlexBool `json:"noted,omitempty"` | ||||
| 	Blocked             FlexBool `json:"blocked,omitempty"` | ||||
| 	DevIDOverride       FlexInt  `json:"dev_id_override,omitempty"` | ||||
| 	FingerprintOverride FlexBool `json:"fingerprint_override,omitempty"` | ||||
| } | ||||
|  | @ -0,0 +1,252 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // USG represents all the data from the Ubiquiti Controller for a Unifi Security Gateway.
 | ||||
| type USG struct { | ||||
| 	site                  *Site | ||||
| 	SourceName            string               `json:"-"` | ||||
| 	ID                    string               `json:"_id"` | ||||
| 	Adopted               FlexBool             `json:"adopted"` | ||||
| 	Cfgversion            string               `json:"cfgversion"` | ||||
| 	ConfigNetwork         *ConfigNetwork       `json:"config_network"` | ||||
| 	EthernetTable         []*EthernetTable     `json:"ethernet_table"` | ||||
| 	FwCaps                FlexInt              `json:"fw_caps"` | ||||
| 	InformIP              string               `json:"inform_ip"` | ||||
| 	InformURL             string               `json:"inform_url"` | ||||
| 	IP                    string               `json:"ip"` | ||||
| 	LedOverride           string               `json:"led_override"` | ||||
| 	LicenseState          string               `json:"license_state"` | ||||
| 	Mac                   string               `json:"mac"` | ||||
| 	Model                 string               `json:"model"` | ||||
| 	Name                  string               `json:"name"` | ||||
| 	OutdoorModeOverride   string               `json:"outdoor_mode_override"` | ||||
| 	Serial                string               `json:"serial"` | ||||
| 	SiteID                string               `json:"site_id"` | ||||
| 	SiteName              string               `json:"-"` | ||||
| 	Type                  string               `json:"type"` | ||||
| 	UsgCaps               FlexInt              `json:"usg_caps"` | ||||
| 	Version               string               `json:"version"` | ||||
| 	RequiredVersion       string               `json:"required_version"` | ||||
| 	EthernetOverrides     []*EthernetOverrides `json:"ethernet_overrides"` | ||||
| 	HwCaps                FlexInt              `json:"hw_caps"` | ||||
| 	BoardRev              FlexInt              `json:"board_rev"` | ||||
| 	Unsupported           FlexBool             `json:"unsupported"` | ||||
| 	UnsupportedReason     FlexInt              `json:"unsupported_reason"` | ||||
| 	DeviceID              string               `json:"device_id"` | ||||
| 	State                 FlexInt              `json:"state"` | ||||
| 	LastSeen              FlexInt              `json:"last_seen"` | ||||
| 	Upgradable            FlexBool             `json:"upgradable"` | ||||
| 	AdoptableWhenUpgraded FlexBool             `json:"adoptable_when_upgraded"` | ||||
| 	Rollupgrade           FlexBool             `json:"rollupgrade"` | ||||
| 	KnownCfgversion       string               `json:"known_cfgversion"` | ||||
| 	Uptime                FlexInt              `json:"uptime"` | ||||
| 	Locating              FlexBool             `json:"locating"` | ||||
| 	ConnectRequestIP      string               `json:"connect_request_ip"` | ||||
| 	ConnectRequestPort    string               `json:"connect_request_port"` | ||||
| 	SysStats              SysStats             `json:"sys_stats"` | ||||
| 	Temperatures          []Temperature        `json:"temperatures,omitempty"` | ||||
| 	SystemStats           SystemStats          `json:"system-stats"` | ||||
| 	GuestToken            string               `json:"guest_token"` | ||||
| 	SpeedtestStatus       SpeedtestStatus      `json:"speedtest-status"` | ||||
| 	SpeedtestStatusSaved  FlexBool             `json:"speedtest-status-saved"` | ||||
| 	Wan1                  Wan                  `json:"wan1"` | ||||
| 	Wan2                  Wan                  `json:"wan2"` | ||||
| 	PortTable             []*Port              `json:"port_table"` | ||||
| 	NetworkTable          NetworkTable         `json:"network_table"` | ||||
| 	Uplink                Uplink               `json:"uplink"` | ||||
| 	Stat                  USGStat              `json:"stat"` | ||||
| 	TxBytes               FlexInt              `json:"tx_bytes"` | ||||
| 	RxBytes               FlexInt              `json:"rx_bytes"` | ||||
| 	Bytes                 FlexInt              `json:"bytes"` | ||||
| 	NumSta                FlexInt              `json:"num_sta"` | ||||
| 	UserNumSta            FlexInt              `json:"user-num_sta"` | ||||
| 	GuestNumSta           FlexInt              `json:"guest-num_sta"` | ||||
| 	NumDesktop            FlexInt              `json:"num_desktop"` | ||||
| 	NumMobile             FlexInt              `json:"num_mobile"` | ||||
| 	NumHandheld           FlexInt              `json:"num_handheld"` | ||||
| } | ||||
| 
 | ||||
| // Uplink is the Internet connection (or uplink) on a UniFi device.
 | ||||
| type Uplink struct { | ||||
| 	BytesR           FlexInt  `json:"bytes-r"` | ||||
| 	Drops            FlexInt  `json:"drops"` | ||||
| 	Enable           FlexBool `json:"enable,omitempty"` | ||||
| 	FullDuplex       FlexBool `json:"full_duplex"` | ||||
| 	Gateways         []string `json:"gateways,omitempty"` | ||||
| 	IP               string   `json:"ip"` | ||||
| 	Latency          FlexInt  `json:"latency"` | ||||
| 	Mac              string   `json:"mac,omitempty"` | ||||
| 	MaxSpeed         FlexInt  `json:"max_speed"` | ||||
| 	Name             string   `json:"name"` | ||||
| 	Nameservers      []string `json:"nameservers"` | ||||
| 	Netmask          string   `json:"netmask"` | ||||
| 	NumPort          FlexInt  `json:"num_port"` | ||||
| 	Media            string   `json:"media"` | ||||
| 	PortIdx          FlexInt  `json:"port_idx"` | ||||
| 	RxBytes          FlexInt  `json:"rx_bytes"` | ||||
| 	RxBytesR         FlexInt  `json:"rx_bytes-r"` | ||||
| 	RxDropped        FlexInt  `json:"rx_dropped"` | ||||
| 	RxErrors         FlexInt  `json:"rx_errors"` | ||||
| 	RxMulticast      FlexInt  `json:"rx_multicast"` | ||||
| 	RxPackets        FlexInt  `json:"rx_packets"` | ||||
| 	RxRate           FlexInt  `json:"rx_rate"` | ||||
| 	Speed            FlexInt  `json:"speed"` | ||||
| 	SpeedtestLastrun FlexInt  `json:"speedtest_lastrun,omitempty"` | ||||
| 	SpeedtestPing    FlexInt  `json:"speedtest_ping,omitempty"` | ||||
| 	SpeedtestStatus  string   `json:"speedtest_status,omitempty"` | ||||
| 	TxBytes          FlexInt  `json:"tx_bytes"` | ||||
| 	TxBytesR         FlexInt  `json:"tx_bytes-r"` | ||||
| 	TxDropped        FlexInt  `json:"tx_dropped"` | ||||
| 	TxErrors         FlexInt  `json:"tx_errors"` | ||||
| 	TxPackets        FlexInt  `json:"tx_packets"` | ||||
| 	TxRate           FlexInt  `json:"tx_rate"` | ||||
| 	Type             string   `json:"type"` | ||||
| 	Up               FlexBool `json:"up"` | ||||
| 	Uptime           FlexInt  `json:"uptime"` | ||||
| 	XputDown         FlexInt  `json:"xput_down,omitempty"` | ||||
| 	XputUp           FlexInt  `json:"xput_up,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // Wan is a Wan interface on a USG or UDM.
 | ||||
| type Wan struct { | ||||
| 	Autoneg     FlexBool `json:"autoneg"` | ||||
| 	BytesR      FlexInt  `json:"bytes-r"` | ||||
| 	DNS         []string `json:"dns"` // may be deprecated
 | ||||
| 	Enable      FlexBool `json:"enable"` | ||||
| 	FlowctrlRx  FlexBool `json:"flowctrl_rx"` | ||||
| 	FlowctrlTx  FlexBool `json:"flowctrl_tx"` | ||||
| 	FullDuplex  FlexBool `json:"full_duplex"` | ||||
| 	Gateway     string   `json:"gateway"` // may be deprecated
 | ||||
| 	IP          string   `json:"ip"` | ||||
| 	Ifname      string   `json:"ifname"` | ||||
| 	IsUplink    FlexBool `json:"is_uplink"` | ||||
| 	Mac         string   `json:"mac"` | ||||
| 	MaxSpeed    FlexInt  `json:"max_speed"` | ||||
| 	Media       string   `json:"media"` | ||||
| 	Name        string   `json:"name"` | ||||
| 	Netmask     string   `json:"netmask"` // may be deprecated
 | ||||
| 	NumPort     int      `json:"num_port"` | ||||
| 	PortIdx     int      `json:"port_idx"` | ||||
| 	PortPoe     FlexBool `json:"port_poe"` | ||||
| 	RxBroadcast FlexInt  `json:"rx_broadcast"` | ||||
| 	RxBytes     FlexInt  `json:"rx_bytes"` | ||||
| 	RxBytesR    FlexInt  `json:"rx_bytes-r"` | ||||
| 	RxDropped   FlexInt  `json:"rx_dropped"` | ||||
| 	RxErrors    FlexInt  `json:"rx_errors"` | ||||
| 	RxMulticast FlexInt  `json:"rx_multicast"` | ||||
| 	RxPackets   FlexInt  `json:"rx_packets"` | ||||
| 	RxRate      FlexInt  `json:"rx_rate"` | ||||
| 	Speed       FlexInt  `json:"speed"` | ||||
| 	SpeedCaps   FlexInt  `json:"speed_caps"` | ||||
| 	TxBroadcast FlexInt  `json:"tx_broadcast"` | ||||
| 	TxBytes     FlexInt  `json:"tx_bytes"` | ||||
| 	TxBytesR    FlexInt  `json:"tx_bytes-r"` | ||||
| 	TxDropped   FlexInt  `json:"tx_dropped"` | ||||
| 	TxErrors    FlexInt  `json:"tx_errors"` | ||||
| 	TxMulticast FlexInt  `json:"tx_multicast"` | ||||
| 	TxPackets   FlexInt  `json:"tx_packets"` | ||||
| 	TxRate      FlexInt  `json:"tx_rate"` | ||||
| 	Type        string   `json:"type"` | ||||
| 	Up          FlexBool `json:"up"` | ||||
| } | ||||
| 
 | ||||
| // SpeedtestStatus is the speed test info on a USG or UDM.
 | ||||
| type SpeedtestStatus struct { | ||||
| 	Latency         FlexInt          `json:"latency"` | ||||
| 	Rundate         FlexInt          `json:"rundate"` | ||||
| 	Runtime         FlexInt          `json:"runtime"` | ||||
| 	ServerDesc      string           `json:"server_desc,omitempty"` | ||||
| 	Server          *SpeedtestServer `json:"server"` | ||||
| 	SourceInterface string           `json:"source_interface"` | ||||
| 	StatusDownload  FlexInt          `json:"status_download"` | ||||
| 	StatusPing      FlexInt          `json:"status_ping"` | ||||
| 	StatusSummary   FlexInt          `json:"status_summary"` | ||||
| 	StatusUpload    FlexInt          `json:"status_upload"` | ||||
| 	XputDownload    FlexInt          `json:"xput_download"` | ||||
| 	XputUpload      FlexInt          `json:"xput_upload"` | ||||
| } | ||||
| 
 | ||||
| type SpeedtestServer struct { | ||||
| 	Cc          string  `json:"cc"` | ||||
| 	City        string  `json:"city"` | ||||
| 	Country     string  `json:"country"` | ||||
| 	Lat         FlexInt `json:"lat"` | ||||
| 	Lon         FlexInt `json:"lon"` | ||||
| 	Provider    string  `json:"provider"` | ||||
| 	ProviderURL string  `json:"provider_url"` | ||||
| } | ||||
| 
 | ||||
| // ConfigNetwork comes from gateways.
 | ||||
| type ConfigNetwork struct { | ||||
| 	Type string `json:"type"` | ||||
| 	IP   string `json:"ip"` | ||||
| } | ||||
| 
 | ||||
| // SystemStats is system info for a UDM, USG, USW.
 | ||||
| type SystemStats struct { | ||||
| 	CPU    FlexInt `json:"cpu"` | ||||
| 	Mem    FlexInt `json:"mem"` | ||||
| 	Uptime FlexInt `json:"uptime"` | ||||
| 	// This exists on at least USG4, may others, maybe not.
 | ||||
| 	// {"Board (CPU)":"51 C","Board (PHY)":"51 C","CPU":"72 C","PHY":"77 C"}
 | ||||
| 	Temps map[string]string `json:"temps,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // SysStats is load info for a UDM, USG, USW.
 | ||||
| type SysStats struct { | ||||
| 	Loadavg1  FlexInt `json:"loadavg_1"` | ||||
| 	Loadavg15 FlexInt `json:"loadavg_15"` | ||||
| 	Loadavg5  FlexInt `json:"loadavg_5"` | ||||
| 	MemBuffer FlexInt `json:"mem_buffer"` | ||||
| 	MemTotal  FlexInt `json:"mem_total"` | ||||
| 	MemUsed   FlexInt `json:"mem_used"` | ||||
| } | ||||
| 
 | ||||
| // USGStat holds the "stat" data for a gateway.
 | ||||
| // This is split out because of a JSON data format change from 5.10 to 5.11.
 | ||||
| type USGStat struct { | ||||
| 	*Gw | ||||
| } | ||||
| 
 | ||||
| // Gw is a subtype of USGStat to make unmarshalling of different controller versions possible.
 | ||||
| type Gw struct { | ||||
| 	SiteID       string    `json:"site_id"` | ||||
| 	O            string    `json:"o"` | ||||
| 	Oid          string    `json:"oid"` | ||||
| 	Gw           string    `json:"gw"` | ||||
| 	Time         FlexInt   `json:"time"` | ||||
| 	Datetime     time.Time `json:"datetime"` | ||||
| 	Duration     FlexInt   `json:"duration"` | ||||
| 	WanRxPackets FlexInt   `json:"wan-rx_packets"` | ||||
| 	WanRxBytes   FlexInt   `json:"wan-rx_bytes"` | ||||
| 	WanRxDropped FlexInt   `json:"wan-rx_dropped"` | ||||
| 	WanTxPackets FlexInt   `json:"wan-tx_packets"` | ||||
| 	WanTxBytes   FlexInt   `json:"wan-tx_bytes"` | ||||
| 	LanRxPackets FlexInt   `json:"lan-rx_packets"` | ||||
| 	LanRxBytes   FlexInt   `json:"lan-rx_bytes"` | ||||
| 	LanTxPackets FlexInt   `json:"lan-tx_packets"` | ||||
| 	LanTxBytes   FlexInt   `json:"lan-tx_bytes"` | ||||
| 	LanRxDropped FlexInt   `json:"lan-rx_dropped"` | ||||
| 	WanRxErrors  FlexInt   `json:"wan-rx_errors,omitempty"` | ||||
| 	LanRxErrors  FlexInt   `json:"lan-rx_errors,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // UnmarshalJSON unmarshalls 5.10 or 5.11 formatted Gateway Stat data.
 | ||||
| func (v *USGStat) UnmarshalJSON(data []byte) error { | ||||
| 	var n struct { | ||||
| 		Gw `json:"gw"` | ||||
| 	} | ||||
| 
 | ||||
| 	v.Gw = &n.Gw | ||||
| 
 | ||||
| 	err := json.Unmarshal(data, v.Gw) // controller version 5.10.
 | ||||
| 	if err != nil { | ||||
| 		return json.Unmarshal(data, &n) // controller version 5.11.
 | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | @ -0,0 +1,60 @@ | |||
| package unifi // nolint: testpackage
 | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
| 
 | ||||
| func TestUSGUnmarshalJSON(t *testing.T) { | ||||
| 	testcontroller511 := `{ | ||||
|   "gw": { | ||||
|       "site_id": "mySite", | ||||
|       "o": "gw", | ||||
|       "oid": "00:00:00:00:00:00", | ||||
|       "gw": "00:00:00:00:00:00", | ||||
|       "time": 1577742600000, | ||||
|       "datetime": "2019-12-30T09:50:00Z", | ||||
|       "bytes": 0, | ||||
|       "duration": 3590568000, | ||||
|       "wan-rx_packets": 299729434558, | ||||
|       "wan-rx_bytes": 299882768958208, | ||||
|       "wan-tx_packets": 249639259523, | ||||
|       "wan-tx_bytes": 169183252492369, | ||||
|       "lan-rx_packets": 78912349453, | ||||
|       "lan-rx_bytes": 37599596992669, | ||||
|       "lan-tx_packets": 12991234992, | ||||
|       "lan-tx_bytes": 11794664098210}}` | ||||
| 
 | ||||
| 	testcontroller510 := `{ | ||||
|     "site_id": "mySite", | ||||
|     "o": "gw", | ||||
|     "oid": "00:00:00:00:00:00", | ||||
|     "gw": "00:00:00:00:00:00", | ||||
|     "time": 1577742600000, | ||||
|     "datetime": "2019-12-30T09:50:00Z", | ||||
|     "bytes": 0, | ||||
|     "duration": 3590568000, | ||||
|     "wan-rx_packets": 299729434558, | ||||
|     "wan-rx_bytes": 299882768958208, | ||||
|     "wan-tx_packets": 249639259523, | ||||
|     "wan-tx_bytes": 169183252492369, | ||||
|     "lan-rx_packets": 78912349453, | ||||
|     "lan-rx_bytes": 37599596992669, | ||||
|     "lan-tx_packets": 12991234992, | ||||
|     "lan-tx_bytes": 11794664098210}` | ||||
| 
 | ||||
| 	t.Parallel() | ||||
| 	a := assert.New(t) | ||||
| 
 | ||||
| 	u := &USGStat{} | ||||
| 	lanRx := 37599596992669 | ||||
| 	err := u.UnmarshalJSON([]byte(testcontroller510)) | ||||
| 	a.Nil(err, "must be no error unmarshaling test strings") | ||||
| 	a.Equal(float64(lanRx), u.LanRxBytes.Val, "data was not properly unmarshaled") | ||||
| 
 | ||||
| 	u = &USGStat{} // reset
 | ||||
| 	err = u.UnmarshalJSON([]byte(testcontroller511)) | ||||
| 	a.Nil(err, "must be no error unmarshaling test strings") | ||||
| 	a.Equal(float64(lanRx), u.LanRxBytes.Val, "data was not properly unmarshaled") | ||||
| } | ||||
|  | @ -0,0 +1,400 @@ | |||
| package unifi | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // USW represents all the data from the Ubiquiti Controller for a Unifi Switch.
 | ||||
| type USW struct { | ||||
| 	site                 *Site | ||||
| 	SourceName           string           `json:"-"` | ||||
| 	SiteName             string           `json:"-"` | ||||
| 	ID                   string           `json:"_id"` | ||||
| 	Adopted              FlexBool         `json:"adopted"` | ||||
| 	BoardRev             FlexInt          `json:"board_rev"` | ||||
| 	Cfgversion           string           `json:"cfgversion"` | ||||
| 	ConfigNetwork        *ConfigNetwork   `json:"config_network"` | ||||
| 	Dot1XPortctrlEnabled FlexBool         `json:"dot1x_portctrl_enabled"` | ||||
| 	EthernetTable        []*EthernetTable `json:"ethernet_table"` | ||||
| 	FlowctrlEnabled      FlexBool         `json:"flowctrl_enabled"` | ||||
| 	FwCaps               FlexInt          `json:"fw_caps"` | ||||
| 	HasFan               FlexBool         `json:"has_fan"` | ||||
| 	HasTemperature       FlexBool         `json:"has_temperature"` | ||||
| 	InformIP             string           `json:"inform_ip"` | ||||
| 	InformURL            string           `json:"inform_url"` | ||||
| 	IP                   string           `json:"ip"` | ||||
| 	JumboframeEnabled    FlexBool         `json:"jumboframe_enabled"` | ||||
| 	LedOverride          string           `json:"led_override"` | ||||
| 	LicenseState         string           `json:"license_state"` | ||||
| 	Mac                  string           `json:"mac"` | ||||
| 	Model                string           `json:"model"` | ||||
| 	Name                 string           `json:"name"` | ||||
| 	OutdoorModeOverride  string           `json:"outdoor_mode_override"` | ||||
| 	PortOverrides        []struct { | ||||
| 		Name       string  `json:"name,omitempty"` | ||||
| 		PoeMode    string  `json:"poe_mode,omitempty"` | ||||
| 		PortIdx    FlexInt `json:"port_idx"` | ||||
| 		PortconfID string  `json:"portconf_id"` | ||||
| 	} `json:"port_overrides"` | ||||
| 	PortTable             []Port           `json:"port_table"` | ||||
| 	Serial                string           `json:"serial"` | ||||
| 	SiteID                string           `json:"site_id"` | ||||
| 	StpPriority           FlexInt          `json:"stp_priority"` | ||||
| 	StpVersion            string           `json:"stp_version"` | ||||
| 	Type                  string           `json:"type"` | ||||
| 	Version               string           `json:"version"` | ||||
| 	RequiredVersion       string           `json:"required_version"` | ||||
| 	SwitchCaps            *SwitchCaps      `json:"switch_caps"` | ||||
| 	HwCaps                FlexInt          `json:"hw_caps"` | ||||
| 	Unsupported           FlexBool         `json:"unsupported"` | ||||
| 	UnsupportedReason     FlexInt          `json:"unsupported_reason"` | ||||
| 	SysErrorCaps          FlexInt          `json:"sys_error_caps"` | ||||
| 	DeviceID              string           `json:"device_id"` | ||||
| 	State                 FlexInt          `json:"state"` | ||||
| 	LastSeen              FlexInt          `json:"last_seen"` | ||||
| 	Upgradable            FlexBool         `json:"upgradable,omitempty"` | ||||
| 	AdoptableWhenUpgraded FlexBool         `json:"adoptable_when_upgraded,omitempty"` | ||||
| 	Rollupgrade           FlexBool         `json:"rollupgrade,omitempty"` | ||||
| 	KnownCfgversion       string           `json:"known_cfgversion"` | ||||
| 	Uptime                FlexInt          `json:"uptime"` | ||||
| 	Locating              FlexBool         `json:"locating"` | ||||
| 	ConnectRequestIP      string           `json:"connect_request_ip"` | ||||
| 	ConnectRequestPort    string           `json:"connect_request_port"` | ||||
| 	SysStats              SysStats         `json:"sys_stats"` | ||||
| 	SystemStats           SystemStats      `json:"system-stats"` | ||||
| 	FanLevel              FlexInt          `json:"fan_level"` | ||||
| 	GeneralTemperature    FlexInt          `json:"general_temperature"` | ||||
| 	Overheating           FlexBool         `json:"overheating"` | ||||
| 	TotalMaxPower         FlexInt          `json:"total_max_power"` | ||||
| 	DownlinkTable         []*DownlinkTable `json:"downlink_table"` | ||||
| 	Uplink                Uplink           `json:"uplink"` | ||||
| 	LastUplink            struct { | ||||
| 		UplinkMac string `json:"uplink_mac"` | ||||
| 	} `json:"last_uplink"` | ||||
| 	UplinkDepth FlexInt `json:"uplink_depth"` | ||||
| 	Stat        USWStat `json:"stat"` | ||||
| 	TxBytes     FlexInt `json:"tx_bytes"` | ||||
| 	RxBytes     FlexInt `json:"rx_bytes"` | ||||
| 	Bytes       FlexInt `json:"bytes"` | ||||
| 	NumSta      FlexInt `json:"num_sta"` | ||||
| 	UserNumSta  FlexInt `json:"user-num_sta"` | ||||
| 	GuestNumSta FlexInt `json:"guest-num_sta"` | ||||
| } | ||||
| 
 | ||||
| type SwitchCaps struct { | ||||
| 	FeatureCaps          FlexInt `json:"feature_caps"` | ||||
| 	MaxMirrorSessions    FlexInt `json:"max_mirror_sessions"` | ||||
| 	MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"` | ||||
| } | ||||
| 
 | ||||
| // MacTable is a newer feature on some switched ports.
 | ||||
| type MacTable struct { | ||||
| 	Age           int64    `json:"age"` | ||||
| 	Authorized    FlexBool `json:"authorized"` | ||||
| 	Hostname      string   `json:"hostname"` | ||||
| 	IP            string   `json:"ip"` | ||||
| 	LastReachable int64    `json:"lastReachable"` | ||||
| 	Mac           string   `json:"mac"` | ||||
| } | ||||
| 
 | ||||
| // Port is a physical connection on a USW or Gateway.
 | ||||
| // Not every port has the same capabilities.
 | ||||
| type Port struct { | ||||
| 	AggregatedBy       FlexBool   `json:"aggregated_by"` | ||||
| 	Autoneg            FlexBool   `json:"autoneg,omitempty"` | ||||
| 	BytesR             FlexInt    `json:"bytes-r"` | ||||
| 	DNS                []string   `json:"dns,omitempty"` | ||||
| 	Dot1XMode          string     `json:"dot1x_mode"` | ||||
| 	Dot1XStatus        string     `json:"dot1x_status"` | ||||
| 	Enable             FlexBool   `json:"enable"` | ||||
| 	FlowctrlRx         FlexBool   `json:"flowctrl_rx"` | ||||
| 	FlowctrlTx         FlexBool   `json:"flowctrl_tx"` | ||||
| 	FullDuplex         FlexBool   `json:"full_duplex"` | ||||
| 	IP                 string     `json:"ip,omitempty"` | ||||
| 	Ifname             string     `json:"ifname,omitempty"` | ||||
| 	IsUplink           FlexBool   `json:"is_uplink"` | ||||
| 	Mac                string     `json:"mac,omitempty"` | ||||
| 	MacTable           []MacTable `json:"mac_table,omitempty"` | ||||
| 	Jumbo              FlexBool   `json:"jumbo,omitempty"` | ||||
| 	Masked             FlexBool   `json:"masked"` | ||||
| 	Media              string     `json:"media"` | ||||
| 	Name               string     `json:"name"` | ||||
| 	NetworkName        string     `json:"network_name,omitempty"` | ||||
| 	Netmask            string     `json:"netmask,omitempty"` | ||||
| 	NumPort            int        `json:"num_port,omitempty"` | ||||
| 	OpMode             string     `json:"op_mode"` | ||||
| 	PoeCaps            FlexInt    `json:"poe_caps"` | ||||
| 	PoeClass           string     `json:"poe_class,omitempty"` | ||||
| 	PoeCurrent         FlexInt    `json:"poe_current,omitempty"` | ||||
| 	PoeEnable          FlexBool   `json:"poe_enable,omitempty"` | ||||
| 	PoeGood            FlexBool   `json:"poe_good,omitempty"` | ||||
| 	PoeMode            string     `json:"poe_mode,omitempty"` | ||||
| 	PoePower           FlexInt    `json:"poe_power,omitempty"` | ||||
| 	PoeVoltage         FlexInt    `json:"poe_voltage,omitempty"` | ||||
| 	PortDelta          PortDelta  `json:"port_delta,omitempty"` | ||||
| 	PortIdx            FlexInt    `json:"port_idx"` | ||||
| 	PortPoe            FlexBool   `json:"port_poe"` | ||||
| 	PortconfID         string     `json:"portconf_id"` | ||||
| 	RxBroadcast        FlexInt    `json:"rx_broadcast"` | ||||
| 	RxBytes            FlexInt    `json:"rx_bytes"` | ||||
| 	RxBytesR           FlexInt    `json:"rx_bytes-r"` | ||||
| 	RxDropped          FlexInt    `json:"rx_dropped"` | ||||
| 	RxErrors           FlexInt    `json:"rx_errors"` | ||||
| 	RxMulticast        FlexInt    `json:"rx_multicast"` | ||||
| 	RxPackets          FlexInt    `json:"rx_packets"` | ||||
| 	RxRate             FlexInt    `json:"rx_rate,omitempty"` | ||||
| 	Satisfaction       FlexInt    `json:"satisfaction,omitempty"` | ||||
| 	SatisfactionReason FlexInt    `json:"satisfaction_reason"` | ||||
| 	SFPCompliance      string     `json:"sfp_compliance"` | ||||
| 	SFPCurrent         FlexInt    `json:"sfp_current"` | ||||
| 	SFPFound           FlexBool   `json:"sfp_found"` | ||||
| 	SFPPart            string     `json:"sfp_part"` | ||||
| 	SFPRev             string     `json:"sfp_rev"` | ||||
| 	SFPRxfault         FlexBool   `json:"sfp_rxfault"` | ||||
| 	SFPRxpower         FlexInt    `json:"sfp_rxpower"` | ||||
| 	SFPSerial          string     `json:"sfp_serial"` | ||||
| 	SFPTemperature     FlexInt    `json:"sfp_temperature"` | ||||
| 	SFPTxfault         FlexBool   `json:"sfp_txfault"` | ||||
| 	SFPTxpower         FlexInt    `json:"sfp_txpower"` | ||||
| 	SFPVendor          string     `json:"sfp_vendor"` | ||||
| 	SFPVoltage         FlexInt    `json:"sfp_voltage"` | ||||
| 	Speed              FlexInt    `json:"speed"` | ||||
| 	SpeedCaps          FlexInt    `json:"speed_caps"` | ||||
| 	StpPathcost        FlexInt    `json:"stp_pathcost"` | ||||
| 	StpState           string     `json:"stp_state"` | ||||
| 	TxBroadcast        FlexInt    `json:"tx_broadcast"` | ||||
| 	TxBytes            FlexInt    `json:"tx_bytes"` | ||||
| 	TxBytesR           FlexInt    `json:"tx_bytes-r"` | ||||
| 	TxDropped          FlexInt    `json:"tx_dropped"` | ||||
| 	TxErrors           FlexInt    `json:"tx_errors"` | ||||
| 	TxMulticast        FlexInt    `json:"tx_multicast"` | ||||
| 	TxPackets          FlexInt    `json:"tx_packets"` | ||||
| 	TxRate             FlexInt    `json:"tx_rate,omitempty"` | ||||
| 	Type               string     `json:"type,omitempty"` | ||||
| 	Up                 FlexBool   `json:"up"` | ||||
| } | ||||
| 
 | ||||
| // PortDelta is part of a Port.
 | ||||
| type PortDelta struct { | ||||
| 	TimeDelta         FlexInt `json:"time_delta"` | ||||
| 	TimeDeltaActivity FlexInt `json:"time_delta_activity"` | ||||
| } | ||||
| 
 | ||||
| // USWStat holds the "stat" data for a switch.
 | ||||
| // This is split out because of a JSON data format change from 5.10 to 5.11.
 | ||||
| type USWStat struct { | ||||
| 	*Sw | ||||
| } | ||||
| 
 | ||||
| // Sw is a subtype of USWStat to make unmarshalling of different controller versions possible.
 | ||||
| type Sw struct { | ||||
| 	SiteID      string    `json:"site_id"` | ||||
| 	O           string    `json:"o"` | ||||
| 	Oid         string    `json:"oid"` | ||||
| 	Sw          string    `json:"sw"` | ||||
| 	Time        FlexInt   `json:"time"` | ||||
| 	Datetime    time.Time `json:"datetime"` | ||||
| 	RxPackets   FlexInt   `json:"rx_packets"` | ||||
| 	RxBytes     FlexInt   `json:"rx_bytes"` | ||||
| 	RxErrors    FlexInt   `json:"rx_errors"` | ||||
| 	RxDropped   FlexInt   `json:"rx_dropped"` | ||||
| 	RxCrypts    FlexInt   `json:"rx_crypts"` | ||||
| 	RxFrags     FlexInt   `json:"rx_frags"` | ||||
| 	TxPackets   FlexInt   `json:"tx_packets"` | ||||
| 	TxBytes     FlexInt   `json:"tx_bytes"` | ||||
| 	TxErrors    FlexInt   `json:"tx_errors"` | ||||
| 	TxDropped   FlexInt   `json:"tx_dropped"` | ||||
| 	TxRetries   FlexInt   `json:"tx_retries"` | ||||
| 	RxMulticast FlexInt   `json:"rx_multicast"` | ||||
| 	RxBroadcast FlexInt   `json:"rx_broadcast"` | ||||
| 	TxMulticast FlexInt   `json:"tx_multicast"` | ||||
| 	TxBroadcast FlexInt   `json:"tx_broadcast"` | ||||
| 	Bytes       FlexInt   `json:"bytes"` | ||||
| 	Duration    FlexInt   `json:"duration"` | ||||
| 	/* These are all in port table */ | ||||
| 	/* | ||||
| 		Port1RxPackets    FlexInt   `json:"port_1-rx_packets,omitempty"` | ||||
| 		Port1RxBytes      FlexInt   `json:"port_1-rx_bytes,omitempty"` | ||||
| 		Port1TxPackets    FlexInt   `json:"port_1-tx_packets,omitempty"` | ||||
| 		Port1TxBytes      FlexInt   `json:"port_1-tx_bytes,omitempty"` | ||||
| 		Port1TxMulticast  FlexInt   `json:"port_1-tx_multicast"` | ||||
| 		Port1TxBroadcast  FlexInt   `json:"port_1-tx_broadcast"` | ||||
| 		Port3RxPackets    FlexInt   `json:"port_3-rx_packets,omitempty"` | ||||
| 		Port3RxBytes      FlexInt   `json:"port_3-rx_bytes,omitempty"` | ||||
| 		Port3TxPackets    FlexInt   `json:"port_3-tx_packets,omitempty"` | ||||
| 		Port3TxBytes      FlexInt   `json:"port_3-tx_bytes,omitempty"` | ||||
| 		Port3RxBroadcast  FlexInt   `json:"port_3-rx_broadcast"` | ||||
| 		Port3TxMulticast  FlexInt   `json:"port_3-tx_multicast"` | ||||
| 		Port3TxBroadcast  FlexInt   `json:"port_3-tx_broadcast"` | ||||
| 		Port6RxPackets    FlexInt   `json:"port_6-rx_packets,omitempty"` | ||||
| 		Port6RxBytes      FlexInt   `json:"port_6-rx_bytes,omitempty"` | ||||
| 		Port6TxPackets    FlexInt   `json:"port_6-tx_packets,omitempty"` | ||||
| 		Port6TxBytes      FlexInt   `json:"port_6-tx_bytes,omitempty"` | ||||
| 		Port6RxMulticast  FlexInt   `json:"port_6-rx_multicast"` | ||||
| 		Port6TxMulticast  FlexInt   `json:"port_6-tx_multicast"` | ||||
| 		Port6TxBroadcast  FlexInt   `json:"port_6-tx_broadcast"` | ||||
| 		Port7RxPackets    FlexInt   `json:"port_7-rx_packets,omitempty"` | ||||
| 		Port7RxBytes      FlexInt   `json:"port_7-rx_bytes,omitempty"` | ||||
| 		Port7TxPackets    FlexInt   `json:"port_7-tx_packets,omitempty"` | ||||
| 		Port7TxBytes      FlexInt   `json:"port_7-tx_bytes,omitempty"` | ||||
| 		Port7TxMulticast  FlexInt   `json:"port_7-tx_multicast"` | ||||
| 		Port7TxBroadcast  FlexInt   `json:"port_7-tx_broadcast"` | ||||
| 		Port9RxPackets    FlexInt   `json:"port_9-rx_packets,omitempty"` | ||||
| 		Port9RxBytes      FlexInt   `json:"port_9-rx_bytes,omitempty"` | ||||
| 		Port9TxPackets    FlexInt   `json:"port_9-tx_packets,omitempty"` | ||||
| 		Port9TxBytes      FlexInt   `json:"port_9-tx_bytes,omitempty"` | ||||
| 		Port9TxMulticast  FlexInt   `json:"port_9-tx_multicast"` | ||||
| 		Port9TxBroadcast  FlexInt   `json:"port_9-tx_broadcast"` | ||||
| 		Port10RxPackets   FlexInt   `json:"port_10-rx_packets,omitempty"` | ||||
| 		Port10RxBytes     FlexInt   `json:"port_10-rx_bytes,omitempty"` | ||||
| 		Port10TxPackets   FlexInt   `json:"port_10-tx_packets,omitempty"` | ||||
| 		Port10TxBytes     FlexInt   `json:"port_10-tx_bytes,omitempty"` | ||||
| 		Port10RxMulticast FlexInt   `json:"port_10-rx_multicast"` | ||||
| 		Port10TxMulticast FlexInt   `json:"port_10-tx_multicast"` | ||||
| 		Port10TxBroadcast FlexInt   `json:"port_10-tx_broadcast"` | ||||
| 		Port11RxPackets   FlexInt   `json:"port_11-rx_packets,omitempty"` | ||||
| 		Port11RxBytes     FlexInt   `json:"port_11-rx_bytes,omitempty"` | ||||
| 		Port11TxPackets   FlexInt   `json:"port_11-tx_packets,omitempty"` | ||||
| 		Port11TxBytes     FlexInt   `json:"port_11-tx_bytes,omitempty"` | ||||
| 		Port11TxMulticast FlexInt   `json:"port_11-tx_multicast"` | ||||
| 		Port11TxBroadcast FlexInt   `json:"port_11-tx_broadcast"` | ||||
| 		Port12RxPackets   FlexInt   `json:"port_12-rx_packets,omitempty"` | ||||
| 		Port12RxBytes     FlexInt   `json:"port_12-rx_bytes,omitempty"` | ||||
| 		Port12TxPackets   FlexInt   `json:"port_12-tx_packets,omitempty"` | ||||
| 		Port12TxBytes     FlexInt   `json:"port_12-tx_bytes,omitempty"` | ||||
| 		Port12TxMulticast FlexInt   `json:"port_12-tx_multicast"` | ||||
| 		Port12TxBroadcast FlexInt   `json:"port_12-tx_broadcast"` | ||||
| 		Port13RxPackets   FlexInt   `json:"port_13-rx_packets,omitempty"` | ||||
| 		Port13RxBytes     FlexInt   `json:"port_13-rx_bytes,omitempty"` | ||||
| 		Port13TxPackets   FlexInt   `json:"port_13-tx_packets,omitempty"` | ||||
| 		Port13TxBytes     FlexInt   `json:"port_13-tx_bytes,omitempty"` | ||||
| 		Port13RxMulticast FlexInt   `json:"port_13-rx_multicast"` | ||||
| 		Port13RxBroadcast FlexInt   `json:"port_13-rx_broadcast"` | ||||
| 		Port13TxMulticast FlexInt   `json:"port_13-tx_multicast"` | ||||
| 		Port13TxBroadcast FlexInt   `json:"port_13-tx_broadcast"` | ||||
| 		Port15RxPackets   FlexInt   `json:"port_15-rx_packets,omitempty"` | ||||
| 		Port15RxBytes     FlexInt   `json:"port_15-rx_bytes,omitempty"` | ||||
| 		Port15TxPackets   FlexInt   `json:"port_15-tx_packets,omitempty"` | ||||
| 		Port15TxBytes     FlexInt   `json:"port_15-tx_bytes,omitempty"` | ||||
| 		Port15RxBroadcast FlexInt   `json:"port_15-rx_broadcast"` | ||||
| 		Port15TxMulticast FlexInt   `json:"port_15-tx_multicast"` | ||||
| 		Port15TxBroadcast FlexInt   `json:"port_15-tx_broadcast"` | ||||
| 		Port16RxPackets   FlexInt   `json:"port_16-rx_packets,omitempty"` | ||||
| 		Port16RxBytes     FlexInt   `json:"port_16-rx_bytes,omitempty"` | ||||
| 		Port16TxPackets   FlexInt   `json:"port_16-tx_packets,omitempty"` | ||||
| 		Port16TxBytes     FlexInt   `json:"port_16-tx_bytes,omitempty"` | ||||
| 		Port16TxMulticast FlexInt   `json:"port_16-tx_multicast"` | ||||
| 		Port16TxBroadcast FlexInt   `json:"port_16-tx_broadcast"` | ||||
| 		Port17RxPackets   FlexInt   `json:"port_17-rx_packets,omitempty"` | ||||
| 		Port17RxBytes     FlexInt   `json:"port_17-rx_bytes,omitempty"` | ||||
| 		Port17TxPackets   FlexInt   `json:"port_17-tx_packets,omitempty"` | ||||
| 		Port17TxBytes     FlexInt   `json:"port_17-tx_bytes,omitempty"` | ||||
| 		Port17TxMulticast FlexInt   `json:"port_17-tx_multicast"` | ||||
| 		Port17TxBroadcast FlexInt   `json:"port_17-tx_broadcast"` | ||||
| 		Port18RxPackets   FlexInt   `json:"port_18-rx_packets,omitempty"` | ||||
| 		Port18RxBytes     FlexInt   `json:"port_18-rx_bytes,omitempty"` | ||||
| 		Port18TxPackets   FlexInt   `json:"port_18-tx_packets,omitempty"` | ||||
| 		Port18TxBytes     FlexInt   `json:"port_18-tx_bytes,omitempty"` | ||||
| 		Port18RxMulticast FlexInt   `json:"port_18-rx_multicast"` | ||||
| 		Port18TxMulticast FlexInt   `json:"port_18-tx_multicast"` | ||||
| 		Port18TxBroadcast FlexInt   `json:"port_18-tx_broadcast"` | ||||
| 		Port19RxPackets   FlexInt   `json:"port_19-rx_packets,omitempty"` | ||||
| 		Port19RxBytes     FlexInt   `json:"port_19-rx_bytes,omitempty"` | ||||
| 		Port19TxPackets   FlexInt   `json:"port_19-tx_packets,omitempty"` | ||||
| 		Port19TxBytes     FlexInt   `json:"port_19-tx_bytes,omitempty"` | ||||
| 		Port19TxMulticast FlexInt   `json:"port_19-tx_multicast"` | ||||
| 		Port19TxBroadcast FlexInt   `json:"port_19-tx_broadcast"` | ||||
| 		Port21RxPackets   FlexInt   `json:"port_21-rx_packets,omitempty"` | ||||
| 		Port21RxBytes     FlexInt   `json:"port_21-rx_bytes,omitempty"` | ||||
| 		Port21TxPackets   FlexInt   `json:"port_21-tx_packets,omitempty"` | ||||
| 		Port21TxBytes     FlexInt   `json:"port_21-tx_bytes,omitempty"` | ||||
| 		Port21RxBroadcast FlexInt   `json:"port_21-rx_broadcast"` | ||||
| 		Port21TxMulticast FlexInt   `json:"port_21-tx_multicast"` | ||||
| 		Port21TxBroadcast FlexInt   `json:"port_21-tx_broadcast"` | ||||
| 		Port22RxPackets   FlexInt   `json:"port_22-rx_packets,omitempty"` | ||||
| 		Port22RxBytes     FlexInt   `json:"port_22-rx_bytes,omitempty"` | ||||
| 		Port22TxPackets   FlexInt   `json:"port_22-tx_packets,omitempty"` | ||||
| 		Port22TxBytes     FlexInt   `json:"port_22-tx_bytes,omitempty"` | ||||
| 		Port22RxMulticast FlexInt   `json:"port_22-rx_multicast"` | ||||
| 		Port22TxMulticast FlexInt   `json:"port_22-tx_multicast"` | ||||
| 		Port22TxBroadcast FlexInt   `json:"port_22-tx_broadcast"` | ||||
| 		Port23RxPackets   FlexInt   `json:"port_23-rx_packets,omitempty"` | ||||
| 		Port23RxBytes     FlexInt   `json:"port_23-rx_bytes,omitempty"` | ||||
| 		Port23RxDropped   FlexInt   `json:"port_23-rx_dropped"` | ||||
| 		Port23TxPackets   FlexInt   `json:"port_23-tx_packets,omitempty"` | ||||
| 		Port23TxBytes     FlexInt   `json:"port_23-tx_bytes,omitempty"` | ||||
| 		Port23RxMulticast FlexInt   `json:"port_23-rx_multicast"` | ||||
| 		Port23RxBroadcast FlexInt   `json:"port_23-rx_broadcast"` | ||||
| 		Port23TxMulticast FlexInt   `json:"port_23-tx_multicast"` | ||||
| 		Port23TxBroadcast FlexInt   `json:"port_23-tx_broadcast"` | ||||
| 		Port24RxPackets   FlexInt   `json:"port_24-rx_packets,omitempty"` | ||||
| 		Port24RxBytes     FlexInt   `json:"port_24-rx_bytes,omitempty"` | ||||
| 		Port24TxPackets   FlexInt   `json:"port_24-tx_packets,omitempty"` | ||||
| 		Port24TxBytes     FlexInt   `json:"port_24-tx_bytes,omitempty"` | ||||
| 		Port24RxMulticast FlexInt   `json:"port_24-rx_multicast"` | ||||
| 		Port24TxMulticast FlexInt   `json:"port_24-tx_multicast"` | ||||
| 		Port24TxBroadcast FlexInt   `json:"port_24-tx_broadcast"` | ||||
| 		Port1RxMulticast  FlexInt   `json:"port_1-rx_multicast"` | ||||
| 		Port3RxDropped    FlexInt   `json:"port_3-rx_dropped"` | ||||
| 		Port3RxMulticast  FlexInt   `json:"port_3-rx_multicast"` | ||||
| 		Port6RxDropped    FlexInt   `json:"port_6-rx_dropped"` | ||||
| 		Port7RxDropped    FlexInt   `json:"port_7-rx_dropped"` | ||||
| 		Port7RxMulticast  FlexInt   `json:"port_7-rx_multicast"` | ||||
| 		Port9RxDropped    FlexInt   `json:"port_9-rx_dropped"` | ||||
| 		Port9RxMulticast  FlexInt   `json:"port_9-rx_multicast"` | ||||
| 		Port9RxBroadcast  FlexInt   `json:"port_9-rx_broadcast"` | ||||
| 		Port10RxBroadcast FlexInt   `json:"port_10-rx_broadcast"` | ||||
| 		Port12RxDropped   FlexInt   `json:"port_12-rx_dropped"` | ||||
| 		Port12RxMulticast FlexInt   `json:"port_12-rx_multicast"` | ||||
| 		Port13RxDropped   FlexInt   `json:"port_13-rx_dropped"` | ||||
| 		Port17RxDropped   FlexInt   `json:"port_17-rx_dropped"` | ||||
| 		Port17RxMulticast FlexInt   `json:"port_17-rx_multicast"` | ||||
| 		Port17RxBroadcast FlexInt   `json:"port_17-rx_broadcast"` | ||||
| 		Port19RxDropped   FlexInt   `json:"port_19-rx_dropped"` | ||||
| 		Port19RxMulticast FlexInt   `json:"port_19-rx_multicast"` | ||||
| 		Port19RxBroadcast FlexInt   `json:"port_19-rx_broadcast"` | ||||
| 		Port21RxDropped   FlexInt   `json:"port_21-rx_dropped"` | ||||
| 		Port21RxMulticast FlexInt   `json:"port_21-rx_multicast"` | ||||
| 		Port7RxBroadcast  FlexInt   `json:"port_7-rx_broadcast"` | ||||
| 		Port18RxBroadcast FlexInt   `json:"port_18-rx_broadcast"` | ||||
| 		Port16RxMulticast FlexInt   `json:"port_16-rx_multicast"` | ||||
| 		Port15RxDropped   FlexInt   `json:"port_15-rx_dropped"` | ||||
| 		Port15RxMulticast FlexInt   `json:"port_15-rx_multicast"` | ||||
| 		Port16RxBroadcast FlexInt   `json:"port_16-rx_broadcast"` | ||||
| 		Port11RxBroadcast FlexInt   `json:"port_11-rx_broadcast"` | ||||
| 		Port12RxBroadcast FlexInt   `json:"port_12-rx_broadcast"` | ||||
| 		Port6RxBroadcast  FlexInt   `json:"port_6-rx_broadcast"` | ||||
| 		Port24RxBroadcast FlexInt   `json:"port_24-rx_broadcast"` | ||||
| 		Port22RxBroadcast FlexInt   `json:"port_22-rx_broadcast"` | ||||
| 		Port10TxDropped   FlexInt   `json:"port_10-tx_dropped"` | ||||
| 		Port16TxDropped   FlexInt   `json:"port_16-tx_dropped"` | ||||
| 		Port1RxBroadcast  FlexInt   `json:"port_1-rx_broadcast"` | ||||
| 		Port4RxPackets    FlexInt   `json:"port_4-rx_packets,omitempty"` | ||||
| 		Port4RxBytes      FlexInt   `json:"port_4-rx_bytes,omitempty"` | ||||
| 		Port4RxDropped    FlexInt   `json:"port_4-rx_dropped"` | ||||
| 		Port4TxPackets    FlexInt   `json:"port_4-tx_packets,omitempty"` | ||||
| 		Port4TxBytes      FlexInt   `json:"port_4-tx_bytes,omitempty"` | ||||
| 		Port4TxDropped    FlexInt   `json:"port_4-tx_dropped"` | ||||
| 		Port4RxMulticast  FlexInt   `json:"port_4-rx_multicast"` | ||||
| 		Port4RxBroadcast  FlexInt   `json:"port_4-rx_broadcast"` | ||||
| 		Port4TxMulticast  FlexInt   `json:"port_4-tx_multicast"` | ||||
| 		Port4TxBroadcast  FlexInt   `json:"port_4-tx_broadcast"` | ||||
| 	*/ | ||||
| } | ||||
| 
 | ||||
| // UnmarshalJSON unmarshalls 5.10 or 5.11 formatted Switch Stat data.
 | ||||
| func (v *USWStat) UnmarshalJSON(data []byte) error { | ||||
| 	var n struct { | ||||
| 		Sw `json:"sw"` | ||||
| 	} | ||||
| 
 | ||||
| 	v.Sw = &n.Sw | ||||
| 
 | ||||
| 	err := json.Unmarshal(data, v.Sw) // controller version 5.10.
 | ||||
| 	if err != nil { | ||||
| 		return json.Unmarshal(data, &n) // controller version 5.11.
 | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | @ -0,0 +1,75 @@ | |||
| package unifi // nolint: testpackage
 | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
| 
 | ||||
| func testGetControllerJSON() (string, string) { | ||||
| 	return `{ | ||||
| 		"sw": { | ||||
| 		  "site_id": "mySite", | ||||
| 		  "o": "sw", | ||||
| 		  "oid": "00:00:00:00:00:00", | ||||
| 		  "sw": "00:00:00:00:00:00", | ||||
| 		  "time": 1577742600000, | ||||
| 		  "datetime": "2019-12-30T09:40:00Z", | ||||
| 		  "rx_packets": 321, | ||||
| 		  "rx_bytes": 321, | ||||
| 		  "rx_errors": 123, | ||||
| 		  "rx_dropped": 123, | ||||
| 		  "rx_crypts": 123, | ||||
| 		  "rx_frags": 123, | ||||
| 		  "tx_packets": 123, | ||||
| 		  "tx_bytes": 123, | ||||
| 		  "tx_errors": 0, | ||||
| 		  "tx_dropped": 0, | ||||
| 		  "tx_retries": 0, | ||||
| 		  "rx_multicast": 123, | ||||
| 		  "rx_broadcast": 123, | ||||
| 		  "tx_multicast": 123, | ||||
| 		  "tx_broadcast": 123, | ||||
| 		  "bytes": 123, | ||||
| 		  "duration": 123}}`, | ||||
| 		`{ | ||||
| 		"site_id": "mySite", | ||||
| 		"o": "sw", | ||||
| 		"oid": "00:00:00:00:00:00", | ||||
| 		"sw": "00:00:00:00:00:00", | ||||
| 		"time": 1577742600000, | ||||
| 		"datetime": "2019-12-30T09:40:00Z", | ||||
| 		"rx_packets": 321, | ||||
| 		"rx_bytes": 321, | ||||
| 		"rx_errors": 123, | ||||
| 		"rx_dropped": 123, | ||||
| 		"rx_crypts": 123, | ||||
| 		"rx_frags": 123, | ||||
| 		"tx_packets": 123, | ||||
| 		"tx_bytes": 123, | ||||
| 		"tx_errors": 0, | ||||
| 		"tx_dropped": 0, | ||||
| 		"tx_retries": 0, | ||||
| 		"rx_multicast": 123, | ||||
| 		"rx_broadcast": 123, | ||||
| 		"tx_multicast": 123, | ||||
| 		"tx_broadcast": 123, | ||||
| 		"bytes": 123, | ||||
| 		"duration": 123}` | ||||
| } | ||||
| 
 | ||||
| func TestUSWUnmarshalJSON(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	a := assert.New(t) | ||||
| 	testcontroller511, testcontroller510 := testGetControllerJSON() | ||||
| 	rxMulticast := 123 | ||||
| 	u := &USWStat{} | ||||
| 	err := u.UnmarshalJSON([]byte(testcontroller510)) | ||||
| 	a.Nil(err, "must be no error unmarshaling test strings") | ||||
| 	a.Equal(float64(rxMulticast), u.RxMulticast.Val, "data was not properly unmarshaled") | ||||
| 
 | ||||
| 	u = &USWStat{} // reset
 | ||||
| 	err = u.UnmarshalJSON([]byte(testcontroller511)) | ||||
| 	a.Nil(err, "must be no error unmarshaling test strings") | ||||
| 	a.Equal(float64(rxMulticast), u.RxMulticast.Val, "data was not properly unmarshaled") | ||||
| } | ||||
|  | @ -0,0 +1,158 @@ | |||
| package unifi | ||||
| 
 | ||||
| // UXG represents all the data from the Ubiquiti Controller for a UniFi 10Gb Gateway.
 | ||||
| // The UDM shares several structs/type-data with USW and USG.
 | ||||
| type UXG struct { | ||||
| 	site                       *Site | ||||
| 	SourceName                 string                  `json:"-"` | ||||
| 	SiteName                   string                  `json:"-"` | ||||
| 	ID                         string                  `json:"_id"` | ||||
| 	IP                         string                  `json:"ip"` | ||||
| 	Mac                        string                  `json:"mac"` | ||||
| 	Model                      string                  `json:"model"` | ||||
| 	ModelInLts                 FlexBool                `json:"model_in_lts"` | ||||
| 	ModelInEol                 FlexBool                `json:"model_in_eol"` | ||||
| 	Type                       string                  `json:"type"` | ||||
| 	Version                    string                  `json:"version"` | ||||
| 	Adopted                    FlexBool                `json:"adopted"` | ||||
| 	SiteID                     string                  `json:"site_id"` | ||||
| 	Cfgversion                 string                  `json:"cfgversion"` | ||||
| 	SyslogKey                  string                  `json:"syslog_key"` | ||||
| 	ConfigNetwork              *ConfigNetwork          `json:"config_network"` | ||||
| 	SetupID                    string                  `json:"setup_id"` | ||||
| 	LicenseState               string                  `json:"license_state"` | ||||
| 	ConfigNetworkLan           *ConfigNetworkLan       `json:"config_network_lan"` | ||||
| 	InformURL                  string                  `json:"inform_url"` | ||||
| 	InformIP                   string                  `json:"inform_ip"` | ||||
| 	RequiredVersion            string                  `json:"required_version"` | ||||
| 	KernelVersion              string                  `json:"kernel_version"` | ||||
| 	Architecture               string                  `json:"architecture"` | ||||
| 	BoardRev                   FlexInt                 `json:"board_rev"` | ||||
| 	ManufacturerID             FlexInt                 `json:"manufacturer_id"` | ||||
| 	Internet                   FlexBool                `json:"internet"` | ||||
| 	ModelIncompatible          FlexBool                `json:"model_incompatible"` | ||||
| 	EthernetTable              []*EthernetTable        `json:"ethernet_table"` | ||||
| 	PortTable                  []Port                  `json:"port_table"` | ||||
| 	EthernetOverrides          []*EthernetOverrides    `json:"ethernet_overrides"` | ||||
| 	UsgCaps                    FlexInt                 `json:"usg_caps"` | ||||
| 	HasSpeaker                 FlexBool                `json:"has_speaker"` | ||||
| 	HasEth1                    FlexBool                `json:"has_eth1"` | ||||
| 	FwCaps                     FlexInt                 `json:"fw_caps"` | ||||
| 	HwCaps                     FlexInt                 `json:"hw_caps"` | ||||
| 	WifiCaps                   FlexInt                 `json:"wifi_caps"` | ||||
| 	SwitchCaps                 *SwitchCaps             `json:"switch_caps"` | ||||
| 	HasFan                     FlexBool                `json:"has_fan"` | ||||
| 	HasTemperature             FlexBool                `json:"has_temperature"` | ||||
| 	Temperatures               []Temperature           `json:"temperatures"` | ||||
| 	Storage                    []*Storage              `json:"storage"` | ||||
| 	RulesetInterfaces          interface{}             `json:"ruleset_interfaces"` | ||||
| 	ConnectedAt                FlexInt                 `json:"connected_at"` | ||||
| 	ProvisionedAt              FlexInt                 `json:"provisioned_at"` | ||||
| 	LedOverride                string                  `json:"led_override"` | ||||
| 	LedOverrideColor           string                  `json:"led_override_color"` | ||||
| 	LedOverrideColorBrightness FlexInt                 `json:"led_override_color_brightness"` | ||||
| 	OutdoorModeOverride        string                  `json:"outdoor_mode_override"` | ||||
| 	LcmBrightnessOverride      FlexBool                `json:"lcm_brightness_override"` | ||||
| 	LcmIdleTimeoutOverride     FlexBool                `json:"lcm_idle_timeout_override"` | ||||
| 	Name                       string                  `json:"name"` | ||||
| 	Unsupported                FlexBool                `json:"unsupported"` | ||||
| 	UnsupportedReason          FlexInt                 `json:"unsupported_reason"` | ||||
| 	Serial                     string                  `json:"serial"` | ||||
| 	HashID                     string                  `json:"hash_id"` | ||||
| 	TwoPhaseAdopt              FlexBool                `json:"two_phase_adopt"` | ||||
| 	DeviceID                   string                  `json:"device_id"` | ||||
| 	State                      FlexInt                 `json:"state"` | ||||
| 	StartDisconnectedMillis    FlexInt                 `json:"start_disconnected_millis"` | ||||
| 	UpgradeState               FlexInt                 `json:"upgrade_state"` | ||||
| 	StartConnectedMillis       FlexInt                 `json:"start_connected_millis"` | ||||
| 	LastSeen                   FlexInt                 `json:"last_seen"` | ||||
| 	Uptime                     FlexInt                 `json:"uptime"` | ||||
| 	UnderscoreUptime           FlexInt                 `json:"_uptime"` | ||||
| 	Locating                   FlexBool                `json:"locating"` | ||||
| 	SysStats                   SysStats                `json:"sys_stats"` | ||||
| 	SystemStats                SystemStats             `json:"system-stats"` | ||||
| 	GuestKicks                 FlexInt                 `json:"guest_kicks"` | ||||
| 	GuestToken                 string                  `json:"guest_token"` | ||||
| 	UptimeStats                map[string]*UptimeStats `json:"uptime_stats"` | ||||
| 	Overheating                FlexBool                `json:"overheating"` | ||||
| 	GeoInfo                    map[string]*GeoInfo     `json:"geo_info"` | ||||
| 	LedState                   *LedState               `json:"led_state"` | ||||
| 	SpeedtestStatus            SpeedtestStatus         `json:"speedtest-status"` | ||||
| 	SpeedtestStatusSaved       FlexBool                `json:"speedtest-status-saved"` | ||||
| 	Wan1                       Wan                     `json:"wan1"` | ||||
| 	Wan2                       Wan                     `json:"wan2"` | ||||
| 	Uplink                     Uplink                  `json:"uplink"` | ||||
| 	DownlinkTable              []*DownlinkTable        `json:"downlink_table"` | ||||
| 	NetworkTable               NetworkTable            `json:"network_table"` | ||||
| 	KnownCfgversion            string                  `json:"known_cfgversion"` | ||||
| 	ConnectRequestIP           string                  `json:"connect_request_ip"` | ||||
| 	ConnectRequestPort         string                  `json:"connect_request_port"` | ||||
| 	NextInterval               FlexInt                 `json:"next_interval"` | ||||
| 	NextHeartbeatAt            FlexInt                 `json:"next_heartbeat_at"` | ||||
| 	ConsideredLostAt           FlexInt                 `json:"considered_lost_at"` | ||||
| 	Stat                       *UXGStat                `json:"stat"` | ||||
| 	TxBytes                    FlexInt                 `json:"tx_bytes"` | ||||
| 	RxBytes                    FlexInt                 `json:"rx_bytes"` | ||||
| 	Bytes                      FlexInt                 `json:"bytes"` | ||||
| 	NumSta                     FlexInt                 `json:"num_sta"` | ||||
| 	WlanNumSta                 FlexInt                 `json:"wlan-num_sta"` | ||||
| 	LanNumSta                  FlexInt                 `json:"lan-num_sta"` | ||||
| 	UserWlanNumSta             FlexInt                 `json:"user-wlan-num_sta"` | ||||
| 	UserLanNumSta              FlexInt                 `json:"user-lan-num_sta"` | ||||
| 	UserNumSta                 FlexInt                 `json:"user-num_sta"` | ||||
| 	GuestWlanNumSta            FlexInt                 `json:"guest-wlan-num_sta"` | ||||
| 	GuestLanNumSta             FlexInt                 `json:"guest-lan-num_sta"` | ||||
| 	GuestNumSta                FlexInt                 `json:"guest-num_sta"` | ||||
| 	NumDesktop                 FlexInt                 `json:"num_desktop"` | ||||
| 	NumMobile                  FlexInt                 `json:"num_mobile"` | ||||
| 	NumHandheld                FlexInt                 `json:"num_handheld"` | ||||
| } | ||||
| 
 | ||||
| // ConfigNetworkLan is part of a UXG, maybe others.
 | ||||
| type ConfigNetworkLan struct { | ||||
| 	DhcpEnabled FlexBool `json:"dhcp_enabled"` | ||||
| 	Vlan        int      `json:"vlan"` | ||||
| } | ||||
| 
 | ||||
| // DownlinkTable is part of a UXG and UDM output.
 | ||||
| type DownlinkTable struct { | ||||
| 	PortIdx    FlexInt  `json:"port_idx"` | ||||
| 	Speed      FlexInt  `json:"speed"` | ||||
| 	FullDuplex FlexBool `json:"full_duplex"` | ||||
| 	Mac        string   `json:"mac"` | ||||
| } | ||||
| 
 | ||||
| // LedState is incuded with newer devices.
 | ||||
| type LedState struct { | ||||
| 	Pattern string  `json:"pattern"` | ||||
| 	Tempo   FlexInt `json:"tempo"` | ||||
| } | ||||
| 
 | ||||
| // GeoInfo is incuded with certain devices.
 | ||||
| type GeoInfo struct { | ||||
| 	Accuracy        FlexInt `json:"accuracy"` | ||||
| 	Address         string  `json:"address"` | ||||
| 	Asn             FlexInt `json:"asn"` | ||||
| 	City            string  `json:"city"` | ||||
| 	ContinentCode   string  `json:"continent_code"` | ||||
| 	CountryCode     string  `json:"country_code"` | ||||
| 	CountryName     string  `json:"country_name"` | ||||
| 	IspName         string  `json:"isp_name"` | ||||
| 	IspOrganization string  `json:"isp_organization"` | ||||
| 	Latitude        FlexInt `json:"latitude"` | ||||
| 	Longitude       FlexInt `json:"longitude"` | ||||
| 	Timezone        string  `json:"timezone"` | ||||
| } | ||||
| 
 | ||||
| // UptimeStats is incuded with certain devices.
 | ||||
| type UptimeStats struct { | ||||
| 	Availability   FlexInt `json:"availability"` | ||||
| 	LatencyAverage FlexInt `json:"latency_average"` | ||||
| 	TimePeriod     FlexInt `json:"time_period"` | ||||
| } | ||||
| 
 | ||||
| // UXGStat holds the "stat" data for a 10Gb gateway.
 | ||||
| type UXGStat struct { | ||||
| 	*Gw `json:"gw"` | ||||
| 	*Sw `json:"sw"` | ||||
| } | ||||
		Loading…
	
		Reference in New Issue