Browse Source
Squashed commit of the following:
Squashed commit of the following:
commitasmodeus 3.8.062e7cb88f4
Author: Gisle Aune <dev@gisle.me> Date: Mon Feb 28 22:52:06 2022 +0100 refactor color system to be separate from models, also move errors out of models. commitdf865574bf
Author: Gisle Aune <dev@gisle.me> Date: Mon Feb 21 19:27:42 2022 +0100 rework color system and make it work with drivers.
Gisle Aune
3 years ago
36 changed files with 680 additions and 375 deletions
-
3app/api/devices.go
-
5app/api/presets.go
-
28app/api/util.go
-
3cmd/bridgetest/main.go
-
48cmd/xy/main.go
-
369internal/color/color.go
-
17internal/color/hs.go
-
29internal/color/rgb.go
-
84internal/color/xy.go
-
5internal/drivers/hue/bridge.go
-
3internal/drivers/hue/driver.go
-
8internal/drivers/hue/state.go
-
15internal/drivers/hue2/bridge.go
-
4internal/drivers/hue2/client.go
-
8internal/drivers/hue2/data.go
-
3internal/drivers/hue2/driver.go
-
18internal/drivers/lifx/bridge.go
-
6internal/drivers/lifx/client.go
-
6internal/drivers/lifx/packet.go
-
14internal/drivers/lifx/payloads.go
-
26internal/drivers/lifx/state.go
-
19internal/drivers/mill/bridge.go
-
38internal/drivers/nanoleaf/bridge.go
-
5internal/drivers/nanoleaf/driver.go
-
8internal/drivers/provider.go
-
2internal/lerrors/errors.go
-
21internal/mysql/devicerepo.go
-
18internal/mysql/presetrepo.go
-
6internal/mysql/util.go
-
3models/colorpreset.go
-
129models/colorvalue.go
-
32models/device.go
-
15models/scene.go
-
9scripts/20220220121712_color_preset_wipe.sql
-
9scripts/20220220121724_color_preset_add_column_value.sql
-
9scripts/20220220124631_device_state_color.sql
@ -1,49 +1,15 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/json" |
|||
"fmt" |
|||
"git.aiterp.net/lucifer/new-server/internal/drivers/hue2" |
|||
"git.aiterp.net/lucifer/new-server/models" |
|||
"git.aiterp.net/lucifer/new-server/internal/color" |
|||
"log" |
|||
) |
|||
|
|||
func main() { |
|||
client := hue2.NewClient("10.80.8.8", "o2XKGgmVUGNBghYFdLUCVuinOTMxFH4pHV9PuTbU") |
|||
bridge := hue2.NewBridge(client) |
|||
|
|||
err := bridge.RefreshAll(context.Background()) |
|||
if err != nil { |
|||
log.Fatalln(err) |
|||
} |
|||
j, _ := json.Marshal(bridge.GenerateDevices()) |
|||
fmt.Println(string(j)) |
|||
|
|||
ch := make(chan models.Event) |
|||
go func() { |
|||
for event := range ch { |
|||
log.Println("EVENT", event.Name, event.Payload) |
|||
} |
|||
}() |
|||
|
|||
for i, dev := range bridge.GenerateDevices() { |
|||
device := dev |
|||
switch device.InternalID { |
|||
case "6d5a45b0-ec69-4417-8588-717358b05086": |
|||
c, _ := models.ParseColorValue("xy:0.22,0.18") |
|||
device.State.Color = c |
|||
device.State.Intensity = 0.3 |
|||
case "a71128f4-5295-4ae4-9fbc-5541abc8739b": |
|||
c, _ := models.ParseColorValue("k:6500") |
|||
device.State.Color = c |
|||
device.State.Intensity = 0.2 |
|||
} |
|||
|
|||
device.ID = i + 1 |
|||
bridge.Update(device) |
|||
} |
|||
|
|||
err = bridge.Run(context.Background(), ch) |
|||
log.Println(err) |
|||
cv, _ := color.Parse("xy:0.1944,0.0942") |
|||
hs, _ := cv.ToHS() |
|||
log.Println(cv.String()) |
|||
log.Println(hs.String()) |
|||
xy, _ := hs.ToXY() |
|||
log.Println(xy.String()) |
|||
} |
@ -0,0 +1,369 @@ |
|||
package color |
|||
|
|||
import ( |
|||
"fmt" |
|||
"git.aiterp.net/lucifer/new-server/internal/lerrors" |
|||
"github.com/lucasb-eyer/go-colorful" |
|||
"strconv" |
|||
"strings" |
|||
) |
|||
|
|||
type Color struct { |
|||
RGB *RGB `json:"rgb,omitempty"` |
|||
HS *HueSat `json:"hs,omitempty"` |
|||
K *int `json:"k,omitempty"` |
|||
XY *XY `json:"xy,omitempty"` |
|||
} |
|||
|
|||
func (col *Color) IsHueSat() bool { |
|||
return col.HS != nil |
|||
} |
|||
|
|||
func (col *Color) IsHueSatKelvin() bool { |
|||
return col.HS != nil && col.K != nil |
|||
} |
|||
|
|||
func (col *Color) IsKelvin() bool { |
|||
return col.K != nil |
|||
} |
|||
|
|||
func (col *Color) IsEmpty() bool { |
|||
return *col == Color{} |
|||
} |
|||
|
|||
func (col *Color) SetK(k int) { |
|||
*col = Color{K: &k} |
|||
} |
|||
|
|||
func (col *Color) SetXY(xy XY) { |
|||
*col = Color{XY: &xy} |
|||
} |
|||
|
|||
// ToRGB tries to copy the color to an RGB color. If it's already RGB, it will be plainly copied, but HS
|
|||
// will be returned. If ok is false, then no copying has occurred and cv2 will be blank.
|
|||
func (col *Color) ToRGB() (col2 Color, ok bool) { |
|||
if col.RGB != nil { |
|||
rgb := *col.RGB |
|||
col2 = Color{RGB: &rgb} |
|||
ok = true |
|||
} else if col.HS != nil { |
|||
rgb := col.HS.ToRGB() |
|||
col2 = Color{RGB: &rgb} |
|||
ok = true |
|||
} else if col.XY != nil { |
|||
rgb := col.XY.ToRGB() |
|||
col2 = Color{RGB: &rgb} |
|||
ok = true |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
func (col *Color) ToHS() (col2 Color, ok bool) { |
|||
if col.HS != nil { |
|||
hs := *col.HS |
|||
col2 = Color{HS: &hs} |
|||
ok = true |
|||
} else if col.RGB != nil { |
|||
hs := col.RGB.ToHS() |
|||
col2 = Color{HS: &hs} |
|||
ok = true |
|||
} else if col.XY != nil { |
|||
hs := col.XY.ToHS() |
|||
col2 = Color{HS: &hs} |
|||
ok = true |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
func (col *Color) ToHSK() (col2 Color, ok bool) { |
|||
k := 4000 |
|||
|
|||
if col.HS != nil { |
|||
hs := *col.HS |
|||
col2 = Color{HS: &hs} |
|||
|
|||
if col.K != nil { |
|||
k = *col.K |
|||
} |
|||
col2.K = &k |
|||
|
|||
ok = true |
|||
} else if col.RGB != nil { |
|||
hs := col.RGB.ToHS() |
|||
col2 = Color{HS: &hs} |
|||
col2.K = &k |
|||
ok = true |
|||
} else if col.XY != nil { |
|||
hs := col.XY.ToHS() |
|||
col2 = Color{HS: &hs} |
|||
col2.K = &k |
|||
ok = true |
|||
} else if col.K != nil { |
|||
k = *col.K |
|||
col2.HS = &HueSat{Hue: 0, Sat: 0} |
|||
col2.K = &k |
|||
ok = true |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
// ToXY tries to copy the color to an XY color.
|
|||
func (col *Color) ToXY() (col2 Color, ok bool) { |
|||
if col.XY != nil { |
|||
xy := *col.XY |
|||
col2 = Color{XY: &xy} |
|||
ok = true |
|||
} else if col.HS != nil { |
|||
xy := col.HS.ToXY() |
|||
col2 = Color{XY: &xy} |
|||
ok = true |
|||
} else if col.RGB != nil { |
|||
xy := col.RGB.ToXY() |
|||
col2 = Color{XY: &xy} |
|||
ok = true |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
func (col *Color) Interpolate(other Color, fac float64) Color { |
|||
// Special case for kelvin values.
|
|||
if col.IsKelvin() && other.IsKelvin() { |
|||
k1 := *col.K |
|||
k2 := *col.K |
|||
k3 := k1 + int(float64(k2-k1)*fac) |
|||
return Color{K: &k3} |
|||
} |
|||
|
|||
// Get the colorful values.
|
|||
cvCF := col.colorful() |
|||
otherCF := other.colorful() |
|||
|
|||
// Blend and normalize
|
|||
blended := cvCF.BlendLuv(otherCF, fac) |
|||
blendedHue, blendedSat, _ := blended.Hsv() |
|||
blendedHs := HueSat{Hue: blendedHue, Sat: blendedSat} |
|||
|
|||
// Convert to the first's type
|
|||
switch col.Kind() { |
|||
case "rgb": |
|||
rgb := blendedHs.ToRGB() |
|||
return Color{RGB: &rgb} |
|||
case "xy": |
|||
xy := blendedHs.ToXY() |
|||
return Color{XY: &xy} |
|||
default: |
|||
return Color{HS: &blendedHs} |
|||
} |
|||
} |
|||
|
|||
func (col *Color) Kind() string { |
|||
switch { |
|||
case col.RGB != nil: |
|||
return "rgb" |
|||
case col.XY != nil: |
|||
return "xy" |
|||
case col.HS != nil && col.K != nil: |
|||
return "hsk" |
|||
case col.HS != nil: |
|||
return "hs" |
|||
case col.K != nil: |
|||
return "k" |
|||
default: |
|||
return "" |
|||
} |
|||
} |
|||
|
|||
func (col *Color) String() string { |
|||
switch { |
|||
case col.RGB != nil: |
|||
return fmt.Sprintf("rgb:%.3f,%.3f,%.3f", col.RGB.Red, col.RGB.Green, col.RGB.Blue) |
|||
case col.XY != nil: |
|||
return fmt.Sprintf("xy:%.4f,%.4f", col.XY.X, col.XY.Y) |
|||
case col.HS != nil && col.K != nil: |
|||
return fmt.Sprintf("hsk:%.4f,%.3f,%d", col.HS.Hue, col.HS.Sat, *col.K) |
|||
case col.HS != nil: |
|||
return fmt.Sprintf("hs:%.4f,%.3f", col.HS.Hue, col.HS.Sat) |
|||
case col.K != nil: |
|||
return fmt.Sprintf("k:%d", *col.K) |
|||
default: |
|||
return "" |
|||
} |
|||
} |
|||
|
|||
func (col *Color) colorful() colorful.Color { |
|||
switch { |
|||
case col.HS != nil: |
|||
return colorful.Hsv(col.HS.Hue, col.HS.Sat, 1) |
|||
case col.RGB != nil: |
|||
return colorful.Color{R: col.RGB.Red, G: col.RGB.Green, B: col.RGB.Blue} |
|||
case col.XY != nil: |
|||
return colorful.Xyy(col.XY.X, col.XY.Y, 0.5) |
|||
default: |
|||
return colorful.Color{R: 255, B: 255, G: 255} |
|||
} |
|||
} |
|||
|
|||
func Parse(raw string) (col Color, err error) { |
|||
if raw == "" { |
|||
return |
|||
} |
|||
|
|||
tokens := strings.SplitN(raw, ":", 2) |
|||
if len(tokens) != 2 { |
|||
err = lerrors.ErrBadInput |
|||
return |
|||
} |
|||
|
|||
switch tokens[0] { |
|||
case "kelvin", "k": |
|||
{ |
|||
parsedPart, err := strconv.Atoi(tokens[1]) |
|||
if err != nil { |
|||
err = lerrors.ErrBadInput |
|||
break |
|||
} |
|||
|
|||
col.K = &parsedPart |
|||
} |
|||
|
|||
case "xy": |
|||
{ |
|||
parts := strings.Split(tokens[1], ",") |
|||
if len(parts) < 2 { |
|||
err = lerrors.ErrUnknownColorFormat |
|||
return |
|||
} |
|||
|
|||
x, err1 := strconv.ParseFloat(parts[0], 64) |
|||
y, err2 := strconv.ParseFloat(parts[1], 64) |
|||
if err1 != nil || err2 != nil { |
|||
err = lerrors.ErrBadInput |
|||
break |
|||
} |
|||
|
|||
col.XY = &XY{x, y} |
|||
} |
|||
|
|||
case "hs": |
|||
{ |
|||
parts := strings.Split(tokens[1], ",") |
|||
if len(parts) < 2 { |
|||
err = lerrors.ErrUnknownColorFormat |
|||
return |
|||
} |
|||
|
|||
part1, err1 := strconv.ParseFloat(parts[0], 64) |
|||
part2, err2 := strconv.ParseFloat(parts[1], 64) |
|||
if err1 != nil || err2 != nil { |
|||
err = lerrors.ErrBadInput |
|||
break |
|||
} |
|||
|
|||
col.HS = &HueSat{Hue: part1, Sat: part2} |
|||
} |
|||
|
|||
case "hsk": |
|||
{ |
|||
parts := strings.Split(tokens[1], ",") |
|||
if len(parts) < 3 { |
|||
err = lerrors.ErrUnknownColorFormat |
|||
return |
|||
} |
|||
|
|||
part1, err1 := strconv.ParseFloat(parts[0], 64) |
|||
part2, err2 := strconv.ParseFloat(parts[1], 64) |
|||
part3, err3 := strconv.Atoi(parts[2]) |
|||
if err1 != nil || err2 != nil || err3 != nil { |
|||
err = lerrors.ErrBadInput |
|||
break |
|||
} |
|||
|
|||
col.HS = &HueSat{Hue: part1, Sat: part2} |
|||
col.K = &part3 |
|||
} |
|||
|
|||
case "rgb": |
|||
{ |
|||
if strings.HasPrefix(tokens[1], "#") { |
|||
hex := tokens[1][1:] |
|||
if !validHex(hex) { |
|||
err = lerrors.ErrBadInput |
|||
break |
|||
} |
|||
|
|||
if len(hex) == 6 { |
|||
col.RGB = &RGB{ |
|||
Red: float64(hex2num(hex[0:2])) / 255.0, |
|||
Green: float64(hex2num(hex[2:4])) / 255.0, |
|||
Blue: float64(hex2num(hex[4:6])) / 255.0, |
|||
} |
|||
} else if len(hex) == 3 { |
|||
col.RGB = &RGB{ |
|||
Red: float64(hex2digit(hex[0])) / 15.0, |
|||
Green: float64(hex2digit(hex[1])) / 15.0, |
|||
Blue: float64(hex2digit(hex[2])) / 15.0, |
|||
} |
|||
} else { |
|||
err = lerrors.ErrUnknownColorFormat |
|||
return |
|||
} |
|||
} else { |
|||
parts := strings.Split(tokens[1], ",") |
|||
if len(parts) < 3 { |
|||
err = lerrors.ErrUnknownColorFormat |
|||
return |
|||
} |
|||
|
|||
part1, err1 := strconv.ParseFloat(parts[0], 64) |
|||
part2, err2 := strconv.ParseFloat(parts[1], 64) |
|||
part3, err3 := strconv.ParseFloat(parts[2], 64) |
|||
if err1 != nil || err2 != nil || err3 != nil { |
|||
err = lerrors.ErrBadInput |
|||
break |
|||
} |
|||
|
|||
col.RGB = &RGB{Red: part1, Green: part2, Blue: part3} |
|||
} |
|||
|
|||
normalizedRGB := col.RGB.ToHS().ToRGB() |
|||
col.RGB = &normalizedRGB |
|||
} |
|||
|
|||
default: |
|||
err = lerrors.ErrUnknownColorFormat |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
func validHex(h string) bool { |
|||
for _, ch := range h { |
|||
if !((ch >= 'a' && ch <= 'f') || (ch >= '0' || ch <= '9')) { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
return true |
|||
} |
|||
|
|||
func hex2num(s string) int { |
|||
v := 0 |
|||
for _, h := range s { |
|||
v *= 16 |
|||
v += hex2digit(byte(h)) |
|||
} |
|||
|
|||
return v |
|||
} |
|||
|
|||
func hex2digit(h byte) int { |
|||
if h >= 'a' && h <= 'f' { |
|||
return 10 + int(h-'a') |
|||
} else { |
|||
return int(h - '0') |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
package color |
|||
|
|||
import "github.com/lucasb-eyer/go-colorful" |
|||
|
|||
type HueSat struct { |
|||
Hue float64 `json:"hue"` |
|||
Sat float64 `json:"sat"` |
|||
} |
|||
|
|||
func (hs HueSat) ToXY() XY { |
|||
return hs.ToRGB().ToXY() |
|||
} |
|||
|
|||
func (hs HueSat) ToRGB() RGB { |
|||
c := colorful.Hsv(hs.Hue, hs.Sat, 1) |
|||
return RGB{Red: c.R, Green: c.G, Blue: c.B} |
|||
} |
@ -0,0 +1,29 @@ |
|||
package color |
|||
|
|||
import "github.com/lucasb-eyer/go-colorful" |
|||
|
|||
type RGB struct { |
|||
Red float64 `json:"red"` |
|||
Green float64 `json:"green"` |
|||
Blue float64 `json:"blue"` |
|||
} |
|||
|
|||
func (rgb RGB) AtIntensity(intensity float64) RGB { |
|||
hue, sat, _ := colorful.Color{R: rgb.Red, G: rgb.Green, B: rgb.Blue}.Hsv() |
|||
hsv2 := colorful.Hsv(hue, sat, intensity) |
|||
return RGB{Red: hsv2.R, Green: hsv2.G, Blue: hsv2.B} |
|||
} |
|||
|
|||
func (rgb RGB) ToHS() HueSat { |
|||
hue, sat, _ := colorful.Color{R: rgb.Red, G: rgb.Green, B: rgb.Blue}.Hsv() |
|||
return HueSat{Hue: hue, Sat: sat} |
|||
} |
|||
|
|||
func (rgb RGB) ToXY() XY { |
|||
x, y, z := (colorful.Color{R: rgb.Red, G: rgb.Green, B: rgb.Blue}).Xyz() |
|||
|
|||
return XY{ |
|||
X: x / (x + y + z), |
|||
Y: y / (x + y + z), |
|||
} |
|||
} |
@ -1,14 +1,16 @@ |
|||
package drivers |
|||
|
|||
import "git.aiterp.net/lucifer/new-server/models" |
|||
import ( |
|||
"git.aiterp.net/lucifer/new-server/internal/lerrors" |
|||
"git.aiterp.net/lucifer/new-server/models" |
|||
) |
|||
|
|||
type DriverMap map[models.DriverKind]models.Driver |
|||
|
|||
func (m DriverMap) Provide(kind models.DriverKind) (models.Driver, error) { |
|||
if m[kind] == nil { |
|||
return nil, models.ErrUnknownDriver |
|||
return nil, lerrors.ErrUnknownDriver |
|||
} |
|||
|
|||
return m[kind], nil |
|||
} |
|||
|
@ -1,4 +1,4 @@ |
|||
package models |
|||
package lerrors |
|||
|
|||
import "errors" |
|||
|
@ -1,129 +0,0 @@ |
|||
package models |
|||
|
|||
import ( |
|||
"fmt" |
|||
"math" |
|||
"strconv" |
|||
"strings" |
|||
) |
|||
|
|||
type ColorValue struct { |
|||
Hue float64 `json:"h,omitempty"` // 0..360
|
|||
Saturation float64 `json:"s,omitempty"` // 0..=1
|
|||
Kelvin int `json:"kelvin,omitempty"` |
|||
XY *ColorXY `json:"xy,omitempty"` |
|||
} |
|||
|
|||
func (c *ColorValue) IsEmpty() bool { |
|||
return c.XY == nil && c.Kelvin == 0 && c.Saturation == 0 && c.Hue == 0 |
|||
} |
|||
|
|||
func (c *ColorValue) IsHueSat() bool { |
|||
return !c.IsKelvin() |
|||
} |
|||
|
|||
func (c *ColorValue) IsKelvin() bool { |
|||
return !c.IsXY() && c.Kelvin > 0 |
|||
} |
|||
|
|||
func (c *ColorValue) IsXY() bool { |
|||
return c.XY != nil |
|||
} |
|||
|
|||
// ToXY converts the color to XY if possible. If the color already is XY, it returns
|
|||
// a copy of its held value. There are no guarantees of conforming to a gamut, however.
|
|||
func (c *ColorValue) ToXY() (xy ColorXY, ok bool) { |
|||
if c.XY != nil { |
|||
xy = *c.XY |
|||
ok = true |
|||
} else if c.Kelvin > 0 && c.Hue < 0.001 && c.Saturation <= 0.001 { |
|||
ok = false |
|||
} else { |
|||
xy = hsToXY(c.Hue, c.Saturation) |
|||
ok = true |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
func (c *ColorValue) String() string { |
|||
if c.Kelvin > 0 { |
|||
return fmt.Sprintf("k:%d", c.Kelvin) |
|||
} |
|||
|
|||
return fmt.Sprintf("hs:%.4g,%.3g", c.Hue, c.Saturation) |
|||
} |
|||
|
|||
func ParseColorValue(raw string) (ColorValue, error) { |
|||
tokens := strings.SplitN(raw, ":", 2) |
|||
if len(tokens) != 2 { |
|||
return ColorValue{}, ErrBadInput |
|||
} |
|||
|
|||
switch tokens[0] { |
|||
case "kelvin", "k": |
|||
{ |
|||
parsedPart, err := strconv.Atoi(tokens[1]) |
|||
if err != nil { |
|||
return ColorValue{}, ErrBadInput |
|||
} |
|||
|
|||
return ColorValue{Kelvin: parsedPart}, nil |
|||
} |
|||
|
|||
case "xy": |
|||
{ |
|||
parts := strings.Split(tokens[1], ",") |
|||
if len(parts) < 2 { |
|||
return ColorValue{}, ErrUnknownColorFormat |
|||
} |
|||
|
|||
x, err1 := strconv.ParseFloat(parts[0], 64) |
|||
y, err2 := strconv.ParseFloat(parts[1], 64) |
|||
if err1 != nil || err2 != nil { |
|||
return ColorValue{}, ErrBadInput |
|||
} |
|||
|
|||
return ColorValue{XY: &ColorXY{X: x, Y: y}}, nil |
|||
} |
|||
|
|||
case "hs": |
|||
{ |
|||
parts := strings.Split(tokens[1], ",") |
|||
if len(parts) < 2 { |
|||
return ColorValue{}, ErrUnknownColorFormat |
|||
} |
|||
|
|||
part1, err1 := strconv.ParseFloat(parts[0], 64) |
|||
part2, err2 := strconv.ParseFloat(parts[1], 64) |
|||
if err1 != nil || err2 != nil { |
|||
return ColorValue{}, ErrBadInput |
|||
} |
|||
|
|||
return ColorValue{Hue: math.Mod(part1, 360), Saturation: math.Min(math.Max(part2, 0), 1)}, nil |
|||
} |
|||
|
|||
case "hsk": |
|||
{ |
|||
parts := strings.Split(tokens[1], ",") |
|||
if len(parts) < 3 { |
|||
return ColorValue{}, ErrUnknownColorFormat |
|||
} |
|||
|
|||
part1, err1 := strconv.ParseFloat(parts[0], 64) |
|||
part2, err2 := strconv.ParseFloat(parts[1], 64) |
|||
part3, err3 := strconv.Atoi(parts[2]) |
|||
if err1 != nil || err2 != nil || err3 != nil { |
|||
return ColorValue{}, ErrBadInput |
|||
} |
|||
|
|||
return ColorValue{ |
|||
Hue: math.Mod(part1, 360), |
|||
Saturation: math.Min(math.Max(part2, 0), 1), |
|||
Kelvin: part3, |
|||
}, nil |
|||
} |
|||
} |
|||
|
|||
return ColorValue{}, ErrUnknownColorFormat |
|||
} |
@ -0,0 +1,9 @@ |
|||
-- +goose Up |
|||
-- +goose StatementBegin |
|||
TRUNCATE color_preset; |
|||
-- +goose StatementEnd |
|||
|
|||
-- +goose Down |
|||
-- +goose StatementBegin |
|||
SELECT 'down SQL query'; |
|||
-- +goose StatementEnd |
@ -0,0 +1,9 @@ |
|||
-- +goose Up |
|||
-- +goose StatementBegin |
|||
ALTER TABLE color_preset ADD COLUMN value VARCHAR(255) NOT NULL DEFAULT 'hs:0,0'; |
|||
-- +goose StatementEnd |
|||
|
|||
-- +goose Down |
|||
-- +goose StatementBegin |
|||
ALTER TABLE color_preset DROP COLUMN value; |
|||
-- +goose StatementEnd |
@ -0,0 +1,9 @@ |
|||
-- +goose Up |
|||
-- +goose StatementBegin |
|||
ALTER TABLE device_state ADD COLUMN color VARCHAR(255) NOT NULL DEFAULT 'hs:0,0'; |
|||
-- +goose StatementEnd |
|||
|
|||
-- +goose Down |
|||
-- +goose StatementBegin |
|||
ALTER TABLE device_state DROP COLUMN IF EXISTS color; |
|||
-- +goose StatementEnd |
Write
Preview
Loading…
Cancel
Save
Reference in new issue