You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							494 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							494 lines
						
					
					
						
							14 KiB
						
					
					
				
								package hue
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"encoding/json"
							 | 
						|
									"encoding/xml"
							 | 
						|
									"fmt"
							 | 
						|
									"git.aiterp.net/lucifer3/server/device"
							 | 
						|
									"git.aiterp.net/lucifer3/server/events"
							 | 
						|
									"git.aiterp.net/lucifer3/server/internal/color"
							 | 
						|
									"git.aiterp.net/lucifer3/server/internal/gentools"
							 | 
						|
									"strings"
							 | 
						|
									"time"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								type DeviceData struct {
							 | 
						|
									ID       string         `json:"id"`
							 | 
						|
									LegacyID string         `json:"id_v1"`
							 | 
						|
									Metadata DeviceMetadata `json:"metadata"`
							 | 
						|
									Type     string         `json:"type"`
							 | 
						|
								
							 | 
						|
									ProductData DeviceProductData `json:"product_data"`
							 | 
						|
									Services    []ResourceLink    `json:"services"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type DeviceMetadata struct {
							 | 
						|
									Archetype string `json:"archetype"`
							 | 
						|
									Name      string `json:"name"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type DeviceProductData struct {
							 | 
						|
									Certified        bool   `json:"certified"`
							 | 
						|
									ManufacturerName string `json:"manufacturer_name"`
							 | 
						|
									ModelID          string `json:"model_id"`
							 | 
						|
									ProductArchetype string `json:"product_archetype"`
							 | 
						|
									ProductName      string `json:"product_name"`
							 | 
						|
									SoftwareVersion  string `json:"software_version"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type SSEUpdate struct {
							 | 
						|
									CreationTime time.Time      `json:"creationTime"`
							 | 
						|
									ID           string         `json:"id"`
							 | 
						|
									Type         string         `json:"type"`
							 | 
						|
									Data         []ResourceData `json:"data"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type ResourceData struct {
							 | 
						|
									ID       string         `json:"id"`
							 | 
						|
									LegacyID string         `json:"id_v1"`
							 | 
						|
									Metadata DeviceMetadata `json:"metadata"`
							 | 
						|
									Type     string         `json:"type"`
							 | 
						|
								
							 | 
						|
									Mode *string `json:"mode"`
							 | 
						|
								
							 | 
						|
									Owner            *ResourceLink      `json:"owner"`
							 | 
						|
									ProductData      *DeviceProductData `json:"product_data"`
							 | 
						|
									Services         []ResourceLink     `json:"services"`
							 | 
						|
									Button           *SensorButton      `json:"button"`
							 | 
						|
									Power            *LightPower        `json:"on"`
							 | 
						|
									Color            *LightColor        `json:"color"`
							 | 
						|
									ColorTemperature *LightCT           `json:"color_temperature"`
							 | 
						|
									Dimming          *LightDimming      `json:"dimming"`
							 | 
						|
									Dynamics         *LightDynamics     `json:"dynamics"`
							 | 
						|
									Alert            *LightAlert        `json:"alert"`
							 | 
						|
									PowerState       *PowerState        `json:"power_state"`
							 | 
						|
									Temperature      *SensorTemperature `json:"temperature"`
							 | 
						|
									Motion           *SensorMotion      `json:"motion"`
							 | 
						|
									Status           *string            `json:"status"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (res *ResourceData) ServiceID(kind string) *string {
							 | 
						|
									for _, ptr := range res.Services {
							 | 
						|
										if ptr.Kind == kind {
							 | 
						|
											return &ptr.ID
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (res *ResourceData) ServiceIndex(kind string, id string) int {
							 | 
						|
									idx := 0
							 | 
						|
									for _, link := range res.Services {
							 | 
						|
										if link.ID == id {
							 | 
						|
											return idx
							 | 
						|
										} else if link.Kind == kind {
							 | 
						|
											idx += 1
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return -1
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (res *ResourceData) FixState(state device.State, resources map[string]*ResourceData) device.State {
							 | 
						|
									fixedState := device.State{}
							 | 
						|
									if lightID := res.ServiceID("light"); lightID != nil {
							 | 
						|
										if light := resources[*lightID]; light != nil {
							 | 
						|
											if state.Color != nil {
							 | 
						|
												if state.Color.IsKelvin() {
							 | 
						|
													if light.ColorTemperature != nil {
							 | 
						|
														mirek := 1000000 / *state.Color.K
							 | 
						|
														if mirek < light.ColorTemperature.MirekSchema.MirekMinimum {
							 | 
						|
															mirek = light.ColorTemperature.MirekSchema.MirekMinimum
							 | 
						|
														}
							 | 
						|
														if mirek > light.ColorTemperature.MirekSchema.MirekMaximum {
							 | 
						|
															mirek = light.ColorTemperature.MirekSchema.MirekMaximum
							 | 
						|
														}
							 | 
						|
								
							 | 
						|
														fixedState.Color = &color.Color{K: gentools.Ptr(1000000 / mirek)}
							 | 
						|
													}
							 | 
						|
												} else {
							 | 
						|
													if light.Color != nil {
							 | 
						|
														if col, ok := state.Color.ToXY(); ok {
							 | 
						|
															col.XY = gentools.Ptr(light.Color.Gamut.Conform(*col.XY))
							 | 
						|
															fixedState.Color = &col
							 | 
						|
														}
							 | 
						|
													}
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if state.Intensity != nil && light.Dimming != nil {
							 | 
						|
												fixedState.Intensity = gentools.ShallowCopy(state.Intensity)
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if state.Power != nil && light.Power != nil {
							 | 
						|
												fixedState.Power = gentools.ShallowCopy(state.Power)
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return fixedState
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (res *ResourceData) WithUpdate(update ResourceUpdate) *ResourceData {
							 | 
						|
									resCopy := *res
							 | 
						|
								
							 | 
						|
									if update.Name != nil {
							 | 
						|
										resCopy.Metadata.Name = *update.Name
							 | 
						|
									}
							 | 
						|
									if update.Power != nil {
							 | 
						|
										cp := *resCopy.Power
							 | 
						|
										resCopy.Power = &cp
							 | 
						|
										resCopy.Power.On = *update.Power
							 | 
						|
									}
							 | 
						|
									if update.ColorXY != nil {
							 | 
						|
										cp := *resCopy.Color
							 | 
						|
										resCopy.Color = &cp
							 | 
						|
										resCopy.Color.XY = *update.ColorXY
							 | 
						|
								
							 | 
						|
										if resCopy.ColorTemperature != nil {
							 | 
						|
											cp := *resCopy.ColorTemperature
							 | 
						|
											resCopy.ColorTemperature = &cp
							 | 
						|
											resCopy.ColorTemperature.Mirek = nil
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									if update.Mirek != nil {
							 | 
						|
										cp := *resCopy.ColorTemperature
							 | 
						|
										resCopy.ColorTemperature = &cp
							 | 
						|
										mirek := *update.Mirek
							 | 
						|
										resCopy.ColorTemperature.Mirek = &mirek
							 | 
						|
									}
							 | 
						|
									if update.Brightness != nil {
							 | 
						|
										cp := *resCopy.Dimming
							 | 
						|
										resCopy.Dimming = &cp
							 | 
						|
										resCopy.Dimming.Brightness = *update.Brightness
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &resCopy
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (res *ResourceData) WithPatch(patch ResourceData) *ResourceData {
							 | 
						|
									res2 := *res
							 | 
						|
								
							 | 
						|
									if patch.Color != nil {
							 | 
						|
										res2.Color = gentools.ShallowCopy(res2.Color)
							 | 
						|
										res2.Color.XY = patch.Color.XY
							 | 
						|
									}
							 | 
						|
									if patch.ColorTemperature != nil {
							 | 
						|
										res2.ColorTemperature = gentools.ShallowCopy(res2.ColorTemperature)
							 | 
						|
										res2.ColorTemperature.Mirek = gentools.ShallowCopy(patch.ColorTemperature.Mirek)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									gentools.ShallowCopyTo(&res2.ProductData, patch.ProductData)
							 | 
						|
									gentools.ShallowCopyTo(&res2.Button, patch.Button)
							 | 
						|
									gentools.ShallowCopyTo(&res2.Power, patch.Power)
							 | 
						|
									gentools.ShallowCopyTo(&res2.Dimming, patch.Dimming)
							 | 
						|
									gentools.ShallowCopyTo(&res2.Dynamics, patch.Dynamics)
							 | 
						|
									gentools.ShallowCopyTo(&res2.Alert, patch.Alert)
							 | 
						|
									gentools.ShallowCopyTo(&res2.PowerState, patch.PowerState)
							 | 
						|
									gentools.ShallowCopyTo(&res2.Temperature, patch.Temperature)
							 | 
						|
									gentools.ShallowCopyTo(&res2.Motion, patch.Motion)
							 | 
						|
									gentools.ShallowCopyTo(&res2.Status, patch.Status)
							 | 
						|
								
							 | 
						|
									return &res2
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (res *ResourceData) GenerateEvent(hostname string, resources map[string]*ResourceData) (events.HardwareState, events.HardwareMetadata) {
							 | 
						|
									hwState := events.HardwareState{
							 | 
						|
										ID:           fmt.Sprintf("hue:%s:%s", hostname, res.ID),
							 | 
						|
										InternalName: res.Metadata.Name,
							 | 
						|
										State:        res.GenerateState(resources),
							 | 
						|
										Unreachable:  false,
							 | 
						|
									}
							 | 
						|
									hwMeta := events.HardwareMetadata{
							 | 
						|
										ID: hwState.ID,
							 | 
						|
									}
							 | 
						|
									buttonCount := 0
							 | 
						|
								
							 | 
						|
									switch res.ProductData.ProductArchetype {
							 | 
						|
									case "candle_bulb":
							 | 
						|
										hwMeta.Icon = "hue_lightbulb_e14"
							 | 
						|
									case "sultan_bulb":
							 | 
						|
										hwMeta.Icon = "hue_lightbulb_e27"
							 | 
						|
									case "hue_signe":
							 | 
						|
										hwMeta.Icon = "hue_signe"
							 | 
						|
									case "unknown_archetype":
							 | 
						|
										switch res.ProductData.ProductName {
							 | 
						|
										case "Hue motion sensor":
							 | 
						|
											hwMeta.Icon = "hue_motionsensor"
							 | 
						|
										case "Hue dimmer switch":
							 | 
						|
											hwMeta.Icon = "hue_dimmerswitch"
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if res.ProductData != nil {
							 | 
						|
										hwMeta.FirmwareVersion = res.ProductData.SoftwareVersion
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, ptr := range res.Services {
							 | 
						|
										svc := resources[ptr.ID]
							 | 
						|
										if svc == nil {
							 | 
						|
											continue // I've never seen this happen, but safety first.
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										switch ptr.Kind {
							 | 
						|
										case "zigbee_connectivity":
							 | 
						|
											switch *svc.Status {
							 | 
						|
											case "connectivity_issue":
							 | 
						|
												hwState.Unreachable = true
							 | 
						|
											}
							 | 
						|
										case "device_power":
							 | 
						|
											hwState.BatteryPercentage = gentools.Ptr(int(svc.PowerState.BatteryLevel))
							 | 
						|
										case "button":
							 | 
						|
											buttonCount += 1
							 | 
						|
											hwState.SupportFlags |= device.SFlagSensorButtons
							 | 
						|
										case "motion":
							 | 
						|
											hwState.SupportFlags |= device.SFlagSensorPresence
							 | 
						|
										case "temperature":
							 | 
						|
											hwState.SupportFlags |= device.SFlagSensorTemperature
							 | 
						|
										case "light":
							 | 
						|
											if svc.Power != nil {
							 | 
						|
												hwState.SupportFlags |= device.SFlagPower
							 | 
						|
											}
							 | 
						|
											if svc.Dimming != nil {
							 | 
						|
												hwState.SupportFlags |= device.SFlagIntensity
							 | 
						|
											}
							 | 
						|
											if svc.ColorTemperature != nil {
							 | 
						|
												hwState.SupportFlags |= device.SFlagColor
							 | 
						|
												hwState.ColorFlags |= device.CFlagKelvin
							 | 
						|
												if svc.ColorTemperature.MirekSchema.MirekMinimum != 0 {
							 | 
						|
													hwState.ColorKelvinRange = &[2]int{
							 | 
						|
														1000000 / svc.ColorTemperature.MirekSchema.MirekMinimum,
							 | 
						|
														1000000 / svc.ColorTemperature.MirekSchema.MirekMaximum,
							 | 
						|
													}
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
											if svc.Color != nil {
							 | 
						|
												hwState.SupportFlags |= device.SFlagColor
							 | 
						|
												hwState.ColorFlags |= device.CFlagXY
							 | 
						|
												hwState.ColorGamut = gentools.Ptr(svc.Color.Gamut)
							 | 
						|
												hwState.ColorGamut.Label = svc.Color.GamutType
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if buttonCount == 4 {
							 | 
						|
										hwState.Buttons = []string{"On", "DimUp", "DimDown", "Off"}
							 | 
						|
									} else if buttonCount == 1 {
							 | 
						|
										hwState.Buttons = []string{"Button"}
							 | 
						|
									} else {
							 | 
						|
										for n := 1; n <= buttonCount; n++ {
							 | 
						|
											hwState.Buttons = append(hwState.Buttons, fmt.Sprint("Button", n))
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return hwState, hwMeta
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (res *ResourceData) GenerateState(resources map[string]*ResourceData) device.State {
							 | 
						|
									state := device.State{}
							 | 
						|
									for _, ptr := range res.Services {
							 | 
						|
										switch ptr.Kind {
							 | 
						|
										case "light":
							 | 
						|
											light := resources[ptr.ID]
							 | 
						|
											if light == nil {
							 | 
						|
												continue
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if light.Power != nil {
							 | 
						|
												state.Power = gentools.Ptr(light.Power.On)
							 | 
						|
											}
							 | 
						|
											if light.Dimming != nil {
							 | 
						|
												state.Intensity = gentools.Ptr(light.Dimming.Brightness / 100.0)
							 | 
						|
											}
							 | 
						|
											if light.ColorTemperature != nil {
							 | 
						|
												if light.ColorTemperature.Mirek != nil {
							 | 
						|
													state.Color = &color.Color{K: gentools.Ptr(1000000 / *light.ColorTemperature.Mirek)}
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
											if light.Color != nil {
							 | 
						|
												if state.Color == nil || state.Color.IsEmpty() {
							 | 
						|
													state.Color = &color.Color{XY: &light.Color.XY}
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return state
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type SensorButton struct {
							 | 
						|
									LastEvent string `json:"last_event"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type SensorMotion struct {
							 | 
						|
									Motion bool `json:"motion"`
							 | 
						|
									Valid  bool `json:"motion_valid"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type SensorTemperature struct {
							 | 
						|
									Temperature float64 `json:"temperature"`
							 | 
						|
									Valid       bool    `json:"temperature_valid"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type PowerState struct {
							 | 
						|
									BatteryState string  `json:"battery_state"`
							 | 
						|
									BatteryLevel float64 `json:"battery_level"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type LightPower struct {
							 | 
						|
									On bool `json:"on"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type LightDimming struct {
							 | 
						|
									Brightness float64 `json:"brightness"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type LightColor struct {
							 | 
						|
									Gamut     color.Gamut `json:"gamut"`
							 | 
						|
									GamutType string      `json:"gamut_type"`
							 | 
						|
									XY        color.XY    `json:"xy"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type LightCT struct {
							 | 
						|
									Mirek       *int               `json:"mirek"`
							 | 
						|
									MirekSchema LightCTMirekSchema `json:"mirek_schema"`
							 | 
						|
									MirekValid  bool               `json:"mirek_valid"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type LightCTMirekSchema struct {
							 | 
						|
									MirekMaximum int `json:"mirek_maximum"`
							 | 
						|
									MirekMinimum int `json:"mirek_minimum"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type LightDynamics struct {
							 | 
						|
									Speed        float64  `json:"speed"`
							 | 
						|
									SpeedValid   bool     `json:"speed_valid"`
							 | 
						|
									Status       string   `json:"status"`
							 | 
						|
									StatusValues []string `json:"status_values"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type LightAlert struct {
							 | 
						|
									ActionValues []string `json:"action_values"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type ResourceUpdate struct {
							 | 
						|
									Name               *string
							 | 
						|
									Power              *bool
							 | 
						|
									ColorXY            *color.XY
							 | 
						|
									Brightness         *float64
							 | 
						|
									Mirek              *int
							 | 
						|
									TransitionDuration *time.Duration
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (r ResourceUpdate) Split() []ResourceUpdate {
							 | 
						|
									updates := make([]ResourceUpdate, 0, 2)
							 | 
						|
									if r.Name != nil {
							 | 
						|
										updates = append(updates, ResourceUpdate{Name: r.Name, TransitionDuration: r.TransitionDuration})
							 | 
						|
									}
							 | 
						|
									if r.Power != nil {
							 | 
						|
										updates = append(updates, ResourceUpdate{Power: r.Power, TransitionDuration: r.TransitionDuration})
							 | 
						|
									}
							 | 
						|
									if r.ColorXY != nil {
							 | 
						|
										updates = append(updates, ResourceUpdate{ColorXY: r.ColorXY, TransitionDuration: r.TransitionDuration})
							 | 
						|
									}
							 | 
						|
									if r.Brightness != nil {
							 | 
						|
										updates = append(updates, ResourceUpdate{Brightness: r.Brightness, TransitionDuration: r.TransitionDuration})
							 | 
						|
									}
							 | 
						|
									if r.Mirek != nil {
							 | 
						|
										updates = append(updates, ResourceUpdate{Mirek: r.Mirek, TransitionDuration: r.TransitionDuration})
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return updates
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (r ResourceUpdate) MarshalJSON() ([]byte, error) {
							 | 
						|
									chunks := make([]string, 0, 4)
							 | 
						|
									if r.Name != nil {
							 | 
						|
										s, _ := json.Marshal(*r.Name)
							 | 
						|
										chunks = append(chunks, fmt.Sprintf(`"metadata":{"name":%s}`, string(s)))
							 | 
						|
									}
							 | 
						|
									if r.Power != nil {
							 | 
						|
										chunks = append(chunks, fmt.Sprintf(`"on":{"on":%v}`, *r.Power))
							 | 
						|
									}
							 | 
						|
									if r.ColorXY != nil {
							 | 
						|
										chunks = append(chunks, fmt.Sprintf(`"color":{"xy":{"x":%f,"y":%f}}`, r.ColorXY.X, r.ColorXY.Y))
							 | 
						|
									}
							 | 
						|
									if r.Brightness != nil {
							 | 
						|
										chunks = append(chunks, fmt.Sprintf(`"dimming":{"brightness":%f}`, *r.Brightness))
							 | 
						|
									}
							 | 
						|
									if r.Mirek != nil {
							 | 
						|
										chunks = append(chunks, fmt.Sprintf(`"color_temperature":{"mirek":%d}`, *r.Mirek))
							 | 
						|
									}
							 | 
						|
									if r.TransitionDuration != nil {
							 | 
						|
										chunks = append(chunks, fmt.Sprintf(`"dynamics":{"duration":%d}`, r.TransitionDuration.Truncate(time.Millisecond*100).Milliseconds()))
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return []byte(fmt.Sprintf("{%s}", strings.Join(chunks, ","))), nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type ResourceLink struct {
							 | 
						|
									ID   string `json:"rid"`
							 | 
						|
									Kind string `json:"rtype"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (rl *ResourceLink) Path() string {
							 | 
						|
									return fmt.Sprintf("/clip/v2/resource/%s/%s", rl.Kind, rl.ID)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type CreateUserInput struct {
							 | 
						|
									DeviceType string `json:"devicetype"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type CreateUserResponse struct {
							 | 
						|
									Success *struct {
							 | 
						|
										Username string `json:"username"`
							 | 
						|
									} `json:"success"`
							 | 
						|
									Error *struct {
							 | 
						|
										Type        int    `json:"type"`
							 | 
						|
										Address     string `json:"address"`
							 | 
						|
										Description string `json:"description"`
							 | 
						|
									} `json:"error"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type DiscoveryEntry struct {
							 | 
						|
									Id                string `json:"id"`
							 | 
						|
									InternalIPAddress string `json:"internalipaddress"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type BridgeDeviceInfo struct {
							 | 
						|
									XMLName     xml.Name `xml:"root"`
							 | 
						|
									Text        string   `xml:",chardata"`
							 | 
						|
									Xmlns       string   `xml:"xmlns,attr"`
							 | 
						|
									SpecVersion struct {
							 | 
						|
										Text  string `xml:",chardata"`
							 | 
						|
										Major string `xml:"major"`
							 | 
						|
										Minor string `xml:"minor"`
							 | 
						|
									} `xml:"specVersion"`
							 | 
						|
									URLBase string `xml:"URLBase"`
							 | 
						|
									Device  struct {
							 | 
						|
										Text             string `xml:",chardata"`
							 | 
						|
										DeviceType       string `xml:"deviceType"`
							 | 
						|
										FriendlyName     string `xml:"friendlyName"`
							 | 
						|
										Manufacturer     string `xml:"manufacturer"`
							 | 
						|
										ManufacturerURL  string `xml:"manufacturerURL"`
							 | 
						|
										ModelDescription string `xml:"modelDescription"`
							 | 
						|
										ModelName        string `xml:"modelName"`
							 | 
						|
										ModelNumber      string `xml:"modelNumber"`
							 | 
						|
										ModelURL         string `xml:"modelURL"`
							 | 
						|
										SerialNumber     string `xml:"serialNumber"`
							 | 
						|
										UDN              string `xml:"UDN"`
							 | 
						|
										PresentationURL  string `xml:"presentationURL"`
							 | 
						|
										IconList         struct {
							 | 
						|
											Text string `xml:",chardata"`
							 | 
						|
											Icon struct {
							 | 
						|
												Text     string `xml:",chardata"`
							 | 
						|
												Mimetype string `xml:"mimetype"`
							 | 
						|
												Height   string `xml:"height"`
							 | 
						|
												Width    string `xml:"width"`
							 | 
						|
												Depth    string `xml:"depth"`
							 | 
						|
												URL      string `xml:"url"`
							 | 
						|
											} `xml:"icon"`
							 | 
						|
										} `xml:"iconList"`
							 | 
						|
									} `xml:"device"`
							 | 
						|
								}
							 |