Merge pull request #45 from unifi-poller/dn2_power_cycle

Add all Device Manager methods, original: Add ability to power cycle USW port
This commit is contained in:
David Newhall 2021-03-22 03:05:36 -07:00 committed by GitHub
commit 1fe69b42c9
9 changed files with 403 additions and 18 deletions

View File

@ -20,18 +20,89 @@ func (u *Unifi) GetDevices(sites []*Site) (*Devices, error) {
return nil, err
}
loopDevices := u.parseDevices(response.Data, site.SiteName)
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
}
// GetUSWs 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
}
// GetUSWs 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
}
// GetUSWs 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
}
// GetUSWs 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, siteName string) *Devices {
func (u *Unifi) parseDevices(data []json.RawMessage, site *Site) *Devices {
devices := new(Devices)
for _, r := range data {
@ -43,20 +114,20 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices {
}
assetType, _ := o["type"].(string)
u.DebugLog("Unmarshalling Device Type: %v, site %s ", assetType, siteName)
u.DebugLog("Unmarshalling Device Type: %v, site %s ", assetType, site.Name)
// Choose which type to unmarshal into based on the "type" json key.
switch assetType { // Unmarshal again into the correct type..
case "uap":
u.unmarshallUAP(siteName, r, devices)
u.unmarshallUAP(site, r, devices)
case "ugw", "usg": // in case they ever fix the name in the api.
u.unmarshallUSG(siteName, r, devices)
u.unmarshallUSG(site, r, devices)
case "usw":
u.unmarshallUSW(siteName, r, devices)
u.unmarshallUSW(site, r, devices)
case "udm":
u.unmarshallUDM(siteName, r, devices)
u.unmarshallUDM(site, r, devices)
case "uxg":
u.unmarshallUXG(siteName, r, devices)
u.unmarshallUXG(site, r, devices)
default:
u.ErrorLog("unknown asset type - %v - skipping", assetType)
}
@ -65,42 +136,47 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices {
return devices
}
func (u *Unifi) unmarshallUAP(siteName string, payload json.RawMessage, devices *Devices) {
dev := &UAP{SiteName: siteName, SourceName: u.URL}
func (u *Unifi) unmarshallUAP(site *Site, payload json.RawMessage, devices *Devices) {
dev := &UAP{SiteName: site.Name, 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(siteName string, payload json.RawMessage, devices *Devices) {
dev := &USG{SiteName: siteName, SourceName: u.URL}
func (u *Unifi) unmarshallUSG(site *Site, payload json.RawMessage, devices *Devices) {
dev := &USG{SiteName: site.Name, 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(siteName string, payload json.RawMessage, devices *Devices) {
dev := &USW{SiteName: siteName, SourceName: u.URL}
func (u *Unifi) unmarshallUSW(site *Site, payload json.RawMessage, devices *Devices) {
dev := &USW{SiteName: site.Name, 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(siteName string, payload json.RawMessage, devices *Devices) {
dev := &UXG{SiteName: siteName, SourceName: u.URL}
func (u *Unifi) unmarshallUXG(site *Site, payload json.RawMessage, devices *Devices) {
dev := &UXG{SiteName: site.Name, 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(siteName string, payload json.RawMessage, devices *Devices) {
dev := &UDM{SiteName: siteName, SourceName: u.URL}
func (u *Unifi) unmarshallUDM(site *Site, payload json.RawMessage, devices *Devices) {
dev := &UDM{SiteName: site.Name, 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)
}
}

299
core/unifi/devmgr.go Normal file
View File

@ -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
}

View File

@ -20,6 +20,8 @@ func (u *Unifi) GetSites() ([]*Site, error) {
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.
@ -67,6 +69,7 @@ func (u *Unifi) GetSiteDPI(sites []*Site) ([]*DPITable, error) {
// Site represents a site's data.
type Site struct {
controller *Unifi
SourceName string `json:"-"`
ID string `json:"_id"`
Name string `json:"name"`

View File

@ -44,6 +44,8 @@ const (
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.

View File

@ -9,6 +9,7 @@ import (
// 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"`

View File

@ -3,6 +3,7 @@ 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:"-"`

View File

@ -7,6 +7,7 @@ import (
// 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"`

View File

@ -7,6 +7,7 @@ import (
// 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"`

View File

@ -3,6 +3,7 @@ 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"`