diff --git a/Gopkg.lock b/Gopkg.lock index 1d22cfd..ff3bfe2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -18,6 +18,12 @@ revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" +[[projects]] + name = "github.com/gorilla/schema" + packages = ["."] + revision = "b0e8c201c781a2ff27702d53885093aefb15b623" + version = "v1.0.1" + [[projects]] name = "github.com/jessevdk/go-flags" packages = ["."] @@ -35,11 +41,17 @@ version = "v1.0.0" [[projects]] - name = "github.com/qmsk/go-web" + name = "github.com/qmsk/go-logging" packages = ["."] - revision = "b8620920c43c463bea270aa53914b6918adc2bac" + revision = "69543e686d5be4cd70a1628e49fbca86308e3fcb" version = "v0.1.0" +[[projects]] + name = "github.com/qmsk/go-web" + packages = ["."] + revision = "0eebe52e538250744b98dc31e840bc5344be567b" + version = "v0.2.0" + [[projects]] name = "github.com/stretchr/objx" packages = ["."] @@ -69,6 +81,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "0aed30493f39d1c5159c221532ae7800809447a98673542e0739326c34aed5ba" + inputs-digest = "9bdc9ed0abd6bd567112741fa34fe653296550a056fb882dde949c6b7c824e33" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 01e1ad1..767814e 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -39,4 +39,4 @@ [[constraint]] name = "github.com/qmsk/go-web" - version = "0.1.0" + version = "~0.2.0" diff --git a/api/channel.go b/api/channel.go new file mode 100644 index 0000000..c14fba5 --- /dev/null +++ b/api/channel.go @@ -0,0 +1,51 @@ +package api + +type ChannelColor string + +const ( + ChannelColorRed = "red" + ChannelColorGreen = "green" + ChannelColorBlue = "blue" +) + +type ChannelConfig struct { + Control string `json:",omitempty"` + Intensity bool `json:",omitempty"` + Color ChannelColor `json:",omitempty"` +} + +func (config ChannelConfig) String() string { + if config.Control != "" { + return "control:" + config.Control + } + if config.Intensity { + return "intensity" + } + if config.Color != "" { + return "color:" + string(config.Color) + } + + return "" +} + +func (config ChannelConfig) ID() ChannelID { + return ChannelID(config.String()) +} + +type ChannelID string +type Channels map[ChannelID]Channel + +type Channel struct { + ID ChannelID + Config ChannelConfig + Index uint + Address DMXAddress + + DMX DMXValue + Value Value +} + +type ChannelParams struct { + DMX *DMXValue `json:",omitempty"` + Value *Value `json:",omitempty"` +} diff --git a/api/color.go b/api/color.go new file mode 100644 index 0000000..6288409 --- /dev/null +++ b/api/color.go @@ -0,0 +1,44 @@ +package api + +type ColorID string + +type Colors map[ColorID]Color + +// Merge in colors from given map, overriding colors in this map +func (colors Colors) Merge(merge Colors) Colors { + var merged = make(Colors) + + for colorID, color := range colors { + merged[colorID] = color + } + + for colorID, color := range merge { + merged[colorID] = color + } + + return merged +} + +type Color struct { + Red Value + Green Value + Blue Value +} + +func (color Color) IsZero() bool { + return color.Red == 0.0 && color.Green == 0.0 && color.Blue == 0.0 +} + +// Linear RGB intensity scaling +func (color Color) Scale(intensity Value) Color { + return Color{ + Red: color.Red * intensity, + Green: color.Green * intensity, + Blue: color.Blue * intensity, + } +} + +type ColorParams struct { + ScaleIntensity *Value + Color +} diff --git a/api/config.go b/api/config.go new file mode 100644 index 0000000..b014f60 --- /dev/null +++ b/api/config.go @@ -0,0 +1,11 @@ +package api + +type Config struct { + HeadTypes map[TypeID]HeadType + Colors Colors + + Heads map[HeadID]HeadConfig + Groups map[GroupID]GroupConfig + + Presets map[PresetID]PresetConfig +} diff --git a/api/event.go b/api/event.go new file mode 100644 index 0000000..ec6bfd6 --- /dev/null +++ b/api/event.go @@ -0,0 +1,7 @@ +package api + +type Event struct { + Outputs Outputs + Heads Heads + Groups Groups +} diff --git a/api/group.go b/api/group.go new file mode 100644 index 0000000..3c3b72f --- /dev/null +++ b/api/group.go @@ -0,0 +1,25 @@ +package api + +type GroupConfig struct { + Heads []HeadID + Name string +} + +type GroupID string + +type Groups map[GroupID]Group + +type Group struct { + GroupConfig // TODO: separate config field + ID GroupID + Heads []HeadID + Colors Colors + + Intensity *Intensity + Color *Color +} + +type GroupParams struct { + Intensity *IntensityParams `json:",omitempty"` + Color *ColorParams `json:",omitempty"` +} diff --git a/api/head.go b/api/head.go new file mode 100644 index 0000000..ec71550 --- /dev/null +++ b/api/head.go @@ -0,0 +1,49 @@ +package api + +import ( + "fmt" +) + +type TypeID string +type HeadType struct { + Vendor string + Model string + Mode string + URL string + + Channels []ChannelConfig + Colors Colors +} + +func (headType HeadType) String() string { + return fmt.Sprintf("%v/%v=%v", headType.Vendor, headType.Model, headType.Mode) +} + +type HeadConfig struct { + Type TypeID + Universe Universe + Address DMXAddress + Name string + Count uint // Clone multiple copies of the head at id.N + Groups []GroupID +} + +type HeadID string + +type Heads map[HeadID]Head + +type Head struct { + ID HeadID + Config HeadConfig + Type HeadType + + Channels Channels `json:",omitempty"` + Intensity *Intensity `json:",omitempty"` + Color *Color `json:",omitempty"` +} + +type HeadParams struct { + Channels map[ChannelID]ChannelParams `json:",omitempty"` + Intensity *IntensityParams `json:",omitempty"` + Color *ColorParams `json:",omitempty"` +} diff --git a/api/index.go b/api/index.go new file mode 100644 index 0000000..a6ef08c --- /dev/null +++ b/api/index.go @@ -0,0 +1,8 @@ +package api + +type Index struct { + Outputs Outputs + Heads Heads + Groups Groups + Presets Presets +} diff --git a/api/intensity.go b/api/intensity.go new file mode 100644 index 0000000..65bdc8d --- /dev/null +++ b/api/intensity.go @@ -0,0 +1,14 @@ +package api + +type Intensity struct { + Intensity Value +} + +func (intensity Intensity) Scale(scale Value) Intensity { + return Intensity{Intensity: intensity.Intensity * scale} +} + +type IntensityParams struct { + ScaleIntensity *Value + Intensity +} diff --git a/api/output.go b/api/output.go new file mode 100644 index 0000000..96e4d95 --- /dev/null +++ b/api/output.go @@ -0,0 +1,24 @@ +package api + +import ( + "time" +) + +type OutputStatus struct { + Seen time.Time + Address string + Port int + + Artnet interface{} // metadata +} + +type OutputID string +type Outputs map[OutputID]Output + +type Output struct { + ID OutputID + Universe Universe + Connected *time.Time + + OutputStatus +} diff --git a/api/preset.go b/api/preset.go new file mode 100644 index 0000000..2f7c8f3 --- /dev/null +++ b/api/preset.go @@ -0,0 +1,52 @@ +package api + +type PresetConfigParams struct { + Intensity *IntensityParams + Color *ColorParams +} + +func (params PresetConfigParams) Scale(scale Value) PresetConfigParams { + var ret PresetConfigParams + + if params.Intensity != nil { + var intensity = *params.Intensity + + ret.Intensity = &intensity + ret.Intensity.ScaleIntensity = &scale + } + + if params.Color != nil { + var color = *params.Color + + ret.Color = &color + ret.Color.ScaleIntensity = &scale + } + + return ret +} + +type PresetGroups map[GroupID]PresetConfigParams +type PresetHeads map[HeadID]PresetConfigParams + +type PresetConfig struct { + Name string + All *PresetConfigParams + Groups PresetGroups + Heads PresetHeads +} + +type PresetID string +type Presets map[PresetID]Preset + +type Preset struct { + ID PresetID + Config PresetConfig + + // TODO: superfluous re config? + Groups PresetGroups + Heads PresetHeads +} + +type PresetParams struct { + Intensity *Value // TODO: rename to Scale +} diff --git a/api/types.go b/api/types.go new file mode 100644 index 0000000..74f3a52 --- /dev/null +++ b/api/types.go @@ -0,0 +1,6 @@ +package api + +type Universe int +type DMXAddress int +type DMXValue uint8 +type Value float64 diff --git a/heads/channel.go b/heads/channel.go index f7c48ac..90f0270 100644 --- a/heads/channel.go +++ b/heads/channel.go @@ -2,33 +2,43 @@ package heads import ( "github.com/qmsk/dmx" + "github.com/qmsk/dmx/api" "github.com/qmsk/go-web" ) -type ChannelType struct { - Control string `json:",omitempty"` - Intensity bool `json:",omitempty"` - Color ColorChannel `json:",omitempty"` -} +// Channels +type channels map[api.ChannelID]*Channel -func (channelType ChannelType) String() string { - if channelType.Control != "" { - return "control:" + channelType.Control - } - if channelType.Intensity { - return "intensity" - } - if channelType.Color != "" { - return "color:" + string(channelType.Color) +func (channels channels) makeAPI() api.Channels { + var apiChannels = make(api.Channels) + + for id, channel := range channels { + apiChannels[id] = channel.GetChannel() } - return "" + return apiChannels +} + +type channelsView struct { + channels channels +} + +func (view channelsView) Index(name string) (web.Resource, error) { + if channel := view.channels[api.ChannelID(name)]; channel == nil { + return nil, nil + } else { + return channel, nil + } +} +func (view channelsView) GetREST() (web.Resource, error) { + return view.channels.makeAPI(), nil } type Channel struct { - channelType ChannelType - output *Output - index uint + id api.ChannelID + config api.ChannelConfig + output *Output + index uint address dmx.Address } @@ -37,82 +47,56 @@ func (channel *Channel) init() { channel.output.SetDMX(channel.address, 0) } -func (channel *Channel) GetDMX() dmx.Channel { - return channel.output.GetDMX(channel.address) +func (channel *Channel) GetDMX() api.DMXValue { + return api.DMXValue(channel.output.GetDMX(channel.address)) } -func (channel *Channel) GetValue() Value { - return channel.output.GetValue(channel.address) +func (channel *Channel) GetValue() api.Value { + return api.Value(channel.output.GetValue(channel.address)) } -func (channel *Channel) SetDMX(value dmx.Channel) { - channel.output.SetDMX(channel.address, value) +func (channel *Channel) SetDMX(value api.DMXValue) { + channel.output.SetDMX(channel.address, dmx.Channel(value)) } -func (channel *Channel) SetValue(value Value) Value { - return channel.output.SetValue(channel.address, value) +func (channel *Channel) SetValue(value api.Value) api.Value { + return api.Value(channel.output.SetValue(channel.address, Value(value))) } -// Web API -type APIChannel struct { - channel *Channel - - ID string - Type ChannelType - Index uint - Address dmx.Address - - DMX dmx.Channel - Value Value +func (channel *Channel) SetChannel(params api.ChannelParams) { + if params.DMX != nil { + channel.SetDMX(*params.DMX) + } + if params.Value != nil { + channel.SetValue(*params.Value) + } } -func (channel *Channel) makeAPI() APIChannel { - return APIChannel{ - channel: channel, - ID: channel.channelType.String(), +func (channel *Channel) GetChannel() api.Channel { + return api.Channel{ + ID: channel.id, Index: channel.index, - Type: channel.channelType, - Address: channel.address, + Config: channel.config, + Address: api.DMXAddress(channel.address), DMX: channel.GetDMX(), Value: channel.GetValue(), } } -func (channel *Channel) GetREST() (web.Resource, error) { - return channel.makeAPI(), nil -} - -type APIChannels map[string]APIChannel - -func (apiChannels APIChannels) GetREST() (web.Resource, error) { - return apiChannels, nil -} - -type APIChannelParams struct { +// API +type channelView struct { channel *Channel - - DMX *dmx.Channel `json:",omitempty"` - Value *Value `json:",omitempty"` + params api.ChannelParams } -func (channel *Channel) PostREST() (web.Resource, error) { - return &APIChannelParams{channel: channel}, nil +func (view *channelView) GetREST() (web.Resource, error) { + return view.channel.GetChannel(), nil } -func (params *APIChannelParams) Apply() error { - if params.DMX != nil { - params.channel.SetDMX(*params.DMX) - } - if params.Value != nil { - *params.Value = params.channel.SetValue(*params.Value) - } +func (view *channelView) IntoREST() interface{} { + return &view.params +} - if params.DMX == nil { - var dmxValue = params.channel.GetDMX() - params.DMX = &dmxValue - } - if params.Value == nil { - var value = params.channel.GetValue() - params.Value = &value - } +func (view *channelView) PostREST() (web.Resource, error) { + view.channel.SetChannel(view.params) - return nil + return view.channel.GetChannel(), nil } diff --git a/heads/color.go b/heads/color.go index bb63467..34ead15 100644 --- a/heads/color.go +++ b/heads/color.go @@ -1,52 +1,13 @@ package heads import ( - "fmt" + "github.com/qmsk/dmx/api" "github.com/qmsk/go-web" ) -// Config Channels -type ColorChannel string - -const ( - ColorChannelRed = "red" - ColorChannelGreen = "green" - ColorChannelBlue = "blue" -) - -// Config -type ColorID string - -type ColorMap map[ColorID]Color - -// Merge in new colors from given map -// Preserves existing colors -func (colorMap ColorMap) Merge(mergeMap ColorMap) { - for colorID, color := range mergeMap { - if _, exists := colorMap[colorID]; !exists { - colorMap[colorID] = color - } - } -} - -// Types -type Color struct { - Red Value - Green Value - Blue Value -} - -func (color Color) IsZero() bool { - return color.Red == 0.0 && color.Green == 0.0 && color.Blue == 0.0 -} - -// Linear RGB intensity scaling -func (color Color) ScaleIntensity(intensity Intensity) Color { - return Color{ - Red: color.Red * Value(intensity), - Green: color.Green * Value(intensity), - Blue: color.Blue * Value(intensity), - } +type ColorHandler interface { + GetColor() api.Color + SetColor(api.ColorParams) api.Color } // Head.Color @@ -61,7 +22,7 @@ func (it HeadColor) exists() bool { return it.red != nil || it.green != nil || it.blue != nil } -func (hc HeadColor) Get() (color Color) { +func (hc HeadColor) GetColor() (color api.Color) { if hc.red != nil { color.Red = hc.red.GetValue() } @@ -74,7 +35,7 @@ func (hc HeadColor) Get() (color Color) { return } -func (hc HeadColor) Set(color Color) Color { +func (hc HeadColor) setColor(color api.Color) api.Color { if hc.red != nil { color.Red = hc.red.SetValue(color.Red) } @@ -88,36 +49,27 @@ func (hc HeadColor) Set(color Color) Color { } // Set color with intensity, using either head intensity channel or linear RGB scaling -func (hc HeadColor) SetIntensity(color Color, intensity Intensity) { +func (hc HeadColor) setIntensity(color api.Color, intensity api.Value) api.Color { if hc.intensity != nil { - hc.Set(color) - hc.intensity.SetValue(Value(intensity)) + hc.setColor(color) + hc.intensity.SetValue(intensity) + return color } else { - hc.Set(color.ScaleIntensity(intensity)) + return hc.setColor(color.Scale(intensity)) } } -func (headColor *HeadColor) makeAPI() *APIColor { - if headColor == nil { - return nil - } - - return &APIColor{ - headColor: headColor, - Color: headColor.Get(), +func (hc HeadColor) SetColor(params api.ColorParams) api.Color { + if params.ScaleIntensity != nil { + return hc.setIntensity(params.Color, *params.ScaleIntensity) + } else { + return hc.setColor(params.Color) } } -func (headColor HeadColor) GetREST() (web.Resource, error) { - return headColor.makeAPI(), nil -} -func (headColor HeadColor) PostREST() (web.Resource, error) { - return headColor.makeAPI(), nil -} - // Group.Color type GroupColor struct { - headColors map[HeadID]HeadColor + headColors map[api.HeadID]HeadColor } func (groupColor GroupColor) exists() bool { @@ -125,82 +77,39 @@ func (groupColor GroupColor) exists() bool { } // Return one color for the group -func (groupColor GroupColor) Get() (color Color) { +func (groupColor GroupColor) GetColor() (color api.Color) { for _, headColor := range groupColor.headColors { // This works fine assuming they are all the same color :) - return headColor.Get() + return headColor.GetColor() } return } -func (groupColor GroupColor) Set(color Color) Color { +func (groupColor GroupColor) SetColor(params api.ColorParams) api.Color { + var color api.Color + for _, headColor := range groupColor.headColors { - headColor.Set(color) + color = headColor.SetColor(params) } return color } -func (groupColor *GroupColor) makeAPI() *APIColor { - if groupColor == nil { - return nil - } - - return &APIColor{ - groupColor: groupColor, - Color: groupColor.Get(), - } -} - -// Web API -type APIColor struct { - headColor *HeadColor - groupColor *GroupColor - - ScaleIntensity *Intensity - Color +// API +type colorView struct { + handler ColorHandler + params api.ColorParams } -func (apiColor APIColor) IsZero() bool { - return apiColor.Color.IsZero() -} -func (apiColor APIColor) Equals(other APIColor) bool { - return apiColor.Color == other.Color -} - -func (apiColor *APIColor) initHead(headColor *HeadColor) error { - if headColor == nil { - return fmt.Errorf("Head does not support color") - } - - apiColor.headColor = headColor - - return nil +func (view *colorView) IntoREST() interface{} { + return &view.params } -func (apiColor *APIColor) initGroup(groupColor *GroupColor) error { - if groupColor == nil { - return fmt.Errorf("Group does not support color") - } - - apiColor.groupColor = groupColor - - return nil +func (view *colorView) GetREST() (web.Resource, error) { + return view.handler.GetColor(), nil } -func (apiColor *APIColor) Apply() error { - if apiColor.ScaleIntensity != nil { - apiColor.Color = apiColor.Color.ScaleIntensity(*apiColor.ScaleIntensity) - } - - if apiColor.headColor != nil { - apiColor.Color = apiColor.headColor.Set(apiColor.Color) - } - - if apiColor.groupColor != nil { - apiColor.Color = apiColor.groupColor.Set(apiColor.Color) - } - - return nil +func (view *colorView) PostREST() (web.Resource, error) { + return view.handler.SetColor(view.params), nil } diff --git a/heads/config.go b/heads/config.go index 5c04d1b..5b00408 100644 --- a/heads/config.go +++ b/heads/config.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/BurntSushi/toml" + "github.com/qmsk/dmx/api" "github.com/qmsk/dmx/logging" ) @@ -29,6 +30,8 @@ func load(obj interface{}, path string) error { if err := loadToml(obj, path); err != nil { return fmt.Errorf("Load %T file %v: %v", obj, path, err) } else { + logging.Log.Infof("load %v: %T<%v>", path, obj, obj) + return nil } default: @@ -36,10 +39,10 @@ func load(obj interface{}, path string) error { } } -type configMapper func(id []string) (configObject interface{}, err error) +type walkFunc func(path string, id []string) error // Load config from given path, using the given stat info -func loadsStat(path string, stat os.FileInfo, mapper configMapper, prefix []string, top bool) error { +func walkStat(path string, stat os.FileInfo, f walkFunc, prefix []string, top bool) error { name := stat.Name() if stat.IsDir() { @@ -54,7 +57,7 @@ func loadsStat(path string, stat os.FileInfo, mapper configMapper, prefix []stri id = append(id, name) } - return loadsDir(path, mapper, id) + return walkDir(path, f, id) } else { // take basename *.ext part @@ -72,20 +75,16 @@ func loadsStat(path string, stat os.FileInfo, mapper configMapper, prefix []stri logging.Log.Debugf("heads:loadOne path=%v: file id=%v", path, id) - if obj, err := mapper(id); err != nil { - return err - } else if err := load(obj, path); err != nil { + if err := f(path, id); err != nil { return err } else { - logging.Log.Infof("heads:loads path=%v: %T %v ", path, obj, id) - return nil } } } // Recursively load multiple config files from a directory -func loadsDir(dirPath string, mapper configMapper, prefix []string) error { +func walkDir(dirPath string, f walkFunc, prefix []string) error { if files, err := ioutil.ReadDir(dirPath); err != nil { return fmt.Errorf("read dir %v: %v", dirPath, err) } else { @@ -104,7 +103,7 @@ func loadsDir(dirPath string, mapper configMapper, prefix []string) error { continue } - if err := loadsStat(path, stat, mapper, prefix, false); err != nil { + if err := walkStat(path, stat, f, prefix, false); err != nil { return err } } @@ -114,147 +113,145 @@ func loadsDir(dirPath string, mapper configMapper, prefix []string) error { } // Load config from path, which may either be a file, or a directory to be loaded recursively -func loads(path string, mapper configMapper) error { +func walk(path string, f walkFunc) error { if stat, err := os.Stat(path); err != nil { return err } else { - return loadsStat(path, stat, mapper, nil, true) + return walkStat(path, stat, f, nil, true) } } -type Config struct { - HeadTypes map[TypeID]*HeadType - Colors map[ColorID]*Color +type loadConfig api.Config - Heads map[HeadID]*HeadConfig - Groups map[GroupID]*GroupConfig +func (loadConfig *loadConfig) loadTypes(path string) error { + return walk(path, func(path string, id []string) error { + var typeID = api.TypeID(filepath.Join(id...)) + var headType api.HeadType - Presets map[PresetID]*PresetConfig + if err := load(&headType, path); err != nil { + return err + } else { + loadConfig.HeadTypes[typeID] = headType + } + }) } -func (config *Config) loadTypes(path string) error { - return loads(path, func(id []string) (interface{}, error) { - var typeID = TypeID(filepath.Join(id...)) - var headType = new(HeadType) +func (loadConfig *loadConfig) load(path string) error { + return walk(path, func(path string, id []string) error { + var typ string + var name string - config.HeadTypes[typeID] = headType + if len(id) > 0 { + typ = id[0] + } + if len(id) > 1 { + typ = typ + "/*" + name = filepath.Join(id[1:]...) + } - return headType, nil - }) -} + switch typ { + case "": + return load(loadConfig, path) -func (config *Config) load(path string) error { - return loads(path, func(id []string) (interface{}, error) { - if len(id) == 0 { - return config, nil - } - switch id[0] { case "colors": - if len(id) == 1 { - return &config.Colors, nil - } else { - var color = new(Color) + return load(&loadConfig.Colors, path) - config.Colors[ColorID(filepath.Join(id[1:]...))] = color + case "colors/*": + var color api.Color - return color, nil + if err := load(&color, path); err != nil { + return err + } else { + loadConfig.Colors[api.ColorID(name)] = color } case "heads": - if len(id) == 1 { - return &config.Heads, nil - } else { - var head = new(HeadConfig) + return load(&loadConfig.Heads, path) - config.Heads[HeadID(filepath.Join(id[1:]...))] = head + case "heads/*": + var headConfig api.HeadConfig - return head, nil + if err := load(&headConfig, path); err != nil { + return err + } else { + loadConfig.Heads[api.HeadID(name)] = headConfig } case "groups": - if len(id) == 1 { - return &config.Groups, nil - } else { - var group = new(GroupConfig) + return load(&loadConfig.Groups, path) - config.Groups[GroupID(filepath.Join(id[1:]...))] = group + case "groups/*": + var groupConfig api.GroupConfig - return group, nil + if err := load(&groupConfig, path); err != nil { + return err + } else { + loadConfig.Groups[api.GroupID(name)] = groupConfig } case "presets": - if len(id) == 1 { - return &config.Presets, nil - } else { - var preset = new(PresetConfig) + return load(&loadConfig.Presets, path) - config.Presets[PresetID(filepath.Join(id[1:]...))] = preset + case "presets/*": + var presetConfig api.PresetConfig - return preset, nil + if err := load(&presetConfig, path); err != nil { + return err + } else { + loadConfig.Presets[api.PresetID(name)] = presetConfig } default: - return nil, fmt.Errorf("Bad config path: %v", id) + return fmt.Errorf("Unkonwn config %v: %v", id, path) } - }) + return nil + }) } // map relative Head.Type= references -func (config *Config) mapTypes() error { - // clone to ColorMap without pointers - var colors = make(ColorMap) - for colorID, color := range config.Colors { - colors[colorID] = *color +func loadHeadType(config api.Config, headConfig api.HeadConfig) (api.HeadType, error) { + if headType, exists := config.HeadTypes[headConfig.Type]; !exists { + return headType, fmt.Errorf("Unknown Type=%v", headConfig.Type) + } else { + // merge over global colors + headType.Colors = config.Colors.Merge(headType.Colors) + + return headType, nil } +} - // inherit colors - for _, headType := range config.HeadTypes { - if !headType.IsColor() { - continue - } +// Config +func loadHeadID(headID api.HeadID, index int) api.HeadID { + return api.HeadID(fmt.Sprintf("%s.%d", headID, index+1)) +} - if headType.Colors == nil { - headType.Colors = make(ColorMap) - } +func loadHeadConfig(headConfig api.HeadConfig, index int, headType api.HeadType) api.HeadConfig { + var step = len(headType.Channels) - // each headType has its own copy - headType.Colors.Merge(colors) - } + headConfig.Address = headConfig.Address + api.DMXAddress(index*step) - for headID, headConfig := range config.Heads { - if headType, exists := config.HeadTypes[headConfig.Type]; !exists { - return fmt.Errorf("heads.%s: Invalid Head.Type=%v", headID, headConfig.Type) - } else { - headConfig.headType = headType - } - } - - return nil + return headConfig } -func (options Options) Config(path string) (*Config, error) { - var config = Config{ - HeadTypes: make(map[TypeID]*HeadType), - Colors: make(map[ColorID]*Color), - Heads: make(map[HeadID]*HeadConfig), - Groups: make(map[GroupID]*GroupConfig), - Presets: make(map[PresetID]*PresetConfig), - } +func loadHeads(config api.Config, f func(headID api.HeadID, headConfig api.HeadConfig, headType api.HeadType) error) error { + for headID, headConfig := range config.Heads { + var err error - for _, libraryPath := range options.LibraryPath { - if err := config.loadTypes(libraryPath); err != nil { - return nil, fmt.Errorf("loadTypes %v: %v", libraryPath, err) + if headType, headTypeErr := loadHeadType(config, headConfig); headTypeErr != nil { + err = headTypeErr + } else if headConfig.Count == 0 { + err = f(headID, headConfig, headType) + } else { + for index := 0; index < int(headConfig.Count); index++ { + err = f(loadHeadID(headID, index), loadHeadConfig(headConfig, index, headType), headType) + } } - } - - if err := config.load(path); err != nil { - return nil, err - } - if err := config.mapTypes(); err != nil { - return nil, err + if err != nil { + return fmt.Errorf("Load head %v: %v", headID, err) + } } - return &config, nil + return nil } diff --git a/heads/controller.go b/heads/controller.go new file mode 100644 index 0000000..51c7992 --- /dev/null +++ b/heads/controller.go @@ -0,0 +1,186 @@ +package heads + +import ( + "fmt" + "github.com/qmsk/dmx" + "github.com/qmsk/dmx/api" + "github.com/qmsk/dmx/logging" +) + +func MakeController() Controller { + return Controller{ + outputs: make(outputs), + heads: make(heads), + groups: make(groups), + presets: make(presets), + events: &events{}, + } +} + +type Controller struct { + log logging.Logger + outputs outputs + heads heads + groups groups + presets presets + events *events // optional +} + +func (controller *Controller) output(universe api.Universe) *Output { + output := controller.outputs[universe] + if output == nil { + output = &Output{ + events: controller.events, + } + + output.init(controller.log, universe) + + controller.outputs[universe] = output + } + + return output +} + +// Patch output +// XXX: not goroutine-safe... +func (controller *Controller) Output(universe api.Universe, config OutputConfig, writer dmx.Writer) { + controller.output(universe).connect(config, writer) +} + +func (controller *Controller) Load(config api.Config) error { + // preload groups + for groupID, groupConfig := range config.Groups { + controller.addGroup(groupID, groupConfig) + } + + if err := loadHeads(config, func(headID api.HeadID, headConfig api.HeadConfig, headType api.HeadType) error { + controller.addHead(headID, headConfig, headType) + + return nil + }); err != nil { + return err + } + + // once all heads are patched, init groups + for _, group := range controller.groups { + group.init() + } + + // load presets + for presetID, presetConfig := range config.Presets { + if err := controller.addPreset(presetID, presetConfig); err != nil { + return fmt.Errorf("Load preset %v: %v", presetID, err) + } + } + + return nil +} + +func (controller *Controller) addGroup(id api.GroupID, config api.GroupConfig) *Group { + group := controller.groups[id] + + if group == nil { + group = &Group{ + //XXX: log: controller.options.Log.Logger("group", id), + id: id, + config: config, + heads: make(heads), + colors: make(api.Colors), + events: controller.events, + outputs: controller.outputs, + } + + controller.groups[id] = group + } + + return group +} + +func (controller *Controller) group(id api.GroupID) *Group { + if group := controller.groups[id]; group == nil { + return controller.addGroup(id, api.GroupConfig{}) + } else { + return group + } +} + +// Patch head +func (controller *Controller) addHead(id api.HeadID, config api.HeadConfig, headType api.HeadType) *Head { + var output = controller.output(config.Universe) + var head = Head{ + // XXX: log: controller.options.Log.Logger("head", id), + id: id, + config: config, + headType: headType, + output: output, + events: controller.events, + groups: make(groups), + } + + // load head parameters + head.init() + + // map controller + controller.heads[id] = &head + + // map groups + for _, groupID := range config.Groups { + controller.group(groupID).addHead(&head) + } + + return &head +} + +func (controller *Controller) addPreset(id api.PresetID, config api.PresetConfig) error { + var preset = Preset{ + // XXX: log: controller.options.Log.Logger("preset", id), + events: controller.events, + id: id, + config: config, + groups: make(map[api.GroupID]PresetParameters), + heads: make(map[api.HeadID]PresetParameters), + } + + if config.All != nil { + preset.initAll(controller.heads, controller.groups) + } + + for groupID, params := range config.Groups { + if group := controller.groups[api.GroupID(groupID)]; group == nil { + return fmt.Errorf("Unknown group for preset %v: %v", id, groupID) + } else { + preset.initGroup(group, params) + } + } + + for headID, params := range config.Heads { + if head := controller.heads[api.HeadID(headID)]; head == nil { + return fmt.Errorf("Unknown head for preset %v: %v", id, headID) + } else { + preset.initHead(head, params) + } + } + + controller.presets[id] = &preset + + return nil +} + +func (controller *Controller) Each(fn func(head *Head)) { + for _, head := range controller.heads { + fn(head) + } +} + +// refresh outputs +func (controller *Controller) RefreshOutputs() error { + var refreshErr error + + for _, output := range controller.outputs { + if err := output.Refresh(); err != nil { + refreshErr = err + } + } + + return refreshErr +} diff --git a/heads/events.go b/heads/events.go index 8f21536..dba0457 100644 --- a/heads/events.go +++ b/heads/events.go @@ -1,63 +1,60 @@ package heads import ( + "github.com/qmsk/dmx/api" "github.com/qmsk/dmx/logging" "github.com/qmsk/go-web" ) // WebSocket handler -func (heads *Heads) WebEvents() web.Events { - heads.events.eventChan = make(chan web.Event) +func (controller *Controller) WebEvents() web.Events { + controller.events.eventChan = make(chan web.Event) return web.MakeEvents(web.EventConfig{ - EventPush: heads.events.eventChan, + EventPush: controller.events.eventChan, StateFunc: func() web.State { - heads.log.Info("WebEvents State request") + controller.log.Info("WebEvents State request") // must be goroutine-safe - return heads.makeAPI() + return controller.makeAPI() }, }) } -type APIEvents struct { - Outputs APIOutputs - Heads APIHeads - Groups APIGroups -} +type eventBuilder api.Event -func (event *APIEvents) addHead(head *Head) { - if event.Heads == nil { - event.Heads = APIHeads{head.id: head.makeAPI()} +func (eventBuilder *eventBuilder) addHead(head *Head) { + if eventBuilder.Heads == nil { + eventBuilder.Heads = api.Heads{head.id: head.makeAPI()} } else { - event.Heads[head.id] = head.makeAPI() + eventBuilder.Heads[head.id] = head.makeAPI() } } -func (event *APIEvents) addHeads(heads headMap) { - if event.Heads == nil { - event.Heads = heads.makeAPI() +func (eventBuilder *eventBuilder) addHeads(heads heads) { + if eventBuilder.Heads == nil { + eventBuilder.Heads = heads.makeAPI() } else { for headID, head := range heads { - event.Heads[headID] = head.makeAPI() + eventBuilder.Heads[headID] = head.makeAPI() } } } -func (events *APIEvents) addGroup(group *Group) { - if events.Groups == nil { - events.Groups = APIGroups{group.id: group.makeAPI()} +func (eventBuilder *eventBuilder) addGroup(group *Group) { + if eventBuilder.Groups == nil { + eventBuilder.Groups = api.Groups{group.id: group.makeAPI()} } else { - events.Groups[group.id] = group.makeAPI() + eventBuilder.Groups[group.id] = group.makeAPI() } } -func (event *APIEvents) addGroups(groups groupMap) { - if event.Groups == nil { - event.Groups = groups.makeAPI() +func (eventBuilder *eventBuilder) addGroups(groups groups) { + if eventBuilder.Groups == nil { + eventBuilder.Groups = groups.makeAPI() } else { for groupID, group := range groups { - event.Groups[groupID] = group.makeAPI() + eventBuilder.Groups[groupID] = group.makeAPI() } } } @@ -68,7 +65,7 @@ type events struct { } // push update events via websocket -func (events *events) update(event APIEvents) { +func (events *events) update(event api.Event) { if events.eventChan != nil { events.log.Infof("update") events.eventChan <- event @@ -76,5 +73,5 @@ func (events *events) update(event APIEvents) { } type Events interface { - update(event APIEvents) + update(event api.Event) } diff --git a/heads/group.go b/heads/group.go index 4e8989d..129b2fb 100644 --- a/heads/group.go +++ b/heads/group.go @@ -1,65 +1,61 @@ package heads import ( + "github.com/qmsk/dmx/api" "github.com/qmsk/dmx/logging" "github.com/qmsk/go-web" ) -// Config -type GroupID string +type groups map[api.GroupID]*Group -type GroupConfig struct { - Heads []HeadID - Name string -} - -// heads -type groupMap map[GroupID]*Group - -type APIGroups map[GroupID]APIGroup - -func (groupMap groupMap) makeAPI() APIGroups { - apiGroups := make(APIGroups) +func (groups groups) makeAPI() api.Groups { + apiGroups := make(api.Groups) - for groupID, group := range groupMap { + for groupID, group := range groups { apiGroups[groupID] = group.makeAPI() } return apiGroups } -func (groupMap groupMap) makeAPIList() (apiGroups []APIGroup) { - for _, group := range groupMap { +func (groups groups) makeAPIList() (apiGroups []api.Group) { + for _, group := range groups { apiGroups = append(apiGroups, group.makeAPI()) } return } -func (groupMap groupMap) GetREST() (web.Resource, error) { - return groupMap.makeAPI(), nil +type groupsView struct { + groups groups } -func (groupMap groupMap) Index(name string) (web.Resource, error) { - switch name { - case "": - return groupMap.makeAPIList(), nil - default: - return groupMap[GroupID(name)], nil +func (view groupsView) GetREST() (web.Resource, error) { + return view.groups.makeAPI(), nil +} + +func (view groupsView) Index(name string) (web.Resource, error) { + if name == "" { + return view, nil + } else if group := view.groups[api.GroupID(name)]; group != nil { + return groupView{group: group}, nil + } else { + return nil, nil } } // Group type Group struct { - log logging.Logger - id GroupID - config GroupConfig - heads headMap - events Events + log logging.Logger + id api.GroupID + config api.GroupConfig + heads heads + events Events + outputs outputs intensity *GroupIntensity color *GroupColor - colors ColorMap + colors api.Colors } func (group *Group) addHead(head *Head) { @@ -91,11 +87,11 @@ func (group *Group) init() { func (group *Group) makeIntensity() GroupIntensity { var groupIntensity = GroupIntensity{ - heads: make(map[HeadID]HeadIntensity), + heads: make(map[api.HeadID]HeadIntensity), } for headID, head := range group.heads { - if headIntensity := head.parameters.Intensity; headIntensity != nil { + if headIntensity := head.intensity; headIntensity != nil { groupIntensity.heads[headID] = *headIntensity } } @@ -105,11 +101,11 @@ func (group *Group) makeIntensity() GroupIntensity { func (group *Group) makeColor() GroupColor { var groupColor = GroupColor{ - headColors: make(map[HeadID]HeadColor), + headColors: make(map[api.HeadID]HeadColor), } for headID, head := range group.heads { - if headColor := head.parameters.Color; headColor != nil { + if headColor := head.color; headColor != nil { groupColor.headColors[headID] = *headColor } } @@ -117,24 +113,8 @@ func (group *Group) makeColor() GroupColor { return groupColor } -// Web API -type APIGroupParams struct { - group *Group - Intensity *APIIntensity `json:",omitempty"` - Color *APIColor `json:",omitempty"` -} - -type APIGroup struct { - GroupConfig - ID GroupID - Heads []HeadID - Colors ColorMap - - APIGroupParams -} - -func (group *Group) makeAPIHeads() []HeadID { - var heads = make([]HeadID, 0) +func (group *Group) makeAPIHeads() []api.HeadID { + var heads = make([]api.HeadID, 0) for headID, _ := range group.heads { heads = append(heads, headID) @@ -142,57 +122,73 @@ func (group *Group) makeAPIHeads() []HeadID { return heads } -func (group *Group) makeAPI() APIGroup { - return APIGroup{ +func (group *Group) makeAPI() api.Group { + var apiGroup = api.Group{ GroupConfig: group.config, ID: group.id, Heads: group.makeAPIHeads(), Colors: group.colors, - APIGroupParams: APIGroupParams{ - group: group, - Intensity: group.intensity.makeAPI(), - Color: group.color.makeAPI(), - }, } -} -func (group *Group) GetREST() (web.Resource, error) { - return group.makeAPI(), nil -} -func (group *Group) PostREST() (web.Resource, error) { - return &APIGroupParams{group: group}, nil + if group.intensity != nil { + var intensity = group.intensity.GetIntensity() + + apiGroup.Intensity = &intensity + } + + if group.color != nil { + var color = group.color.GetColor() + + apiGroup.Color = &color + } + + return apiGroup } -func (apiGroupParams APIGroupParams) Apply() error { - if apiGroupParams.Intensity != nil { - if err := apiGroupParams.Intensity.initGroup(apiGroupParams.group.intensity); err != nil { - return web.RequestError(err) - } else if err := apiGroupParams.Intensity.Apply(); err != nil { - return err - } +func (group *Group) applyAPI(params api.GroupParams) error { + if params.Intensity != nil { + group.intensity.SetIntensity(*params.Intensity) } - if apiGroupParams.Color != nil { - if err := apiGroupParams.Color.initGroup(apiGroupParams.group.color); err != nil { - return web.RequestError(err) - } else if err := apiGroupParams.Color.Apply(); err != nil { - return err - } + if params.Color != nil { + group.color.SetColor(*params.Color) } return nil } -// Web API Events -func (group *Group) Apply() error { +func (group *Group) update() error { group.log.Info("Apply") - group.events.update(APIEvents{ - Heads: group.heads.makeAPI(), - Groups: APIGroups{ - group.id: group.makeAPI(), - }, + group.outputs.Refresh() + + group.events.update(api.Event{ + Heads: group.heads.makeAPI(), + Groups: api.Groups{group.id: group.makeAPI()}, }) return nil } + +type groupView struct { + group *Group + params api.GroupParams +} + +func (view *groupView) IntoREST() interface{} { + return &view.params +} + +func (view *groupView) GetREST() (web.Resource, error) { + return view.group.makeAPI(), nil +} + +func (view *groupView) PostREST() (web.Resource, error) { + if err := view.group.applyAPI(view.params); err != nil { + return nil, err + } else if err := view.group.update(); err != nil { + return nil, err + } else { + return view.group.makeAPI(), nil + } +} diff --git a/heads/head.go b/heads/head.go index ea778ff..7b80c5e 100644 --- a/heads/head.go +++ b/heads/head.go @@ -1,79 +1,16 @@ package heads import ( - "fmt" - "github.com/qmsk/dmx" + "github.com/qmsk/dmx/api" "github.com/qmsk/dmx/logging" "github.com/qmsk/go-web" ) -// Config type -type TypeID string - -type HeadType struct { - Vendor string - Model string - Mode string - URL string - - Channels []ChannelType - Colors ColorMap -} - -func (headType HeadType) String() string { - return fmt.Sprintf("%v/%v=%v", headType.Vendor, headType.Model, headType.Mode) -} - -func (headType HeadType) IsColor() bool { - for _, channelType := range headType.Channels { - if channelType.Color != "" { - return true - } - } - return false -} - -// Config -type HeadID string - -func (headID HeadID) index(index uint) HeadID { - return HeadID(fmt.Sprintf("%s.%d", headID, index+1)) -} - -type HeadConfig struct { - Type TypeID - Universe Universe - Address dmx.Address - Name string - Count uint // Clone multiple copies of the head at id.N - Groups []GroupID - - headType *HeadType -} - -// Number of channels used by head for count indexing -func (headConfig HeadConfig) step() uint { - return uint(len(headConfig.headType.Channels)) -} - -// Return an indexed copy of the head, step addresses ahead -func (headConfig HeadConfig) index(index uint) HeadConfig { - // copy - var indexed HeadConfig = headConfig - - indexed.Address = indexed.Address + dmx.Address(index*headConfig.step()) - - return indexed -} - -// Top-level map -type headMap map[HeadID]*Head - -type APIHeads map[HeadID]APIHead +type heads map[api.HeadID]*Head -func (heads headMap) makeAPI() APIHeads { - var apiHeads = make(APIHeads) +func (heads heads) makeAPI() api.Heads { + var apiHeads = make(api.Heads) for headID, head := range heads { apiHeads[headID] = head.makeAPI() @@ -81,84 +18,48 @@ func (heads headMap) makeAPI() APIHeads { return apiHeads } -type headList headMap - -func (heads headList) GetREST() (web.Resource, error) { - var apiHeads []APIHead +func (heads heads) makeAPIList() []api.Head { + var apiHeads = make([]api.Head, 0, len(heads)) for _, head := range heads { apiHeads = append(apiHeads, head.makeAPI()) } - return apiHeads, nil -} - -func (headMap headMap) Index(name string) (web.Resource, error) { - switch name { - case "": - return headList(headMap), nil - default: - return headMap[HeadID(name)], nil - } -} - -func (headMap headMap) GetREST() (web.Resource, error) { - return headMap.makeAPI(), nil -} - -// Channels -type HeadChannels map[ChannelType]*Channel - -func (headChannels HeadChannels) GetID(id string) *Channel { - for channelType, channel := range headChannels { - if channelType.String() == id { - return channel - } - } - - return nil -} - -func (headChannels HeadChannels) makeAPI() APIChannels { - var apiChannels = make(APIChannels) - - for channelType, channel := range headChannels { - apiChannels[channelType.String()] = channel.makeAPI() - } - - return apiChannels + return apiHeads } -func (headChannels HeadChannels) GetREST() (web.Resource, error) { - return headChannels.makeAPI(), nil +type headsView struct { + heads heads } -func (headChannels HeadChannels) Index(name string) (web.Resource, error) { - if channel := headChannels.GetID(name); channel == nil { - return nil, nil +func (view headsView) Index(name string) (web.Resource, error) { + if name == "" { + return view, nil + } else if head := view.heads[api.HeadID(name)]; head != nil { + return headView{head: head}, nil } else { - return web.GetPostResource(channel), nil + return nil, nil } } -type HeadParameters struct { - Intensity *HeadIntensity `json:"intensity,omitempty"` - Color *HeadColor `json:"color,omitempty"` +func (view headsView) GetREST() (web.Resource, error) { + return view.heads.makeAPIList(), nil } // A single DMX receiver using multiple consecutive DMX channels from a base address within a single universe type Head struct { log logging.Logger - id HeadID - config HeadConfig - headType *HeadType + id api.HeadID + config api.HeadConfig + headType api.HeadType output *Output events Events - groups groupMap + groups groups - channels HeadChannels - parameters HeadParameters + channels channels + intensity *HeadIntensity + color *HeadColor } func (head *Head) String() string { @@ -174,27 +75,28 @@ func (head *Head) Name() string { } func (head *Head) init() { - head.channels = make(HeadChannels) + head.channels = make(channels) - for channelIndex, channelType := range head.headType.Channels { + for index, channelConfig := range head.headType.Channels { var channel = &Channel{ - channelType: channelType, - index: uint(channelIndex), - output: head.output, - address: head.config.Address + dmx.Address(channelIndex), + id: channelConfig.ID(), + config: channelConfig, + index: uint(index), + output: head.output, + address: dmx.Address(head.config.Address) + dmx.Address(index), } channel.init() - head.channels[channelType] = channel + head.channels[channel.id] = channel } - // set parameters - if headIntensity := head.getIntensity(); headIntensity.exists() { - head.parameters.Intensity = &headIntensity + // setup parameters + if headIntensity := head.Intensity(); headIntensity.exists() { + head.intensity = &headIntensity } - if headColor := head.getColor(); headColor.exists() { - head.parameters.Color = &headColor + if headColor := head.Color(); headColor.exists() { + head.color = &headColor } } @@ -203,129 +105,126 @@ func (head *Head) initGroup(group *Group) { head.groups[group.id] = group } -func (head *Head) getChannel(channelType ChannelType) *Channel { - return head.channels[channelType] +func (head *Head) Channel(config api.ChannelConfig) *Channel { + return head.channels[config.ID()] } -func (head *Head) getIntensity() HeadIntensity { +func (head *Head) Intensity() HeadIntensity { return HeadIntensity{ - channel: head.getChannel(ChannelType{Intensity: true}), + channel: head.Channel(api.ChannelConfig{Intensity: true}), } } -func (head *Head) getColor() HeadColor { +func (head *Head) Color() HeadColor { return HeadColor{ - red: head.getChannel(ChannelType{Color: ColorChannelRed}), - green: head.getChannel(ChannelType{Color: ColorChannelGreen}), - blue: head.getChannel(ChannelType{Color: ColorChannelBlue}), - intensity: head.getChannel(ChannelType{Intensity: true}), + red: head.Channel(api.ChannelConfig{Color: api.ChannelColorRed}), + green: head.Channel(api.ChannelConfig{Color: api.ChannelColorGreen}), + blue: head.Channel(api.ChannelConfig{Color: api.ChannelColorBlue}), + intensity: head.Channel(api.ChannelConfig{Intensity: true}), } } -func (head *Head) Parameters() HeadParameters { - return head.parameters -} - -// Web API GET -type APIHead struct { - ID HeadID - Config HeadConfig - Type *HeadType - - Channels map[string]APIChannel `json:",omitempty"` - Intensity *APIIntensity `json:",omitempty"` - Color *APIColor `json:",omitempty"` -} - -func (head *Head) makeAPI() APIHead { - return APIHead{ +func (head *Head) makeAPI() api.Head { + var apiHead = api.Head{ ID: head.id, Config: head.config, Type: head.headType, - Channels: head.channels.makeAPI(), - Intensity: head.parameters.Intensity.makeAPI(), - Color: head.parameters.Color.makeAPI(), + Channels: head.channels.makeAPI(), } -} -func (head *Head) GetREST() (web.Resource, error) { - return head.makeAPI(), nil -} + if head.intensity != nil { + var intensity = head.intensity.GetIntensity() -// Web API POST -type APIHeadParams struct { - head *Head + apiHead.Intensity = &intensity + } - Channels map[string]APIChannelParams `json:",omitempty"` - Intensity *APIIntensity `json:",omitempty"` - Color *APIColor `json:",omitempty"` -} + if head.color != nil { + var color = head.color.GetColor() + + apiHead.Color = &color + } -func (head *Head) PostREST() (web.Resource, error) { - // parameters only, not configuration - return &APIHeadParams{head: head}, nil + return apiHead } -func (post *APIHeadParams) Apply() error { - post.head.log.Info("Apply parameters: %#v", post) +func (head *Head) applyAPI(params api.HeadParams) error { + head.log.Info("Apply: %#v", params) - for channelID, channelParams := range post.Channels { - if channel := post.head.channels.GetID(channelID); channel == nil { - return web.Errorf(404, "Channel not found: %v", channelID) + for channelID, channelParams := range params.Channels { + if channel := head.channels[channelID]; channel == nil { + return web.Errorf(422, "No channel for head %v: %v", head.id, channelID) } else { - channelParams.channel = channel - } - - if err := channelParams.Apply(); err != nil { - return err + channel.SetChannel(channelParams) } } - if post.Intensity != nil { - if err := post.Intensity.initHead(post.head.parameters.Intensity); err != nil { - return web.RequestError(err) - } else if err := post.Intensity.Apply(); err != nil { - return err + if params.Intensity != nil { + if head.intensity == nil { + return web.Errorf(422, "No intensity for head %v", head.id) + } else { + head.intensity.SetIntensity(*params.Intensity) } } - if post.Color != nil { - if err := post.Color.initHead(post.head.parameters.Color); err != nil { - return web.RequestError(err) - } else if err := post.Color.Apply(); err != nil { - return err + if params.Color != nil { + if head.color == nil { + return web.Errorf(422, "No color for head %v", head.id) + } else { + head.color.SetColor(*params.Color) } } return nil } -func (head *Head) Index(name string) (web.Resource, error) { +func (head *Head) update() error { + head.log.Info("Apply") + + head.output.Refresh() + + head.events.update(api.Event{ + Heads: api.Heads{head.id: head.makeAPI()}, + Groups: head.groups.makeAPI(), + }) + + return nil +} + +// GET /heads/:id => api.Head +type headView struct { + head *Head + params api.HeadParams +} + +func (view *headView) Index(name string) (web.Resource, error) { switch name { case "": - return head, nil + return view, nil case "channels": - return head.channels, nil + return &channelsView{view.head.channels}, nil case "intensity": - return head.parameters.Intensity, nil + return &intensityView{handler: view.head.intensity}, nil case "color": - return head.parameters.Color, nil + return &colorView{handler: view.head.color}, nil default: return nil, nil } } -// Web API Events -func (head *Head) Apply() error { - head.log.Info("Apply") - - head.events.update(APIEvents{ - Heads: APIHeads{ - head.id: head.makeAPI(), - }, - Groups: head.groups.makeAPI(), - }) +func (view *headView) IntoREST() interface{} { + return &view.params +} - return nil +func (view *headView) GetREST() (web.Resource, error) { + return view.head.makeAPI(), nil +} +func (view *headView) PostREST() (web.Resource, error) { + if err := view.head.applyAPI(view.params); err != nil { + return nil, err + } else if err := view.head.update(); err != nil { + return nil, err + } else { + return view.head.makeAPI(), nil + } } diff --git a/heads/heads.go b/heads/heads.go deleted file mode 100644 index e0c51d7..0000000 --- a/heads/heads.go +++ /dev/null @@ -1,210 +0,0 @@ -package heads - -import ( - "fmt" - "github.com/qmsk/dmx" - "github.com/qmsk/dmx/logging" -) - -type Options struct { - Log logging.Option `long:"log.heads"` - - LibraryPath []string `long:"heads-library" value-name:"PATH"` -} - -func (options Options) Heads(config *Config) (*Heads, error) { - options.Log.Package = "heads" - - var heads = Heads{ - options: options, - log: options.Log.Logger("package", "heads"), - - outputs: make(outputMap), - heads: make(headMap), - groups: make(groupMap), - presets: make(presetMap), - events: &events{ - log: options.Log.Logger("events", nil), - }, - } - - if err := heads.load(config); err != nil { - return nil, err - } - - return &heads, nil -} - -type Heads struct { - options Options - log logging.Logger - config *Config - outputs outputMap - heads headMap - groups groupMap - presets presetMap - events *events // optional -} - -func (heads *Heads) output(universe Universe) *Output { - output := heads.outputs[universe] - if output == nil { - output = &Output{ - events: heads.events, - } - - output.init(heads.log, universe) - - heads.outputs[universe] = output - } - - return output -} - -// Patch output -// XXX: not goroutine-safe... -func (heads *Heads) Output(universe Universe, config OutputConfig, writer dmx.Writer) { - heads.output(universe).connect(config, writer) -} - -func (heads *Heads) load(config *Config) error { - heads.config = config - - // preload groups - for groupID, groupConfig := range config.Groups { - heads.addGroup(groupID, *groupConfig) - } - - for headID, headConfig := range config.Heads { - if headConfig.Count > 0 { - var index uint - for index = 0; index < headConfig.Count; index++ { - heads.addHead(headID.index(index), headConfig.index(index), headConfig.headType) - } - } else { - heads.addHead(headID, *headConfig, headConfig.headType) - } - } - - // once all heads are patched, init groups - for _, group := range heads.groups { - group.init() - } - - // load presets - for presetID, presetConfig := range config.Presets { - if err := heads.addPreset(presetID, *presetConfig); err != nil { - return fmt.Errorf("Load preset=%v: %v", presetID, err) - } - } - - return nil -} - -func (heads *Heads) addGroup(id GroupID, config GroupConfig) *Group { - group := heads.groups[id] - - if group == nil { - group = &Group{ - log: heads.options.Log.Logger("group", id), - id: id, - config: config, - heads: make(headMap), - colors: make(ColorMap), - events: heads.events, - } - - heads.groups[id] = group - } - - return group -} - -func (heads *Heads) group(id GroupID) *Group { - if group := heads.groups[id]; group == nil { - return heads.addGroup(id, GroupConfig{}) - } else { - return group - } -} - -// Patch head -func (heads *Heads) addHead(id HeadID, config HeadConfig, headType *HeadType) *Head { - var output = heads.output(config.Universe) - var head = Head{ - log: heads.options.Log.Logger("head", id), - id: id, - config: config, - headType: headType, - output: output, - events: heads.events, - groups: make(groupMap), - } - - // load head parameters - head.init() - - // map heads - heads.heads[id] = &head - - // map groups - for _, groupID := range config.Groups { - heads.group(groupID).addHead(&head) - } - - return &head -} - -func (heads *Heads) addPreset(id PresetID, config PresetConfig) error { - var preset = Preset{ - log: heads.options.Log.Logger("preset", id), - events: heads.events, - ID: id, - Config: config, - Groups: make(map[GroupID]PresetParameters), - Heads: make(map[HeadID]PresetParameters), - } - - if preset.Config.All != nil { - preset.initAll(heads.heads, heads.groups) - } - - for groupID, presetParameters := range preset.Config.Groups { - if group := heads.groups[GroupID(groupID)]; group == nil { - return fmt.Errorf("No such group: %v", groupID) - } else { - preset.initGroup(group, presetParameters) - } - } - - for headID, presetParameters := range preset.Config.Heads { - if head := heads.heads[HeadID(headID)]; head == nil { - return fmt.Errorf("No such head: %v", headID) - } else { - preset.initHead(head, presetParameters) - } - } - - heads.presets[id] = &preset - - return nil -} - -func (heads *Heads) Each(fn func(head *Head)) { - for _, head := range heads.heads { - fn(head) - } -} - -// refresh outputs -func (heads *Heads) Refresh() error { - var refreshErr error - - for _, output := range heads.outputs { - if err := output.Refresh(); err != nil { - refreshErr = err - } - } - - return refreshErr -} diff --git a/heads/intensity.go b/heads/intensity.go index 732016e..82f0dba 100644 --- a/heads/intensity.go +++ b/heads/intensity.go @@ -1,14 +1,13 @@ package heads import ( - "fmt" + "github.com/qmsk/dmx/api" "github.com/qmsk/go-web" ) -type Intensity Value // 0.0 .. 1.0 - -func (intensity Intensity) ScaleIntensity(scale Intensity) Intensity { - return intensity * scale +type IntensityHandler interface { + GetIntensity() api.Intensity + SetIntensity(api.IntensityParams) api.Intensity } // Head.Intensity @@ -20,119 +19,63 @@ func (it HeadIntensity) exists() bool { return it.channel != nil } -func (it HeadIntensity) Get() Intensity { +func (it HeadIntensity) GetIntensity() api.Intensity { if it.channel != nil { - return Intensity(it.channel.GetValue()) + return api.Intensity{it.channel.GetValue()} } else { - return Intensity(INVALID) + return api.Intensity{} } } -func (it HeadIntensity) Set(intensity Intensity) Intensity { - return Intensity(it.channel.SetValue(Value(intensity))) -} - -func (headIntensity *HeadIntensity) makeAPI() *APIIntensity { - if headIntensity == nil { - return nil - } +func (it HeadIntensity) SetIntensity(params api.IntensityParams) api.Intensity { + var intensity = params.Intensity - return &APIIntensity{ - headIntensity: headIntensity, - Intensity: headIntensity.Get(), + if params.ScaleIntensity != nil { + intensity = intensity.Scale(*params.ScaleIntensity) } -} - -func (headIntensity HeadIntensity) GetREST() (web.Resource, error) { - return headIntensity.makeAPI(), nil -} -func (headIntensity HeadIntensity) PostREST() (web.Resource, error) { - return headIntensity.makeAPI(), nil + return api.Intensity{it.channel.SetValue(intensity.Intensity)} } // Group.Intensity type GroupIntensity struct { - heads map[HeadID]HeadIntensity + heads map[api.HeadID]HeadIntensity } func (groupIntensity GroupIntensity) exists() bool { return len(groupIntensity.heads) > 0 } -func (groupIntensity GroupIntensity) Get() (intensity Intensity) { +func (groupIntensity GroupIntensity) GetIntensity() (intensity api.Intensity) { for _, headIntensity := range groupIntensity.heads { - return headIntensity.Get() + return headIntensity.GetIntensity() } return } -func (groupIntensity GroupIntensity) Set(intensity Intensity) Intensity { +func (groupIntensity GroupIntensity) SetIntensity(params api.IntensityParams) api.Intensity { + var intensity api.Intensity + for _, headIntensity := range groupIntensity.heads { - headIntensity.Set(intensity) + intensity = headIntensity.SetIntensity(params) } return intensity } -func (groupIntensity *GroupIntensity) makeAPI() *APIIntensity { - if groupIntensity == nil { - return nil - } - - return &APIIntensity{ - groupIntensity: groupIntensity, - Intensity: groupIntensity.Get(), - } -} - // Web API -type APIIntensity struct { - headIntensity *HeadIntensity - groupIntensity *GroupIntensity - - ScaleIntensity *Intensity - Intensity +type intensityView struct { + handler IntensityHandler + params api.IntensityParams } -func (apiIntensity APIIntensity) IsZero() bool { - return apiIntensity.Intensity == 0.0 -} -func (apiIntensity APIIntensity) Equals(other APIIntensity) bool { - return apiIntensity.Intensity == other.Intensity +func (view *intensityView) IntoREST() interface{} { + return &view.params } -func (apiIntensity *APIIntensity) initHead(headIntensity *HeadIntensity) error { - if headIntensity == nil { - return fmt.Errorf("Head does not support intensity") - } - - apiIntensity.headIntensity = headIntensity - - return nil -} - -func (apiIntensity *APIIntensity) initGroup(groupIntensity *GroupIntensity) error { - if groupIntensity == nil { - return web.RequestErrorf("Group does not support intensity") - } - - apiIntensity.groupIntensity = groupIntensity - - return nil +func (view *intensityView) GetREST() (web.Resource, error) { + return view.handler.GetIntensity(), nil } -func (apiIntensity *APIIntensity) Apply() error { - if apiIntensity.ScaleIntensity != nil { - apiIntensity.Intensity = apiIntensity.Intensity.ScaleIntensity(*apiIntensity.ScaleIntensity) - } - - if apiIntensity.headIntensity != nil { - apiIntensity.Intensity = apiIntensity.headIntensity.Set(apiIntensity.Intensity) - } - - if apiIntensity.groupIntensity != nil { - apiIntensity.Intensity = apiIntensity.groupIntensity.Set(apiIntensity.Intensity) - } - - return nil +func (view *intensityView) PostREST() (web.Resource, error) { + return view.handler.SetIntensity(view.params), nil } diff --git a/heads/options.go b/heads/options.go new file mode 100644 index 0000000..7a64bd9 --- /dev/null +++ b/heads/options.go @@ -0,0 +1,45 @@ +package heads + +import ( + "fmt" + + "github.com/qmsk/dmx/api" + "github.com/qmsk/dmx/logging" +) + +type Options struct { + Log logging.Option `long:"log.heads"` + + LibraryPath []string `long:"heads-library" value-name:"PATH"` +} + +func (options Options) LoadConfig(path string) (api.Config, error) { + var config loadConfig + + for _, libraryPath := range options.LibraryPath { + if err := config.loadTypes(libraryPath); err != nil { + return api.Config(config), fmt.Errorf("loadTypes %v: %v", libraryPath, err) + } + } + + if err := config.load(path); err != nil { + return api.Config(config), err + } + + return api.Config(config), nil +} + +func (options Options) NewController(config api.Config) (*Controller, error) { + options.Log.Package = "heads" + + var controller = MakeController() + + controller.log = options.Log.Logger("package", "heads") + controller.events.log = options.Log.Logger("events", nil) + + if err := controller.Load(config); err != nil { + return nil, err + } + + return &controller, nil +} diff --git a/heads/output.go b/heads/output.go index 119b44a..d5f5bf4 100644 --- a/heads/output.go +++ b/heads/output.go @@ -5,6 +5,7 @@ import ( "time" "github.com/qmsk/dmx" + "github.com/qmsk/dmx/api" "github.com/qmsk/dmx/logging" "github.com/qmsk/go-web" ) @@ -17,20 +18,30 @@ type OutputConfig struct { Artnet interface{} // metadata } -type outputMap map[Universe]*Output +type outputs map[api.Universe]*Output -func (outputMap outputMap) makeAPI() APIOutputs { - var apiOutputs = make(APIOutputs) +func (outputs outputs) Refresh() { + for _, output := range outputs { + output.Refresh() + } +} + +func (outputs outputs) makeAPI() api.Outputs { + var apiOutputs = make(api.Outputs) - for _, output := range outputMap { - apiOutputs[output.String()] = output.makeAPI() + for _, output := range outputs { + apiOutputs[api.OutputID(output.String())] = output.makeAPI() } return apiOutputs } -func (outputMap outputMap) GetREST() (web.Resource, error) { - return outputMap.makeAPI(), nil +type outputsView struct { + outputs outputs +} + +func (view outputsView) GetREST() (web.Resource, error) { + return view.outputs.makeAPI(), nil } type OutputConnection struct { @@ -43,7 +54,7 @@ type Output struct { log logging.Logger events Events - universe Universe + universe api.Universe dmx dmx.Universe connection *OutputConnection } @@ -52,7 +63,7 @@ func (output *Output) String() string { return fmt.Sprintf("%d", output.universe) } -func (output *Output) init(logger logging.Logger, universe Universe) { +func (output *Output) init(logger logging.Logger, universe api.Universe) { output.log = logger.Logger("universe", universe) output.universe = universe output.dmx = dmx.MakeUniverse() @@ -70,16 +81,14 @@ func (output *Output) connect(config OutputConfig, dmxWriter dmx.Writer) { output.connection.dmxWriter = dmxWriter } - output.apply() + output.update() } -func (output *Output) apply() { - var id = output.String() +func (output *Output) update() { + var id = api.OutputID(output.String()) - output.events.update(APIEvents{ - Outputs: APIOutputs{ - id: output.makeAPI(), - }, + output.events.update(api.Event{ + Outputs: api.Outputs{id: output.makeAPI()}, }) } @@ -122,29 +131,20 @@ func (output *Output) Refresh() error { return nil } -// Web API -type APIOutputs map[string]APIOutput - -type APIOutput struct { - Universe Universe - Connected *time.Time - - *OutputConfig -} - -func (output *Output) makeAPI() APIOutput { - var apiOutput = APIOutput{ +func (output *Output) makeAPI() api.Output { + var apiOutput = api.Output{ Universe: output.universe, } if output.connection != nil { apiOutput.Connected = &output.connection.time - apiOutput.OutputConfig = &output.connection.config + apiOutput.OutputStatus = api.OutputStatus{ + Seen: output.connection.config.Seen, + Address: output.connection.config.Address, + Port: output.connection.config.Port, + Artnet: output.connection.config.Artnet, + } } return apiOutput } - -func (output *Output) GetREST() (web.Resource, error) { - return output.makeAPI(), nil -} diff --git a/heads/preset.go b/heads/preset.go index 0186f61..ee5ca1e 100644 --- a/heads/preset.go +++ b/heads/preset.go @@ -3,6 +3,7 @@ package heads import ( "net/http" + "github.com/qmsk/dmx/api" "github.com/qmsk/dmx/logging" "github.com/qmsk/go-web" @@ -10,14 +11,12 @@ import ( ) // Config -type PresetID string - type PresetParameters struct { - head *Head - group *Group + intensityHandler IntensityHandler + colorHandler ColorHandler - Intensity *APIIntensity - Color *APIColor + Intensity *api.IntensityParams + Color *api.ColorParams } func (presetParameters PresetParameters) IsZero() bool { @@ -47,212 +46,162 @@ func (presetParameters PresetParameters) Overrides(other PresetParameters) bool return false } -func (presetParameters *PresetParameters) init() error { +func (presetParameters PresetParameters) scale(scale api.Value) PresetParameters { if presetParameters.Intensity != nil { - if presetParameters.group != nil { - if err := presetParameters.Intensity.initGroup(presetParameters.group.intensity); err != nil { - return err - } - } - if presetParameters.head != nil { - if err := presetParameters.Intensity.initHead(presetParameters.head.parameters.Intensity); err != nil { - return err - } - } + intensityParams := *presetParameters.Intensity + intensityParams.ScaleIntensity = &scale + + presetParameters.Intensity = &intensityParams } if presetParameters.Color != nil { - if presetParameters.group != nil { - if err := presetParameters.Color.initGroup(presetParameters.group.color); err != nil { - return err - } - } - if presetParameters.head != nil { - if err := presetParameters.Color.initHead(presetParameters.head.parameters.Color); err != nil { - return err - } - } + colorParams := *presetParameters.Color + colorParams.ScaleIntensity = &scale + + presetParameters.Color = &colorParams } - return nil + return presetParameters } -func (presetParameters PresetParameters) scaleIntensity(scaleIntensity Intensity) PresetParameters { - if presetParameters.Intensity != nil { - apiIntensity := *presetParameters.Intensity - apiIntensity.ScaleIntensity = &scaleIntensity - - presetParameters.Intensity = &apiIntensity +func (params PresetParameters) apply() { + if params.intensityHandler != nil && params.Intensity != nil { + params.intensityHandler.SetIntensity(*params.Intensity) } - if presetParameters.Color != nil { - apiColor := *presetParameters.Color - apiColor.ScaleIntensity = &scaleIntensity - - presetParameters.Color = &apiColor + if params.colorHandler != nil && params.Color != nil { + params.colorHandler.SetColor(*params.Color) } - - return presetParameters } -// requires initHead/initGroup -func (presetParameters PresetParameters) Apply() error { - if presetParameters.Intensity == nil { +func (presetParameters PresetParameters) Set(params api.PresetParams) { - } else if err := presetParameters.Intensity.Apply(); err != nil { - return err - } +} - if presetParameters.Color == nil { +type presets map[api.PresetID]*Preset - } else if err := presetParameters.Color.Apply(); err != nil { - return err +func (presets presets) Get() api.Presets { + var apiPresests = make(api.Presets) + + for presetID, preset := range presets { + apiPresests[presetID] = preset.makeAPI() } - return nil + return apiPresests } -type PresetConfig struct { - Name string - All *PresetParameters - Groups map[string]PresetParameters - Heads map[string]PresetParameters +type presetsView struct { + presets presets } -// Heads.Presets -type presetMap map[PresetID]*Preset - -func (presetMap presetMap) GetREST() (web.Resource, error) { - return presetMap, nil +func (view presetsView) GetREST() (web.Resource, error) { + return view.presets.makeAPI(), nil } -func (presetMap presetMap) Index(name string) (web.Resource, error) { - return presetMap[PresetID(name)], nil +func (view presetsView) Index(name string) (web.Resource, error) { + if name == "" { + return view, nil + } else if preset := view.presets[api.PresetID(name)]; preset != nil { + return &presetView{preset: preset}, nil + } else { + return nil, nil + } } type Preset struct { log logging.Logger events Events - ID PresetID - Config PresetConfig + id api.PresetID + config api.PresetConfig - allHeads headMap - allGroups groupMap - Groups map[GroupID]PresetParameters - Heads map[HeadID]PresetParameters + allHeads heads + allGroups groups + groups map[api.GroupID]PresetParameters + heads map[api.HeadID]PresetParameters } -func (preset *Preset) initAll(heads headMap, groups groupMap) { +func (preset *Preset) initAll(heads heads, groups groups) { preset.allHeads = heads preset.allGroups = groups } -func (preset *Preset) initGroup(group *Group, presetParameters PresetParameters) error { +func (preset *Preset) initGroup(group *Group, params api.PresetConfigParams) error { var groupParameters = PresetParameters{ - group: group, - Intensity: presetParameters.Intensity, - Color: presetParameters.Color, - } + intensityHandler: group.intensity, + colorHandler: group.color, - if err := groupParameters.init(); err != nil { - return err + Intensity: params.Intensity, + Color: params.Color, } - preset.Groups[group.id] = groupParameters + preset.groups[group.id] = groupParameters return nil } -func (preset *Preset) initHead(head *Head, presetParameters PresetParameters) error { +func (preset *Preset) initHead(head *Head, params api.PresetConfigParams) error { var headParameters = PresetParameters{ - head: head, - Intensity: presetParameters.Intensity, - Color: presetParameters.Color, - } + intensityHandler: head.intensity, + colorHandler: head.color, - if err := headParameters.init(); err != nil { - return err + Intensity: params.Intensity, + Color: params.Color, } - preset.Heads[head.id] = headParameters + preset.heads[head.id] = headParameters return nil } -func (preset *Preset) GetREST() (web.Resource, error) { - return preset, nil -} +func (preset *Preset) Get() api.Preset { + return api.Preset{ + ID: preset.id, + Config: preset.config, -func (preset *Preset) PostREST() (web.Resource, error) { - return &APIPresetParams{preset: preset}, nil -} - -// API POST -type APIPresetParams struct { - preset *Preset - - Intensity *Intensity + Groups: preset.config.Groups, + Heads: preset.config.Heads, + } } -func (apiPresetParams APIPresetParams) Apply() error { - var preset = apiPresetParams.preset - var event APIEvents +func (preset *Preset) Set(params api.PresetParams) error { + var event eventBuilder preset.log.Info("Apply") if allParams := preset.Config.All; allParams != nil { - for _, head := range preset.allHeads { - var headParams = PresetParameters{ - head: head, - } + if params.Intensity != nil { + allParams = allParams.Scale(*params.Intensity) + } + for _, head := range preset.allHeads { // all params are optional - if allParams.Intensity != nil && head.parameters.Intensity != nil { - headParams.Intensity = allParams.Intensity - } - - if allParams.Color != nil && head.parameters.Color != nil { - headParams.Color = allParams.Color - } - - if apiPresetParams.Intensity != nil { - headParams = headParams.scaleIntensity(*apiPresetParams.Intensity) + if allParams.Intensity != nil && head.intensity != nil { + head.intensity.SetIntensity(*params.Intensity) } - if err := headParams.init(); err != nil { - return err - } else if err := headParams.Apply(); err != nil { - return err + if allParams.Color != nil && head.color != nil { + head.color.SetColor(*params.Color) } } // update everything event.addHeads(preset.allHeads) event.addGroups(preset.allGroups) - - // also update groups after heads have been updated - for _, group := range apiPresetParams.preset.allGroups { - if err := group.Apply(); err != nil { - return err - } - } } - for _, apiGroupParams := range apiPresetParams.preset.Groups { + for _, groupParams := range preset.groups { if apiPresetParams.Intensity != nil { apiGroupParams = apiGroupParams.scaleIntensity(*apiPresetParams.Intensity) } - if err := apiGroupParams.Apply(); err != nil { - return err - } + groupParams.apply() event.addGroup(apiGroupParams.group) event.addHeads(apiGroupParams.group.heads) } - for _, apiHeadParams := range apiPresetParams.preset.Heads { + for _, headParams := range preset.heads { if apiPresetParams.Intensity != nil { apiHeadParams = apiHeadParams.scaleIntensity(*apiPresetParams.Intensity) } @@ -272,11 +221,11 @@ func (apiPresetParams APIPresetParams) Apply() error { // GET /config/preset.toml type httpConfigPreset struct { - heads *Heads + controller *Controller } // Export a preset configuration from the current state -func (heads *Heads) ConfigPreset() PresetConfig { +func (controller *Controller) ConfigPreset() api.PresetConfig { var allParameters = PresetParameters{ Intensity: &APIIntensity{}, Color: &APIColor{}, @@ -288,7 +237,7 @@ func (heads *Heads) ConfigPreset() PresetConfig { Heads: make(map[string]PresetParameters), } - for groupID, group := range heads.groups { + for groupID, group := range controller.groups { var presetParameters = PresetParameters{ Intensity: group.intensity.makeAPI(), Color: group.color.makeAPI(), @@ -303,7 +252,7 @@ func (heads *Heads) ConfigPreset() PresetConfig { presetConfig.Groups[string(groupID)] = presetParameters } - for headID, head := range heads.heads { + for headID, head := range controller.heads { var presetParameters = PresetParameters{ Intensity: head.parameters.Intensity.makeAPI(), Color: head.parameters.Color.makeAPI(), diff --git a/heads/web.go b/heads/web.go index 236b86e..c483f05 100644 --- a/heads/web.go +++ b/heads/web.go @@ -3,56 +3,50 @@ package heads import ( "net/http" + "github.com/qmsk/dmx/api" "github.com/qmsk/go-web" ) -type API struct { - Outputs APIOutputs - Heads APIHeads - Groups APIGroups - Presets presetMap +func (controller *Controller) WebAPI() web.API { + return web.MakeAPI(controller) } -func (heads *Heads) WebAPI() web.API { - return web.MakeAPI(heads) -} - -func (heads *Heads) Index(name string) (web.Resource, error) { +func (controller *Controller) Index(name string) (web.Resource, error) { switch name { case "": - return heads, nil + return controller, nil case "groups": - return heads.groups, nil + return &groupsView{controller.groups}, nil case "outputs": - return heads.outputs, nil + return &outputsView{controller.outputs}, nil case "heads": - return heads.heads, nil + return &headsView{controller.heads}, nil case "presets": - return heads.presets, nil + return controller.presets, nil default: return nil, nil } } -func (heads *Heads) makeAPI() API { - return API{ - Outputs: heads.outputs.makeAPI(), - Heads: heads.heads.makeAPI(), - Groups: heads.groups.makeAPI(), - Presets: heads.presets, +func (controller *Controller) makeAPI() api.Index { + return api.Index{ + Outputs: controller.outputs.makeAPI(), + Heads: controller.heads.makeAPI(), + Groups: controller.groups.makeAPI(), + Presets: controller.presets.makeAPI(), } } -func (heads *Heads) GetREST() (web.Resource, error) { - return heads.makeAPI(), nil +func (controller *Controller) GetREST() (web.Resource, error) { + return controller.makeAPI(), nil } -func (heads *Heads) Apply() error { - heads.log.Info("Apply") +func (controller *Controller) Apply() error { + controller.log.Info("Apply") // Refresh DMX output - if err := heads.Refresh(); err != nil { - heads.log.Warn("Refresh: ", err) + if err := controller.Refresh(); err != nil { + controller.log.Warn("Refresh: ", err) return err } @@ -60,6 +54,6 @@ func (heads *Heads) Apply() error { return nil } -func (heads *Heads) WebConfigPreset() http.Handler { - return httpConfigPreset{heads} +func (controller *Controller) WebConfigPreset() http.Handler { + return httpConfigPreset{controller} }