This commit is contained in:
Melora Hugues 2024-04-30 18:43:01 +02:00
parent b250a6b3c8
commit 081626b434
5 changed files with 137 additions and 14 deletions

View file

@ -60,6 +60,11 @@ type jsonConf struct {
McastGroup string `json:"multicast_group"` McastGroup string `json:"multicast_group"`
SrcAddr string `json:"src_addr"` SrcAddr string `json:"src_addr"`
} `json:"boot_provider"` } `json:"boot_provider"`
HomeAssistant struct {
Enabled bool `json:"enabled"`
Host string `json:"host"`
APIToken string `json:"token"`
} `json:"home_assistant"`
} }
type AppConfig struct { type AppConfig struct {
@ -75,6 +80,9 @@ type AppConfig struct {
UDPPort int UDPPort int
UDPIface string UDPIface string
UDPSrcAddr string UDPSrcAddr string
HomeAssistantEnabled bool
HomeAssistantHost string
HomeAssistantToken string
} }
func parseLevel(lvlStr string) logrus.Level { func parseLevel(lvlStr string) logrus.Level {
@ -108,6 +116,9 @@ func (ac *AppConfig) UnmarshalJSON(data []byte) error {
ac.UDPSrcAddr = jsonConf.BootProvider.SrcAddr ac.UDPSrcAddr = jsonConf.BootProvider.SrcAddr
ac.DataFilepath = jsonConf.Storage.Path ac.DataFilepath = jsonConf.Storage.Path
ac.StaticDir = jsonConf.Storage.StaticDir ac.StaticDir = jsonConf.Storage.StaticDir
ac.HomeAssistantEnabled = jsonConf.HomeAssistant.Enabled
ac.HomeAssistantHost = jsonConf.HomeAssistant.Host
ac.HomeAssistantToken = jsonConf.HomeAssistant.APIToken
return nil return nil
} }

View file

@ -7,7 +7,9 @@ import (
"io" "io"
"net/http" "net/http"
"git.faercol.me/faercol/http-boot-server/bootserver/config"
"git.faercol.me/faercol/http-boot-server/bootserver/helpers" "git.faercol.me/faercol/http-boot-server/bootserver/helpers"
"git.faercol.me/faercol/http-boot-server/bootserver/homeassistant"
"git.faercol.me/faercol/http-boot-server/bootserver/services" "git.faercol.me/faercol/http-boot-server/bootserver/services"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -22,13 +24,15 @@ type setBootOptionPayload struct {
type BootController struct { type BootController struct {
clientService *services.ClientHandlerService clientService *services.ClientHandlerService
appConf *config.AppConfig
l *logrus.Logger l *logrus.Logger
} }
func NewBootController(logger *logrus.Logger, service *services.ClientHandlerService) *BootController { func NewBootController(logger *logrus.Logger, service *services.ClientHandlerService, conf *config.AppConfig) *BootController {
return &BootController{ return &BootController{
clientService: service, clientService: service,
l: logger, l: logger,
appConf: conf,
} }
} }
@ -61,6 +65,18 @@ func (bc *BootController) setBootOption(w http.ResponseWriter, r *http.Request)
return http.StatusInternalServerError, nil, fmt.Errorf("failed to set boot option for client: %w", err) return http.StatusInternalServerError, nil, fmt.Errorf("failed to set boot option for client: %w", err)
} }
if bc.appConf.HomeAssistantEnabled {
bc.l.Debug("Notifying HomeAssistant of change")
newConf, err := bc.clientService.GetClientConfig(clientID)
if err != nil {
bc.l.Errorf("Failed to get new config to send to HA: %s", err.Error())
} else {
if err := homeassistant.New(bc.appConf).SendBootOption(r.Context(), newConf.Name, newConf.Options[newConf.SelectedOption].Name); err != nil {
bc.l.Errorf("Failed to notify HA: %s", err.Error())
}
}
}
return http.StatusAccepted, nil, nil return http.StatusAccepted, nil, nil
} }

View file

@ -0,0 +1,80 @@
package homeassistant
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"git.faercol.me/faercol/http-boot-server/bootserver/config"
"git.faercol.me/faercol/http-boot-server/bootserver/logger"
)
type Entity struct {
State string `json:"state"`
ID string `json:"-"`
Attributes map[string]string `json:"attributes,omitempty"`
}
func newBootOptionEntity(device, option string) Entity {
return Entity{
State: option,
ID: "httpboot." + device,
Attributes: nil,
}
}
type HomeAssistantExporter struct {
clt *http.Client
baseURL string
token string
}
func New(conf *config.AppConfig) *HomeAssistantExporter {
clt := http.Client{}
return &HomeAssistantExporter{
clt: &clt,
baseURL: conf.HomeAssistantHost,
token: conf.HomeAssistantToken,
}
}
func (e *HomeAssistantExporter) SendBootOption(ctx context.Context, device string, option string) error {
subCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
entity := newBootOptionEntity(device, option)
dat, err := json.Marshal(entity)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(subCtx, http.MethodPost, e.baseURL+"/api/states/"+entity.ID, bytes.NewBuffer(dat))
if err != nil {
return err
}
req.Header.Add("Authorization", "Bearer "+e.token)
resp, err := e.clt.Do(req)
if err != nil {
return err
}
switch resp.StatusCode {
case http.StatusOK:
logger.L.Debugf("Updated boot info for device %s to %s", device, option)
case http.StatusCreated:
logger.L.Debugf("Created boot info for device %s with value %s", device, option)
default:
respBod, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
return fmt.Errorf("unexpected returncode %d (%s)", resp.StatusCode, string(respBod))
}
return nil
}

View file

@ -8,6 +8,7 @@ import (
"time" "time"
"git.faercol.me/faercol/http-boot-server/bootserver/config" "git.faercol.me/faercol/http-boot-server/bootserver/config"
"git.faercol.me/faercol/http-boot-server/bootserver/homeassistant"
"git.faercol.me/faercol/http-boot-server/bootserver/logger" "git.faercol.me/faercol/http-boot-server/bootserver/logger"
"git.faercol.me/faercol/http-boot-server/bootserver/server" "git.faercol.me/faercol/http-boot-server/bootserver/server"
"git.faercol.me/faercol/http-boot-server/bootserver/services" "git.faercol.me/faercol/http-boot-server/bootserver/services"
@ -61,6 +62,21 @@ func main() {
logger.L.Fatalf("Failed to start UDP listener: %s", err.Error()) logger.L.Fatalf("Failed to start UDP listener: %s", err.Error())
} }
if conf.HomeAssistantEnabled {
logger.L.Info("Home assistant integration enabled, sending current configuration to the host")
haClt := homeassistant.New(conf)
cltSrv := services.NewClientHandlerService(conf.DataFilepath, logger.L)
clts, err := cltSrv.GetAllClientConfig()
if err != nil {
logger.L.Fatalf("Failed to get current clients from the storage: %s", err.Error())
}
for _, c := range clts {
if err := haClt.SendBootOption(context.Background(), c.Name, c.Options[c.SelectedOption].Name); err != nil {
logger.L.Errorf("Failed to send config to homeassistant: %s", err.Error())
}
}
}
go s.Run(mainCtx) go s.Run(mainCtx)
go listener.Run(mainCtx) go listener.Run(mainCtx)

View file

@ -69,7 +69,7 @@ func New(appConf *config.AppConfig, logger *logrus.Logger) (*Server, error) {
controllers := map[string]http.Handler{ controllers := map[string]http.Handler{
client.EnrollRoute: middlewares.WithLogger(client.NewEnrollController(logger, service, appConf.UDPPort, appConf.UPDMcastGroup), logger), client.EnrollRoute: middlewares.WithLogger(client.NewEnrollController(logger, service, appConf.UDPPort, appConf.UPDMcastGroup), logger),
client.ConfigRoute: middlewares.WithLogger(client.NewGetConfigController(logger, service, appConf), logger), client.ConfigRoute: middlewares.WithLogger(client.NewGetConfigController(logger, service, appConf), logger),
client.SetBootRoute: middlewares.WithLogger(client.NewBootController(logger, service), logger), client.SetBootRoute: middlewares.WithLogger(client.NewBootController(logger, service, appConf), logger),
ui.StaticRoute: middlewares.WithLogger(ui.NewStaticController(appConf.StaticDir), logger), ui.StaticRoute: middlewares.WithLogger(ui.NewStaticController(appConf.StaticDir), logger),
ui.UIRoute: middlewares.WithLogger(ui.NewUIController(logger, service, appConf.StaticDir), logger), ui.UIRoute: middlewares.WithLogger(ui.NewUIController(logger, service, appConf.StaticDir), logger),
} }