Homeautomation/src/main/java/de/ph87/homeautomation/schedule/ScheduleCalculator.java

105 lines
4.2 KiB
Java

package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.schedule.astro.AstroCalculator;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.Optional;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class ScheduleCalculator {
private final ScheduleReader scheduleReader;
private final ApplicationEventPublisher applicationEventPublisher;
private final ScheduleMapper scheduleMapper;
private final AstroCalculator astroCalculator;
@EventListener(ApplicationStartedEvent.class)
public void calculateAllNext() {
final ZonedDateTime now = ZonedDateTime.now();
scheduleReader.findAll().forEach(schedule -> calculateSchedule(schedule, now));
}
public void calculateSchedule(final Schedule schedule, final ZonedDateTime now) {
schedule.getEntries().forEach(entry -> calculateEntry(schedule, entry, now));
final Optional<ScheduleEntry> nextEntry = schedule.getEntries().stream()
.filter(entry -> entry.getNextFuzzyTimestamp() != null && entry.getNextFuzzyTimestamp().isAfter(now))
.min(Comparator.comparing(ScheduleEntry::getNextFuzzyTimestamp));
if (nextEntry.isEmpty()) {
log.info("No next schedule for \"{}\"", schedule.getTitle());
} else {
log.info("Next schedule for \"{}\": {}", schedule.getTitle(), nextEntry.get().getNextFuzzyTimestamp());
}
applicationEventPublisher.publishEvent(new ScheduleThreadWakeUpEvent());
scheduleMapper.publish(schedule, true);
}
private void calculateEntry(final Schedule schedule, final ScheduleEntry entry, final ZonedDateTime now) {
log.debug("calculateNext \"{}\", {}:", schedule.getTitle(), entry);
if (!schedule.isEnabled() || !entry.isEnabled() || !isAnyWeekdayEnabled(entry)) {
entry.setNextClearTimestamp(null);
return;
}
ZonedDateTime midnight = now.withHour(0).withMinute(0).withSecond(0).withNano(0);
ZonedDateTime next = calculateEntryForDay(entry, midnight);
while (next != null && (!next.isAfter(now) || !isAfterLast(entry, next) || !isWeekdayEnabled(entry, next))) {
log.debug(" -- skipping: next={}", next);
midnight = midnight.plusDays(1);
next = calculateEntryForDay(entry, midnight);
}
log.debug(" => {}", next);
entry.setNextClearTimestamp(next);
}
private boolean isAfterLast(final ScheduleEntry entry, final ZonedDateTime next) {
return entry.getLastClearTimestamp() == null || next.isAfter(entry.getLastClearTimestamp());
}
private ZonedDateTime calculateEntryForDay(final ScheduleEntry entry, final ZonedDateTime midnight) {
switch (entry.getType()) {
case TIME:
return midnight.withHour(entry.getHour()).withMinute(entry.getMinute()).withSecond(entry.getSecond());
case SUNRISE:
case SUNSET:
final boolean sunrise = entry.getType() == ScheduleEntryType.SUNRISE;
return astroCalculator.forDay(midnight, sunrise, entry.getZenith());
default:
log.error("AstroEvent not implemented: {}", entry.getType());
break;
}
return null;
}
private boolean isAnyWeekdayEnabled(final ScheduleEntry entry) {
return entry.isMonday() || entry.isTuesday() || entry.isWednesday() || entry.isThursday() || entry.isFriday() || entry.isSaturday() || entry.isSunday();
}
private boolean isWeekdayEnabled(final ScheduleEntry entry, final ZonedDateTime value) {
return switch (value.getDayOfWeek()) {
case MONDAY -> entry.isMonday();
case TUESDAY -> entry.isTuesday();
case WEDNESDAY -> entry.isWednesday();
case THURSDAY -> entry.isThursday();
case FRIDAY -> entry.isFriday();
case SATURDAY -> entry.isSaturday();
case SUNDAY -> entry.isSunday();
};
}
}