package api import ( "time" "gitea.dooplex.hu/admin/felhom-hub/internal/store" ) // Event types of the household power-off (R-998, doc 14). Both are info: recorded on the customer's event list, // never mailed (the dispatcher mails warning and above). const ( eventHostDeliberateOff = "host_deliberate_off" eventHostDeliberateOffEnded = "host_deliberate_off_ended" ) // checkDeliberateOff keeps the host row's deliberate-off time in step with the report: set while the report // carries the field, cleared by the first report without it (the box is in a new boot — switched back on). func (h *Handler) checkDeliberateOff(hostID, custID string, rep *hostReportPayload) { var at *time.Time if rep.DeliberateOff != nil && rep.DeliberateOff.RequestedAt != "" { t, err := time.Parse(time.RFC3339, rep.DeliberateOff.RequestedAt) if err != nil { h.logger.Printf("[WARN] host-report from %s: unreadable deliberate_off.requested_at %q — ignored", hostID, rep.DeliberateOff.RequestedAt) return } t = t.UTC() // The time is the BOX's clock (security review 2026-10-13): a future value would stretch the hold past 14 // days, so it is cut to the hub's own now. An earlier value only shortens the hold — the safe direction. if now := time.Now().UTC().Truncate(time.Second); t.After(now) { h.logger.Printf("[WARN] host-report from %s: deliberate_off.requested_at %s is in the future — using the hub's time", hostID, t.Format(time.RFC3339)) t = now } at = &t } prev, err := h.store.SetHostDeliberateOff(hostID, at) if err != nil { h.logger.Printf("[WARN] host-report from %s: deliberate-off not stored: %v", hostID, err) return } switch { case at != nil && (prev == nil || !prev.Equal(*at)): msg := "Switched off by the household at " + at.Format("2006-01-02 15:04 UTC") + " — silence alarms held for " + holdDays() + " days" h.logger.Printf("[INFO] host %s: deliberate power-off recorded (%s)", hostID, at.Format(time.RFC3339)) h.saveInfoEvent(custID, eventHostDeliberateOff, msg) case at == nil && prev != nil: msg := "Reporting again after the household's power-off of " + prev.Format("2006-01-02 15:04 UTC") h.logger.Printf("[INFO] host %s: deliberate power-off ended (was %s)", hostID, prev.Format(time.RFC3339)) h.saveInfoEvent(custID, eventHostDeliberateOffEnded, msg) } } func holdDays() string { d := int(store.DeliberateOffHold / (24 * time.Hour)) return itoa(d) } func itoa(n int) string { if n == 0 { return "0" } var b []byte for n > 0 { b = append([]byte{byte('0' + n%10)}, b...) n /= 10 } return string(b) } func (h *Handler) saveInfoEvent(custID, eventType, msg string) { if _, err := h.store.SaveEvent(custID, eventType, "info", msg, "{}", "hub"); err != nil { h.logger.Printf("[WARN] save %s event: %v", eventType, err) } }