721 lines
20 KiB
Go
721 lines
20 KiB
Go
// backend\milestone_cameras.go
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type milestoneCameraPatchDevice struct {
|
|
DeviceID string
|
|
InventoryNumber string
|
|
ChildID string
|
|
MilestoneHardwareID string
|
|
MilestoneDeviceID string
|
|
DeviceType string
|
|
Enabled bool
|
|
RecordingEnabled bool
|
|
}
|
|
|
|
type milestoneRecordingStorageRef struct {
|
|
ID string `json:"id"`
|
|
DisplayName string `json:"displayName,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
DiskPath string `json:"diskPath,omitempty"`
|
|
Mounted bool `json:"mounted,omitempty"`
|
|
}
|
|
|
|
type milestoneCameraStream struct {
|
|
Index int `json:"index"`
|
|
Codec string `json:"codec"`
|
|
FPS *int `json:"fps"`
|
|
JPEGQuality *int `json:"jPEGQuality"`
|
|
Quality *int `json:"quality"`
|
|
Resolution string `json:"resolution"`
|
|
EdgeStorageSupported bool `json:"edgeStorageSupported"`
|
|
}
|
|
|
|
type milestoneCameraDetail struct {
|
|
ID string `json:"id"`
|
|
MilestoneDeviceID string `json:"milestoneDeviceId"`
|
|
MilestoneHardwareID string `json:"milestoneHardwareId"`
|
|
Enabled bool `json:"enabled"`
|
|
RecordingEnabled bool `json:"recordingEnabled"`
|
|
RecordingFramerate *int `json:"recordingFramerate"`
|
|
RecordKeyframesOnly bool `json:"recordKeyframesOnly"`
|
|
RecordingStorage *milestoneRecordingStorageRef `json:"recordingStorage,omitempty"`
|
|
ArchiveStorages []milestoneArchiveStorage `json:"archiveStorages"`
|
|
Streams []milestoneCameraStream `json:"streams"`
|
|
}
|
|
|
|
type updateMilestoneCameraStreamRequest struct {
|
|
Index int `json:"index"`
|
|
Codec string `json:"codec"`
|
|
FPS *int `json:"fps"`
|
|
JPEGQuality *int `json:"jPEGQuality"`
|
|
Quality *int `json:"quality"`
|
|
Resolution string `json:"resolution"`
|
|
}
|
|
|
|
type updateMilestoneCameraDetailsRequest struct {
|
|
Enabled *bool `json:"enabled"`
|
|
RecordingEnabled *bool `json:"recordingEnabled"`
|
|
RecordingFramerate *int `json:"recordingFramerate"`
|
|
RecordKeyframesOnly *bool `json:"recordKeyframesOnly"`
|
|
RecordingStorageID *string `json:"recordingStorageId"`
|
|
Streams []updateMilestoneCameraStreamRequest `json:"streams"`
|
|
}
|
|
|
|
func (s *Server) getMilestoneCameraPatchDevice(
|
|
ctx context.Context,
|
|
deviceID string,
|
|
childID string,
|
|
) (milestoneCameraPatchDevice, error) {
|
|
var camera milestoneCameraPatchDevice
|
|
|
|
err := s.db.QueryRow(
|
|
ctx,
|
|
`
|
|
SELECT
|
|
devices.id::TEXT,
|
|
devices.inventory_number,
|
|
milestone_hardware_child_devices.id::TEXT,
|
|
milestone_hardware_child_devices.milestone_hardware_id,
|
|
milestone_hardware_child_devices.milestone_device_id,
|
|
milestone_hardware_child_devices.device_type,
|
|
milestone_hardware_child_devices.enabled,
|
|
COALESCE(milestone_hardware_child_devices.recording_enabled, false)
|
|
FROM milestone_hardware_child_devices
|
|
INNER JOIN devices
|
|
ON devices.milestone_hardware_id = milestone_hardware_child_devices.milestone_hardware_id
|
|
WHERE devices.id = $1
|
|
AND milestone_hardware_child_devices.id = $2
|
|
LIMIT 1
|
|
`,
|
|
deviceID,
|
|
childID,
|
|
).Scan(
|
|
&camera.DeviceID,
|
|
&camera.InventoryNumber,
|
|
&camera.ChildID,
|
|
&camera.MilestoneHardwareID,
|
|
&camera.MilestoneDeviceID,
|
|
&camera.DeviceType,
|
|
&camera.Enabled,
|
|
&camera.RecordingEnabled,
|
|
)
|
|
|
|
return camera, err
|
|
}
|
|
|
|
func (s *Server) handleGetMilestoneCameraDetails(w http.ResponseWriter, r *http.Request) {
|
|
deviceID := strings.TrimSpace(r.PathValue("id"))
|
|
childID := strings.TrimSpace(r.PathValue("childId"))
|
|
|
|
if deviceID == "" || childID == "" {
|
|
writeError(w, http.StatusBadRequest, "Geräte-ID oder Child-ID fehlt")
|
|
return
|
|
}
|
|
|
|
cameraDevice, err := s.getMilestoneCameraPatchDevice(r.Context(), deviceID, childID)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "Milestone-Kamera wurde nicht gefunden")
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Milestone-Kamera konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
if strings.TrimSpace(strings.ToLower(cameraDevice.DeviceType)) != "camera" &&
|
|
strings.TrimSpace(strings.ToLower(cameraDevice.DeviceType)) != "cameras" {
|
|
writeError(w, http.StatusBadRequest, "Das Child-Device ist keine Kamera")
|
|
return
|
|
}
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
cameraData, err := getMilestoneNamedDevice(
|
|
r.Context(),
|
|
config,
|
|
"cameras",
|
|
cameraDevice.MilestoneDeviceID,
|
|
)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Kamera konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
archiveStorages := []milestoneArchiveStorage{}
|
|
var currentStorage *milestoneStorage
|
|
|
|
if recordingStorage, ok := cameraData["recordingStorage"].(map[string]any); ok {
|
|
recordingStorageID := strings.TrimSpace(milestoneStringValue(recordingStorage["id"]))
|
|
if recordingStorageID != "" {
|
|
currentStorage, err = getMilestoneStorage(
|
|
r.Context(),
|
|
config,
|
|
recordingStorageID,
|
|
)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Storage konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
archiveStorages, err = getMilestoneArchiveStorages(
|
|
r.Context(),
|
|
config,
|
|
recordingStorageID,
|
|
)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Archive-Storage konnte nicht geladen werden")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
camera := mapMilestoneCameraDetail(cameraData, cameraDevice, currentStorage, archiveStorages)
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"camera": camera,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleUpdateMilestoneCameraDetails(w http.ResponseWriter, r *http.Request) {
|
|
deviceID := strings.TrimSpace(r.PathValue("id"))
|
|
childID := strings.TrimSpace(r.PathValue("childId"))
|
|
|
|
if deviceID == "" || childID == "" {
|
|
writeError(w, http.StatusBadRequest, "Geräte-ID oder Child-ID fehlt")
|
|
return
|
|
}
|
|
|
|
cameraDevice, err := s.getMilestoneCameraPatchDevice(r.Context(), deviceID, childID)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "Milestone-Kamera wurde nicht gefunden")
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Milestone-Kamera konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
if strings.TrimSpace(strings.ToLower(cameraDevice.DeviceType)) != "camera" &&
|
|
strings.TrimSpace(strings.ToLower(cameraDevice.DeviceType)) != "cameras" {
|
|
writeError(w, http.StatusBadRequest, "Das Child-Device ist keine Kamera")
|
|
return
|
|
}
|
|
|
|
var input updateMilestoneCameraDetailsRequest
|
|
if err := readJSON(r, &input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
|
return
|
|
}
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
currentCamera, err := getMilestoneNamedDevice(
|
|
r.Context(),
|
|
config,
|
|
"cameras",
|
|
cameraDevice.MilestoneDeviceID,
|
|
)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Kamera konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
if input.Enabled != nil {
|
|
currentCamera["enabled"] = *input.Enabled
|
|
}
|
|
|
|
if input.RecordingEnabled != nil {
|
|
currentCamera["recordingEnabled"] = *input.RecordingEnabled
|
|
}
|
|
|
|
if input.RecordingFramerate != nil {
|
|
currentCamera["recordingFramerate"] = *input.RecordingFramerate
|
|
}
|
|
|
|
if input.RecordKeyframesOnly != nil {
|
|
currentCamera["recordKeyframesOnly"] = *input.RecordKeyframesOnly
|
|
}
|
|
|
|
if input.RecordingStorageID != nil {
|
|
recordingStorageID := strings.TrimSpace(*input.RecordingStorageID)
|
|
if recordingStorageID == "" {
|
|
currentCamera["recordingStorage"] = nil
|
|
} else {
|
|
currentCamera["recordingStorage"] = map[string]any{
|
|
"type": "storages",
|
|
"id": recordingStorageID,
|
|
}
|
|
}
|
|
}
|
|
|
|
applyMilestoneCameraStreamUpdates(currentCamera, input.Streams)
|
|
|
|
if err := patchMilestoneNamedDevice(
|
|
r.Context(),
|
|
config,
|
|
"cameras",
|
|
cameraDevice.MilestoneDeviceID,
|
|
currentCamera,
|
|
); err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Kamera konnte nicht aktualisiert werden")
|
|
return
|
|
}
|
|
|
|
_, err = s.db.Exec(
|
|
r.Context(),
|
|
`
|
|
UPDATE milestone_hardware_child_devices
|
|
SET
|
|
enabled = COALESCE($2, enabled),
|
|
recording_enabled = COALESCE($3, recording_enabled),
|
|
recording_storage_id = COALESCE($4, recording_storage_id),
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
`,
|
|
cameraDevice.ChildID,
|
|
input.Enabled,
|
|
input.RecordingEnabled,
|
|
input.RecordingStorageID,
|
|
)
|
|
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Milestone-Kamera konnte lokal nicht aktualisiert werden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"ok": true,
|
|
})
|
|
}
|
|
|
|
func mapMilestoneCameraDetail(
|
|
cameraData map[string]any,
|
|
cameraDevice milestoneCameraPatchDevice,
|
|
currentStorage *milestoneStorage,
|
|
archiveStorages []milestoneArchiveStorage,
|
|
) milestoneCameraDetail {
|
|
detail := milestoneCameraDetail{
|
|
ID: cameraDevice.ChildID,
|
|
MilestoneDeviceID: cameraDevice.MilestoneDeviceID,
|
|
MilestoneHardwareID: cameraDevice.MilestoneHardwareID,
|
|
Enabled: milestoneBoolValue(cameraData["enabled"]),
|
|
RecordingEnabled: milestoneBoolValue(cameraData["recordingEnabled"]),
|
|
RecordKeyframesOnly: milestoneBoolValue(cameraData["recordKeyframesOnly"]),
|
|
Streams: extractMilestoneCameraStreams(cameraData),
|
|
ArchiveStorages: archiveStorages,
|
|
}
|
|
|
|
if value, ok := milestoneIntPointerValue(cameraData["recordingFramerate"]); ok {
|
|
detail.RecordingFramerate = value
|
|
}
|
|
|
|
if currentStorage != nil && strings.TrimSpace(currentStorage.ID) != "" {
|
|
detail.RecordingStorage = &milestoneRecordingStorageRef{
|
|
ID: currentStorage.ID,
|
|
DisplayName: currentStorage.DisplayName,
|
|
Name: currentStorage.Name,
|
|
DiskPath: currentStorage.DiskPath,
|
|
Mounted: currentStorage.Mounted,
|
|
}
|
|
} else if recordingStorage, ok := cameraData["recordingStorage"].(map[string]any); ok {
|
|
recordingStorageID := strings.TrimSpace(milestoneStringValue(recordingStorage["id"]))
|
|
if recordingStorageID != "" {
|
|
detail.RecordingStorage = &milestoneRecordingStorageRef{
|
|
ID: recordingStorageID,
|
|
}
|
|
}
|
|
}
|
|
|
|
return detail
|
|
}
|
|
|
|
func extractMilestoneCameraStreams(cameraData map[string]any) []milestoneCameraStream {
|
|
settings, ok := cameraData["settings"].([]any)
|
|
if !ok {
|
|
return []milestoneCameraStream{}
|
|
}
|
|
|
|
streams := []milestoneCameraStream{}
|
|
|
|
for _, rawSetting := range settings {
|
|
setting, ok := rawSetting.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
rawStreams, ok := setting["stream"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
for index, rawStream := range rawStreams {
|
|
streamMap, ok := rawStream.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
stream := milestoneCameraStream{
|
|
Index: index,
|
|
Codec: milestoneStringValue(streamMap["codec"]),
|
|
Resolution: milestoneStringValue(streamMap["resolution"]),
|
|
EdgeStorageSupported: milestoneBoolValue(streamMap["edgeStorageSupported"]),
|
|
}
|
|
|
|
if value, ok := milestoneIntPointerValue(streamMap["FPS"]); ok {
|
|
stream.FPS = value
|
|
}
|
|
|
|
if value, ok := milestoneIntPointerValue(streamMap["jPEGQuality"]); ok {
|
|
stream.JPEGQuality = value
|
|
}
|
|
|
|
if value, ok := milestoneIntPointerValue(streamMap["quality"]); ok {
|
|
stream.Quality = value
|
|
}
|
|
|
|
streams = append(streams, stream)
|
|
}
|
|
}
|
|
|
|
return streams
|
|
}
|
|
|
|
type milestoneCameraListItem struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"displayName"`
|
|
Type string `json:"type"`
|
|
Enabled bool `json:"enabled"`
|
|
RecordingEnabled bool `json:"recordingEnabled"`
|
|
RecordingStorageID string `json:"recordingStorageId"`
|
|
HardwareID string `json:"hardwareId"`
|
|
HardwareDisplayName string `json:"hardwareDisplayName"`
|
|
}
|
|
|
|
func (s *Server) getMilestoneHardwareDisplayNameMap(
|
|
ctx context.Context,
|
|
) (map[string]string, error) {
|
|
rows, err := s.db.Query(
|
|
ctx,
|
|
`
|
|
SELECT
|
|
COALESCE(milestone_hardware_id, ''),
|
|
COALESCE(milestone_display_name, inventory_number, '')
|
|
FROM devices
|
|
WHERE COALESCE(milestone_hardware_id, '') <> ''
|
|
`,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
out := map[string]string{}
|
|
|
|
for rows.Next() {
|
|
var hardwareID string
|
|
var displayName string
|
|
|
|
if err := rows.Scan(&hardwareID, &displayName); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hardwareID = strings.TrimSpace(hardwareID)
|
|
displayName = strings.TrimSpace(displayName)
|
|
|
|
if hardwareID == "" || displayName == "" {
|
|
continue
|
|
}
|
|
|
|
if _, exists := out[hardwareID]; !exists {
|
|
out[hardwareID] = displayName
|
|
}
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func getMilestoneDeviceList(
|
|
ctx context.Context,
|
|
config milestoneRuntimeConfig,
|
|
collection string,
|
|
hardwareDisplayNames map[string]string,
|
|
) ([]milestoneCameraListItem, error) {
|
|
requestURL := strings.TrimRight(config.Host, "/") +
|
|
"/API/rest/v1/" + collection + "?disabled"
|
|
|
|
request, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setMilestoneRequestHeaders(request, config, false)
|
|
|
|
response, err := milestoneHTTPClient(config.SkipTLSVerify).Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
body, err := io.ReadAll(response.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
|
return nil, fmt.Errorf("milestone %s list failed: status=%d", collection, response.StatusCode)
|
|
}
|
|
|
|
var decoded map[string]any
|
|
if err := json.Unmarshal(body, &decoded); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rawItems []any
|
|
if array, ok := decoded["array"].([]any); ok {
|
|
rawItems = array
|
|
} else if data, ok := decoded["data"].(map[string]any); ok {
|
|
if array, ok := data["array"].([]any); ok {
|
|
rawItems = array
|
|
}
|
|
}
|
|
|
|
deviceType := "camera"
|
|
if collection == "microphones" {
|
|
deviceType = "microphone"
|
|
}
|
|
|
|
items := make([]milestoneCameraListItem, 0, len(rawItems))
|
|
|
|
for _, raw := range rawItems {
|
|
item, ok := raw.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
id := milestoneNamedDeviceID(item)
|
|
if id == "" {
|
|
continue
|
|
}
|
|
|
|
name := milestoneStringValue(item["name"])
|
|
displayName := milestoneStringValue(item["displayName"])
|
|
if name == "" {
|
|
name = displayName
|
|
}
|
|
if displayName == "" {
|
|
displayName = name
|
|
}
|
|
if name == "" {
|
|
continue
|
|
}
|
|
|
|
enabled := milestoneItemEnabledValue(item)
|
|
|
|
recordingEnabled := false
|
|
if v, ok := item["recordingEnabled"].(bool); ok {
|
|
recordingEnabled = v
|
|
}
|
|
|
|
recordingStorageID := ""
|
|
if rs, ok := item["recordingStorage"].(map[string]any); ok {
|
|
recordingStorageID = strings.TrimSpace(milestoneStringValue(rs["id"]))
|
|
}
|
|
if recordingStorageID == "" {
|
|
if relations, ok := item["relations"].(map[string]any); ok {
|
|
if rs, ok := relations["recordingStorage"].(map[string]any); ok {
|
|
recordingStorageID = strings.TrimSpace(milestoneStringValue(rs["id"]))
|
|
}
|
|
}
|
|
}
|
|
|
|
hardwareID := milestoneNamedDeviceParentID(item)
|
|
hardwareDisplayName := strings.TrimSpace(hardwareDisplayNames[hardwareID])
|
|
|
|
items = append(items, milestoneCameraListItem{
|
|
ID: id,
|
|
Name: name,
|
|
DisplayName: displayName,
|
|
Type: deviceType,
|
|
Enabled: enabled,
|
|
RecordingEnabled: recordingEnabled,
|
|
RecordingStorageID: recordingStorageID,
|
|
HardwareID: hardwareID,
|
|
HardwareDisplayName: hardwareDisplayName,
|
|
})
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
func (s *Server) handleListMilestoneCameras(w http.ResponseWriter, r *http.Request) {
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
hardwareDisplayNames, err := s.getMilestoneHardwareDisplayNameMap(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Milestone-Hardwarenamen konnten nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
cameras, err := getMilestoneDeviceList(r.Context(), config, "cameras", hardwareDisplayNames)
|
|
if err != nil {
|
|
log.Printf("Milestone cameras list failed: %v", err)
|
|
writeError(w, http.StatusBadGateway, "Milestone-Kameras konnten nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
microphones, err := getMilestoneDeviceList(r.Context(), config, "microphones", hardwareDisplayNames)
|
|
if err != nil {
|
|
log.Printf("Milestone microphones list failed: %v", err)
|
|
writeError(w, http.StatusBadGateway, "Milestone-Mikrofone konnten nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
all := make([]milestoneCameraListItem, 0, len(cameras)+len(microphones))
|
|
all = append(all, cameras...)
|
|
all = append(all, microphones...)
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{"devices": all})
|
|
}
|
|
|
|
type patchMilestoneDeviceRequest struct {
|
|
Enabled *bool `json:"enabled"`
|
|
RecordingEnabled *bool `json:"recordingEnabled"`
|
|
}
|
|
|
|
func (s *Server) handlePatchMilestoneDevice(w http.ResponseWriter, r *http.Request) {
|
|
collection := strings.TrimSpace(r.PathValue("collection"))
|
|
deviceID := strings.TrimSpace(r.PathValue("id"))
|
|
|
|
if collection != "cameras" && collection != "microphones" {
|
|
writeError(w, http.StatusBadRequest, "Ungültige Gerätekategorie")
|
|
return
|
|
}
|
|
|
|
if deviceID == "" {
|
|
writeError(w, http.StatusBadRequest, "Geräte-ID fehlt")
|
|
return
|
|
}
|
|
|
|
var input patchMilestoneDeviceRequest
|
|
if err := readJSON(r, &input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
|
|
return
|
|
}
|
|
|
|
payload := map[string]any{}
|
|
if input.Enabled != nil {
|
|
payload["enabled"] = *input.Enabled
|
|
}
|
|
if input.RecordingEnabled != nil {
|
|
payload["recordingEnabled"] = *input.RecordingEnabled
|
|
}
|
|
|
|
if len(payload) == 0 {
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
return
|
|
}
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
if err := patchMilestoneNamedDevice(r.Context(), config, collection, deviceID, payload); err != nil {
|
|
log.Printf("Milestone device patch failed: collection=%s id=%s error=%v", collection, deviceID, err)
|
|
writeError(w, http.StatusBadGateway, "Gerät konnte nicht aktualisiert werden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
}
|
|
|
|
func applyMilestoneCameraStreamUpdates(
|
|
cameraData map[string]any,
|
|
streamUpdates []updateMilestoneCameraStreamRequest,
|
|
) {
|
|
if len(streamUpdates) == 0 {
|
|
return
|
|
}
|
|
|
|
settings, ok := cameraData["settings"].([]any)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
updatesByIndex := map[int]updateMilestoneCameraStreamRequest{}
|
|
for _, streamUpdate := range streamUpdates {
|
|
updatesByIndex[streamUpdate.Index] = streamUpdate
|
|
}
|
|
|
|
for _, rawSetting := range settings {
|
|
setting, ok := rawSetting.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
rawStreams, ok := setting["stream"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
for index, rawStream := range rawStreams {
|
|
streamMap, ok := rawStream.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
update, exists := updatesByIndex[index]
|
|
if !exists {
|
|
continue
|
|
}
|
|
|
|
streamMap["codec"] = update.Codec
|
|
streamMap["resolution"] = update.Resolution
|
|
|
|
if update.FPS != nil {
|
|
streamMap["FPS"] = *update.FPS
|
|
}
|
|
|
|
if update.JPEGQuality != nil {
|
|
streamMap["jPEGQuality"] = *update.JPEGQuality
|
|
}
|
|
|
|
if update.Quality != nil {
|
|
streamMap["quality"] = *update.Quality
|
|
}
|
|
}
|
|
}
|
|
}
|