refactor: remove temporary files
doc: add readme
This commit is contained in:
parent
c30cb9bfab
commit
6a9b5db10d
8 changed files with 213 additions and 65 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/ha-rgb-screen
|
||||
36
README.md
Normal file
36
README.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# RGB Screen Sync for Home Assistant
|
||||
|
||||
## 🌈 Overview
|
||||
|
||||
This project synchronizes your RGB lamp (connected via Home Assistant) with the average color displayed on your screen in real time. The goal is to create an immersive ambient lighting experience that reacts dynamically to your screen’s content — perfect for movies, gaming, or music visualization.
|
||||
|
||||
## 🧩 How It Works
|
||||
|
||||
- The script captures a screenshot of your screen
|
||||
- It calculates the average RGB color
|
||||
- It sends a color update command to your Home Assistant instance via its REST API
|
||||
- Your RGB lamp updates its color to match the screen
|
||||
|
||||
## Home Assistant configuration
|
||||
|
||||
### Create an automation
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Set a trigger and copy the URL
|
||||
|
||||

|
||||
|
||||
### Set an action
|
||||
|
||||

|
||||
|
||||
## 🧪 Example
|
||||
|
||||
```
|
||||
ha-rgb-screen -url http://homeassistant.local:8123/api/webhook/xxxxx -screen 0 -d 500
|
||||
```
|
||||
|
||||
Runs the RGB Screen Sync tool and connects it to your Home Assistant instance via a webhook. The `-url` parameter specifies the webhook endpoint used to send color updates. The `-screen 0` option selects the first display (useful if multiple monitors are connected), and the `-d 500` option sets the delay between color updates to 500 milliseconds — meaning the lamp’s color will refresh twice per second to match the average color of the chosen screen.
|
||||
39
cmd/main.go
Normal file
39
cmd/main.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gitnet.fr/deblan/ha-rgb-screen/internal/config"
|
||||
"gitnet.fr/deblan/ha-rgb-screen/internal/ha"
|
||||
"gitnet.fr/deblan/ha-rgb-screen/internal/img"
|
||||
"gitnet.fr/deblan/ha-rgb-screen/internal/screen"
|
||||
)
|
||||
|
||||
func main() {
|
||||
params := config.GetConfig()
|
||||
|
||||
if !params.IsValid() {
|
||||
params.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
s := screen.NewScreen(params)
|
||||
|
||||
client := ha.NewClient(params)
|
||||
|
||||
for {
|
||||
capture, err := s.Capture()
|
||||
|
||||
if err == nil {
|
||||
if err := client.Update(img.GetRgbAverage(capture)); err != nil {
|
||||
log.Println("Error: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Fatalf("Fatal error: %s", err.Error())
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(params.Delay) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
36
internal/config/config.go
Normal file
36
internal/config/config.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package config
|
||||
|
||||
import "flag"
|
||||
|
||||
type Config struct {
|
||||
Url string
|
||||
Delay int
|
||||
Screen int
|
||||
Verbose bool
|
||||
Usage func()
|
||||
}
|
||||
|
||||
func (c *Config) IsValid() bool {
|
||||
if c.Url == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func GetConfig() *Config {
|
||||
url := flag.String("url", "", "Webhook URL")
|
||||
delay := flag.Int("delay", 500, "Delay in ms")
|
||||
screen := flag.Int("screen", 0, "Screen index")
|
||||
verbose := flag.Bool("v", false, "Verbose mode")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
return &Config{
|
||||
Url: *url,
|
||||
Delay: *delay,
|
||||
Screen: *screen,
|
||||
Verbose: *verbose,
|
||||
Usage: flag.Usage,
|
||||
}
|
||||
}
|
||||
47
internal/ha/client.go
Normal file
47
internal/ha/client.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package ha
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"gitnet.fr/deblan/ha-rgb-screen/internal/config"
|
||||
"gitnet.fr/deblan/ha-rgb-screen/internal/img"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
params *config.Config
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewClient(params *config.Config) *Client {
|
||||
return &Client{
|
||||
params: params,
|
||||
client: new(http.Client),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Update(rgb img.RGB) error {
|
||||
data := fmt.Sprintf(`{"rgb": [%.0f, %.0f, %.0f]}`, rgb.R, rgb.G, rgb.B)
|
||||
|
||||
if c.params.Verbose {
|
||||
log.Println(data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(
|
||||
"POST",
|
||||
c.params.Url,
|
||||
bytes.NewBuffer([]byte(data)),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
_, err = c.client.Do(req)
|
||||
|
||||
return err
|
||||
}
|
||||
32
internal/img/image.go
Normal file
32
internal/img/image.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package img
|
||||
|
||||
import "image"
|
||||
|
||||
type RGB struct {
|
||||
R float64
|
||||
G float64
|
||||
B float64
|
||||
}
|
||||
|
||||
func GetRgbAverage(i *image.RGBA) RGB {
|
||||
bounds := i.Bounds()
|
||||
|
||||
var rSum, gSum, bSum uint64
|
||||
var count uint64
|
||||
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
r, g, b, _ := i.At(x, y).RGBA()
|
||||
rSum += uint64(r >> 8)
|
||||
gSum += uint64(g >> 8)
|
||||
bSum += uint64(b >> 8)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return RGB{
|
||||
R: float64(rSum) / float64(count),
|
||||
G: float64(gSum) / float64(count),
|
||||
B: float64(bSum) / float64(count),
|
||||
}
|
||||
}
|
||||
22
internal/screen/screen.go
Normal file
22
internal/screen/screen.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package screen
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
s "github.com/kbinani/screenshot"
|
||||
"gitnet.fr/deblan/ha-rgb-screen/internal/config"
|
||||
)
|
||||
|
||||
type Screen struct {
|
||||
displayBounds image.Rectangle
|
||||
}
|
||||
|
||||
func NewScreen(params *config.Config) *Screen {
|
||||
return &Screen{
|
||||
displayBounds: s.GetDisplayBounds(params.Screen),
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *Screen) Capture() (*image.RGBA, error) {
|
||||
return s.CaptureRect(sc.displayBounds)
|
||||
}
|
||||
65
main.go
65
main.go
|
|
@ -1,65 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
_ "image/jpeg" // pour pouvoir lire les fichiers .jpg
|
||||
_ "image/png" // pour pouvoir lire les fichiers .png
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/kbinani/screenshot"
|
||||
)
|
||||
|
||||
func main() {
|
||||
webhook := flag.String("webhook", "", "Webhook URL")
|
||||
flag.Parse()
|
||||
|
||||
if *webhook == "" {
|
||||
log.Fatal("webhook is required")
|
||||
}
|
||||
|
||||
displayBounds := screenshot.GetDisplayBounds(0)
|
||||
client := &http.Client{}
|
||||
|
||||
for {
|
||||
if img, err := screenshot.CaptureRect(displayBounds); err == nil {
|
||||
bounds := img.Bounds()
|
||||
|
||||
var rSum, gSum, bSum uint64
|
||||
var count uint64
|
||||
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
r, g, b, _ := img.At(x, y).RGBA()
|
||||
rSum += uint64(r >> 8)
|
||||
gSum += uint64(g >> 8)
|
||||
bSum += uint64(b >> 8)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
rAvg := float64(rSum) / float64(count)
|
||||
gAvg := float64(gSum) / float64(count)
|
||||
bAvg := float64(bSum) / float64(count)
|
||||
|
||||
data := fmt.Sprintf(`{"rgb": [%.0f, %.0f, %.0f]}`, rAvg, gAvg, bAvg)
|
||||
|
||||
req, _ := http.NewRequest(
|
||||
"POST",
|
||||
*webhook,
|
||||
bytes.NewBuffer([]byte(data)),
|
||||
)
|
||||
|
||||
log.Println(data)
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client.Do(req)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue