148 lines
4.1 KiB
Go
148 lines
4.1 KiB
Go
package statusreporter
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Snapshot struct {
|
|
Status string
|
|
ServerConnectivity string
|
|
ScreenID string
|
|
ServerBaseURL string
|
|
MQTTBroker string
|
|
HeartbeatEverySeconds int
|
|
StartedAt time.Time
|
|
LastHeartbeatAt time.Time
|
|
DisplayState string
|
|
}
|
|
|
|
type statusPayload struct {
|
|
ScreenID string `json:"screen_id"`
|
|
Timestamp string `json:"ts"`
|
|
Status string `json:"status"`
|
|
ServerConnectivity string `json:"server_connectivity"`
|
|
ServerURL string `json:"server_url"`
|
|
MQTTBroker string `json:"mqtt_broker"`
|
|
HeartbeatEverySeconds int `json:"heartbeat_every_seconds"`
|
|
StartedAt string `json:"started_at,omitempty"`
|
|
LastHeartbeatAt string `json:"last_heartbeat_at,omitempty"`
|
|
DisplayState string `json:"display_state,omitempty"`
|
|
}
|
|
|
|
// MQTTConfig holds the MQTT broker configuration returned by the server in the
|
|
// status-report response. All fields are empty when the server did not send
|
|
// a mqtt object (e.g. MQTT is disabled on the server side).
|
|
type MQTTConfig struct {
|
|
Broker string
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
// serverResponse is the JSON body returned by POST /api/v1/player/status.
|
|
type serverResponse struct {
|
|
Status string `json:"status"`
|
|
MQTT *serverMQTTBlock `json:"mqtt,omitempty"`
|
|
}
|
|
|
|
type serverMQTTBlock struct {
|
|
Broker string `json:"broker"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type Reporter struct {
|
|
baseURL string
|
|
client *http.Client
|
|
now func() time.Time
|
|
}
|
|
|
|
func New(baseURL string, client *http.Client, now func() time.Time) *Reporter {
|
|
if client == nil {
|
|
client = &http.Client{Timeout: 5 * time.Second}
|
|
}
|
|
|
|
if now == nil {
|
|
now = time.Now
|
|
}
|
|
|
|
return &Reporter{
|
|
baseURL: strings.TrimRight(baseURL, "/"),
|
|
client: client,
|
|
now: now,
|
|
}
|
|
}
|
|
|
|
// Send reports the snapshot to the server and returns the MQTT configuration
|
|
// provided by the server in the response body. If the server does not include
|
|
// a mqtt object the returned MQTTConfig will have an empty Broker field.
|
|
func (r *Reporter) Send(ctx context.Context, snapshot Snapshot) (MQTTConfig, error) {
|
|
payload := buildPayload(snapshot, r.now())
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return MQTTConfig{}, fmt.Errorf("marshal status payload: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.baseURL+"/api/v1/player/status", bytes.NewReader(body))
|
|
if err != nil {
|
|
return MQTTConfig{}, fmt.Errorf("build status request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := r.client.Do(req)
|
|
if err != nil {
|
|
return MQTTConfig{}, fmt.Errorf("send status request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return MQTTConfig{}, fmt.Errorf("unexpected status response: %s", resp.Status)
|
|
}
|
|
|
|
var sr serverResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&sr); err != nil {
|
|
// Non-fatal: decoding failure just means no MQTT config update.
|
|
return MQTTConfig{}, nil
|
|
}
|
|
|
|
if sr.MQTT != nil {
|
|
return MQTTConfig{
|
|
Broker: sr.MQTT.Broker,
|
|
Username: sr.MQTT.Username,
|
|
Password: sr.MQTT.Password,
|
|
}, nil
|
|
}
|
|
|
|
return MQTTConfig{}, nil
|
|
}
|
|
|
|
func buildPayload(snapshot Snapshot, now time.Time) statusPayload {
|
|
payload := statusPayload{
|
|
ScreenID: snapshot.ScreenID,
|
|
Timestamp: now.Format(time.RFC3339),
|
|
Status: snapshot.Status,
|
|
ServerConnectivity: snapshot.ServerConnectivity,
|
|
ServerURL: snapshot.ServerBaseURL,
|
|
MQTTBroker: snapshot.MQTTBroker,
|
|
HeartbeatEverySeconds: snapshot.HeartbeatEverySeconds,
|
|
}
|
|
|
|
if !snapshot.StartedAt.IsZero() {
|
|
payload.StartedAt = snapshot.StartedAt.Format(time.RFC3339)
|
|
}
|
|
|
|
if !snapshot.LastHeartbeatAt.IsZero() {
|
|
payload.LastHeartbeatAt = snapshot.LastHeartbeatAt.Format(time.RFC3339)
|
|
}
|
|
|
|
if snapshot.DisplayState != "" {
|
|
payload.DisplayState = snapshot.DisplayState
|
|
}
|
|
|
|
return payload
|
|
}
|