commit ec0ccab3a95b979c1e1c924dde4a3cadfdc76b2d Author: Dreaded_X Date: Mon Jan 24 21:57:36 2022 +0100 First commit diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..891c361 --- /dev/null +++ b/.air.toml @@ -0,0 +1,36 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata", "storage"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + kill_delay = "0s" + log = "build-errors.log" + send_interrupt = false + stop_on_error = true + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e826079 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git/ +storage/ +automation +.env diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..aac7d3c --- /dev/null +++ b/.drone.yml @@ -0,0 +1,47 @@ +kind: pipeline +type: docker +name: default + +steps: + - name: build + image: docker + volumes: + - name: socket + path: /var/run/docker.sock + commands: + - docker build -t inventory . + + - name: deploy + image: docker + volumes: + - name: socket + path: /var/run/docker.sock + environment: + MQTT_HOST: + from_secret: MQTT_HOST + MQTT_PORT: + from_secret: MQTT_PORT + MQTT_USER: + from_secret: MQTT_USER + MQTT_PASS: + from_secret: MQTT_PASS + HUE_BRIDGE: + from_secret: HUE_BRIDGE + commands: + - docker stop automation || true + + - docker rm automation || true + + - docker run -e MQTT_HOST=$MQTT_HOST -e MQTT_PORT=$MQTT_PORT -e MQTT_USER=$MQTT_USER -e MQTT_PASS=$MQTT_PASS -e HUE_BRIDGE=$HUE_BRIDGE --network mqtt --name automation -d automation + + when: + branch: + - master + event: + exclude: + - pull_request + +volumes: + - name: socket + host: + path: /var/run/docker.sock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1cdc3bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +storage +automation +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0a474f7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:alpine as build-automation + +WORKDIR /src +COPY ./go.mod . +COPY ./go.sum . +RUN go mod download + +COPY . . + +RUN go build + + +FROM golang:alpine + +WORKDIR /app +COPY --from=build-automation /src/automation /app/automation + +CMD ["/app/automation"] diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..77066fe --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module automation + +go 1.17 + +require ( + github.com/amimof/huego v1.2.1 // indirect + github.com/eclipse/paho.mqtt.golang v1.3.5 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/joho/godotenv v1.4.0 // indirect + github.com/k0kubun/pp v3.0.1+incompatible // indirect + github.com/kelvins/sunrisesunset v0.0.0-20210220141756-39fa1bd816d5 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect + golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5b25028 --- /dev/null +++ b/go.sum @@ -0,0 +1,32 @@ +github.com/amimof/huego v1.2.1 h1:kd36vsieclW4fZ4Vqii9DNU2+6ptWWtkp4OG0AXM8HE= +github.com/amimof/huego v1.2.1/go.mod h1:z1Sy7Rrdzmb+XsGHVEhODrRJRDq4RCFW7trCI5cKmeA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= +github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= +github.com/kelvins/sunrisesunset v0.0.0-20210220141756-39fa1bd816d5 h1:ouekCqYkMw4QXFCaLyYqjBe99/MUW4Qf3DJhCRh1G18= +github.com/kelvins/sunrisesunset v0.0.0-20210220141756-39fa1bd816d5/go.mod h1:3oZ7G+fb8Z8KF+KPHxeDO3GWpEjgvk/f+d/yaxmDRT4= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c0560d9 --- /dev/null +++ b/main.go @@ -0,0 +1,230 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/amimof/huego" + MQTT "github.com/eclipse/paho.mqtt.golang" + "github.com/joho/godotenv" + "github.com/kelvins/sunrisesunset" +) + +type Location struct { + Longitude float32 `json:"lon"` + Latitude float32 `json:"lat"` + Altitude int `json:"alt"` + InRegions []string `json:"inregions"` +} + +var defaultHandler MQTT.MessageHandler = func(client MQTT.Client, msg MQTT.Message) { + fmt.Printf("TOPIC: %s\n", msg.Topic()) + fmt.Printf("MSG: %s\n", msg.Payload()) +} + +const groupId = 1 +const brightness = 100 + +func getNextSunriseSunset() (time.Time, time.Time) { + p := sunrisesunset.Parameters{ + Latitude: 51.9808334, + Longitude: 4.347818, + Date: time.Now(), + } + + sunrise, sunset, err := p.GetSunriseSunset() + if err != nil { + panic(err) + } + + p2 := sunrisesunset.Parameters{ + Latitude: 51.9808334, + Longitude: 4.347818, + Date: time.Now().Add(time.Hour * 24), + } + sunrise2, sunset2, err := p2.GetSunriseSunset() + + now := time.Now() + if now.After(sunrise) { + sunrise = sunrise2 + } + + if now.After(sunset) { + sunset = sunset2 + } + + return sunrise, sunset +} + +func isDay() bool { + p := sunrisesunset.Parameters{ + Latitude: 51.9808334, + Longitude: 4.347818, + Date: time.Now(), + } + + sunrise, sunset, err := p.GetSunriseSunset() + if err != nil { + panic(err) + } + + return time.Now().After(sunrise) && time.Now().Before(sunset) +} + +func allLightsOff(bridge *huego.Bridge) { + lights, _ := bridge.GetLights() + for _, l := range lights { + l.Off() + } +} + +func main() { + _ = godotenv.Load() + + host, ok := os.LookupEnv("MQTT_HOST") + if !ok { + host = "localhost" + } + port, ok := os.LookupEnv("MQTT_PORT") + if !ok { + port = "1883" + } + user, ok := os.LookupEnv("MQTT_USER") + if !ok { + user = "test" + } + pass, ok := os.LookupEnv("MQTT_PASS") + if !ok { + pass = "test" + } + login, ok := os.LookupEnv("HUE_BRIDGE") + + fmt.Println(host, port, user, pass, login) + + halt := make(chan os.Signal, 1) + signal.Notify(halt, os.Interrupt, syscall.SIGTERM) + + bridge, _ := huego.Discover() + bridge = bridge.Login(login) + livingRoom, _ := bridge.GetGroup(groupId) + + opts := MQTT.NewClientOptions().AddBroker(fmt.Sprintf("%s:%s", host, port)) + opts.SetClientID("automation") + opts.SetDefaultPublishHandler(defaultHandler) + opts.SetUsername(user) + opts.SetPassword(pass) + + c := MQTT.NewClient(opts) + if token := c.Connect(); token.Wait() && token.Error() != nil { + panic(token.Error()) + } + + home := make(chan bool, 1) + var locationHandler MQTT.MessageHandler = func(client MQTT.Client, msg MQTT.Message) { + var location Location + json.Unmarshal(msg.Payload(), &location) + + fmt.Println(location) + temp := false + for _, region := range location.InRegions { + if region == "home" { + temp = true + } + } + fmt.Println(temp) + + home <- temp + } + + if token := c.Subscribe("owntracks/mqtt/apollo", 0, locationHandler); token.Wait() && token.Error() != nil { + fmt.Println(token.Error()) + os.Exit(1) + } + + // Setup initial states + isHome := false + + sunrise, sunset := getNextSunriseSunset() + sunriseTimer := time.NewTimer(sunrise.Sub(time.Now())) + sunsetTimer := time.NewTimer(sunset.Sub(time.Now())) + + // Create the ticker, but stop it + ticker := time.NewTicker(time.Second) + ticker.Stop() + + var brightness uint8 = 0 + + // Event loop + events: + for { + select { + case temp := <-home: + // Only do stuff if the state changes + if temp == isHome { + break + } + isHome = temp + + if isHome { + fmt.Println("Coming home") + if !isDay() { + fmt.Println("\tTurning on lights in the living room") + livingRoom.Bri(0xff) + } + } else { + // Stop the ticker in case it is running + ticker.Stop() + + fmt.Println("Leaving home") + allLightsOff(bridge) + break + } + + case <-sunriseTimer.C: + fmt.Println("Sun is rising, turning off all lights") + allLightsOff(bridge) + + // Set new timer + sunrise, _ := getNextSunriseSunset() + sunriseTimer.Reset(sunrise.Sub(time.Now())) + + case <-sunsetTimer.C: + fmt.Println("Sun is setting") + if isHome { + fmt.Println("\tGradually turning on lights in the living room") + // Start the ticker to gradually turn on the living room lights + ticker.Reset(1200 * time.Millisecond) + } + + // Set new timer + _, sunset := getNextSunriseSunset() + sunsetTimer.Reset(sunset.Sub(time.Now())) + + case <-ticker.C: + fmt.Println("Setting brightness:", brightness) + livingRoom.Bri(brightness) + + brightness++ + if brightness == 0xff { + fmt.Println("Lights are now on, stopping ticker") + ticker.Stop() + brightness = 0 + } + + case <-halt: + break events + } + } + + // Cleanup + if token := c.Unsubscribe("owntracks/mqtt/apollo"); token.Wait() && token.Error() != nil { + fmt.Println(token.Error()) + os.Exit(1) + } + + c.Disconnect(250) +}