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,21 +60,29 @@ 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 {
LogLevel logrus.Level LogLevel logrus.Level
ServerMode ListeningMode ServerMode ListeningMode
DataFilepath string DataFilepath string
StaticDir string StaticDir string
Host string Host string
PublicHost string PublicHost string
Port int Port int
SockPath string SockPath string
UPDMcastGroup string UPDMcastGroup string
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),
} }