Implemented fuzzy-shift for Scheduler

This commit is contained in:
Patrick Haßel 2021-10-04 10:37:06 +02:00
parent caa7e55d10
commit 36ee78783c
9 changed files with 151 additions and 71 deletions

View File

@ -1,6 +1,7 @@
package de.ph87.homeautomation;
import com.luckycatlabs.sunrisesunset.Zenith;
import de.ph87.homeautomation.knx.group.KnxGroupDto;
import de.ph87.homeautomation.knx.group.KnxGroupRepository;
import de.ph87.homeautomation.knx.group.KnxGroupWriteService;
import de.ph87.homeautomation.schedule.PropertyEntry;
@ -18,20 +19,13 @@ import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Map;
@SuppressWarnings({"unchecked", "UnusedReturnValue", "SameParameterValue", "UnusedAssignment"})
@Slf4j
@Service
@RequiredArgsConstructor
public class DemoDataService {
private static final GroupAddress WOHNZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = new GroupAddress(0, 4, 24);
private static final GroupAddress SCHLAFZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = new GroupAddress(0, 3, 3);
private static final GroupAddress FLUR_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = new GroupAddress(0, 5, 13);
private static final GroupAddress BADEWANNE_SCHALTEN = new GroupAddress(781);
private static final GroupAddress BADEWANNE_STATUS = new GroupAddress(782);
public static final int MIN30_SEC = 30 * 60;
private final KnxGroupWriteService knxGroupWriteService;
@ -41,64 +35,86 @@ public class DemoDataService {
@PostConstruct
public void postConstruct() {
createKnxGroupIfNotExists("Wohnzimmer Rollladen Position Anfahren", WOHNZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "5.001", false);
createKnxGroupIfNotExists("Schlafzimmer Rollladen Position Anfahren", SCHLAFZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "5.001", false);
createKnxGroupIfNotExists("Flur Rollladen Position Anfahren", FLUR_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "5.001", false);
createKnxGroupIfNotExists("Badewanne Schalten", BADEWANNE_SCHALTEN, "1.001", false);
createKnxGroupIfNotExists("Badewanne Status", BADEWANNE_STATUS, "1.001", true);
final KnxGroupDto eg_ambiente_schalten = createKnxGroupIfNotExists("EG Ambiente Schalten", 848, "1.001", false);
final KnxGroupDto og_ambiente_schalten = createKnxGroupIfNotExists("OG Ambiente Schalten", 1539, "1.001", false);
final KnxGroupDto wohnzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Wohnzimmer Rollladen Position Anfahren", new GroupAddress(0, 4, 24), "5.001", false);
final KnxGroupDto schlafzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Schlafzimmer Rollladen Position Anfahren", new GroupAddress(0, 3, 3), "5.001", false);
final KnxGroupDto flur_rollladen_position_anfahren = createKnxGroupIfNotExists("Flur Rollladen Position Anfahren", new GroupAddress(0, 5, 13), "5.001", false);
final KnxGroupDto badewanne_schalten = createKnxGroupIfNotExists("Badewanne Schalten", new GroupAddress(781), "1.001", false);
final KnxGroupDto badewanne_status = createKnxGroupIfNotExists("Badewanne Status", new GroupAddress(782), "1.001", true);
final Schedule wohnzimmer = new Schedule();
wohnzimmer.setName("Rollläden Wohnzimmer");
createSunrise(wohnzimmer, Zenith.OFFICIAL, new PropertyEntry(WOHNZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "0"));
createSunset(wohnzimmer, Zenith.OFFICIAL, new PropertyEntry(WOHNZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "100"));
scheduleRepository.save(wohnzimmer);
final Schedule scheduleEgAmbiente = new Schedule();
scheduleEgAmbiente.setName("EG Ambiente");
createTime(scheduleEgAmbiente, 7, 15, 0, MIN30_SEC, MIN30_SEC, new PropertyEntry(eg_ambiente_schalten.getAddress(), "true"));
createTime(scheduleEgAmbiente, 9, 30, 0, MIN30_SEC, MIN30_SEC, new PropertyEntry(eg_ambiente_schalten.getAddress(), "false"));
createSunset(scheduleEgAmbiente, Zenith.OFFICIAL, MIN30_SEC, MIN30_SEC, new PropertyEntry(eg_ambiente_schalten.getAddress(), "true"));
createSunset(scheduleEgAmbiente, Zenith.ASTRONOMICAL, MIN30_SEC, MIN30_SEC, new PropertyEntry(eg_ambiente_schalten.getAddress(), "false"));
scheduleRepository.save(scheduleEgAmbiente);
final Schedule schlafzimmer = new Schedule();
schlafzimmer.setName("Rollläden Schlafzimmer");
createTime(schlafzimmer, 7, 0, 0, new PropertyEntry(SCHLAFZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "0"));
createTime(schlafzimmer, 20, 0, 0, new PropertyEntry(SCHLAFZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "100"));
scheduleRepository.save(schlafzimmer);
final Schedule scheduleOgAmbiente = new Schedule();
scheduleOgAmbiente.setName("OG Ambiente");
createTime(scheduleOgAmbiente, 7, 15, 0, MIN30_SEC, MIN30_SEC, new PropertyEntry(og_ambiente_schalten.getAddress(), "true"));
createTime(scheduleOgAmbiente, 9, 30, 0, MIN30_SEC, MIN30_SEC, new PropertyEntry(og_ambiente_schalten.getAddress(), "false"));
createSunset(scheduleOgAmbiente, Zenith.OFFICIAL, MIN30_SEC, MIN30_SEC, new PropertyEntry(og_ambiente_schalten.getAddress(), "true"));
createSunset(scheduleOgAmbiente, Zenith.ASTRONOMICAL, MIN30_SEC, MIN30_SEC, new PropertyEntry(og_ambiente_schalten.getAddress(), "false"));
scheduleRepository.save(scheduleOgAmbiente);
final Schedule flur = new Schedule();
flur.setName("Rollläden Flur");
createSunrise(flur, Zenith.NAUTICAL, new PropertyEntry(FLUR_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "0"));
createSunset(flur, Zenith.NAUTICAL, new PropertyEntry(FLUR_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "100"));
scheduleRepository.save(flur);
final Schedule scheduleWohnzimmerRollladen = new Schedule();
scheduleWohnzimmerRollladen.setName("Rollläden Wohnzimmer");
createSunrise(scheduleWohnzimmerRollladen, Zenith.OFFICIAL, 0, 0, new PropertyEntry(wohnzimmer_rollladen_position_anfahren.getAddress(), "0"));
createSunset(scheduleWohnzimmerRollladen, Zenith.OFFICIAL, 0, 0, new PropertyEntry(wohnzimmer_rollladen_position_anfahren.getAddress(), "100"));
scheduleRepository.save(scheduleWohnzimmerRollladen);
final Schedule badewanne = new Schedule();
badewanne.setName("Badewanne");
int seconds = 2;
createRelative(badewanne, seconds += 2, new PropertyEntry(BADEWANNE_SCHALTEN, "true"));
createRelative(badewanne, seconds += 2, new PropertyEntry(BADEWANNE_SCHALTEN, "false"));
createRelative(badewanne, seconds += 2, new PropertyEntry(BADEWANNE_SCHALTEN, "true"));
createRelative(badewanne, seconds += 2, new PropertyEntry(BADEWANNE_SCHALTEN, "false"));
scheduleRepository.save(badewanne);
final Schedule scheduleSchlafzimmerRollladen = new Schedule();
scheduleSchlafzimmerRollladen.setName("Rollläden Schlafzimmer");
createTime(scheduleSchlafzimmerRollladen, 7, 0, 0, 0, 0, new PropertyEntry(schlafzimmer_rollladen_position_anfahren.getAddress(), "0"));
createTime(scheduleSchlafzimmerRollladen, 20, 0, 0, 0, 0, new PropertyEntry(schlafzimmer_rollladen_position_anfahren.getAddress(), "100"));
scheduleRepository.save(scheduleSchlafzimmerRollladen);
final Schedule scheduleFlurRollladen = new Schedule();
scheduleFlurRollladen.setName("Rollläden Flur");
createSunrise(scheduleFlurRollladen, Zenith.NAUTICAL, 0, 0, new PropertyEntry(flur_rollladen_position_anfahren.getAddress(), "0"));
createSunset(scheduleFlurRollladen, Zenith.NAUTICAL, 0, 0, new PropertyEntry(flur_rollladen_position_anfahren.getAddress(), "100"));
scheduleRepository.save(scheduleFlurRollladen);
final Schedule scheduleBadewanneBlinken = new Schedule();
scheduleBadewanneBlinken.setName("Badewanne");
final int interval = 2;
final int fuzzy = 0;
int seconds = interval;
createRelative(scheduleBadewanneBlinken, seconds += interval, fuzzy, fuzzy, new PropertyEntry(badewanne_schalten.getAddress(), "true"));
createRelative(scheduleBadewanneBlinken, seconds += interval, fuzzy, fuzzy, new PropertyEntry(badewanne_schalten.getAddress(), "false"));
createRelative(scheduleBadewanneBlinken, seconds += interval, fuzzy, fuzzy, new PropertyEntry(badewanne_schalten.getAddress(), "true"));
createRelative(scheduleBadewanneBlinken, seconds += interval, fuzzy, fuzzy, new PropertyEntry(badewanne_schalten.getAddress(), "false"));
scheduleRepository.save(scheduleBadewanneBlinken);
}
private void createKnxGroupIfNotExists(final String name, final GroupAddress address, final String dpt, final boolean readable) {
if (!knxGroupRepository.existsByAddressRaw(address.getRawAddress())) {
knxGroupWriteService.create(name, address, dpt, readable);
}
private KnxGroupDto createKnxGroupIfNotExists(final String name, final int address, final String dpt, final boolean readable) {
return createKnxGroupIfNotExists(name, new GroupAddress(address), dpt, readable);
}
private ScheduleEntry createRelative(final Schedule schedule, final int inSeconds, final Map.Entry<String, String>... entries) {
private KnxGroupDto createKnxGroupIfNotExists(final String name, final GroupAddress address, final String dpt, final boolean readable) {
return knxGroupRepository.findByAddressRaw(address.getRawAddress()).map(KnxGroupDto::new).orElseGet(() -> knxGroupWriteService.create(name, address, dpt, readable));
}
private ScheduleEntry createRelative(final Schedule schedule, final int inSeconds, final int fuzzyMinusSeconds, final int fuzzyPlusSeconds, final Map.Entry<String, String>... entries) {
final ZonedDateTime now = ZonedDateTime.now().plusSeconds(inSeconds).withNano(0);
return create(schedule, ScheduleEntryType.TIME, null, now.getHour(), now.getMinute(), now.getSecond(), entries);
return create(schedule, ScheduleEntryType.TIME, null, now.getHour(), now.getMinute(), now.getSecond(), fuzzyMinusSeconds, fuzzyPlusSeconds, entries);
}
private ScheduleEntry createTime(final Schedule schedule, final int hour, final int minute, final int second, final Map.Entry<String, String>... entries) {
return create(schedule, ScheduleEntryType.TIME, null, hour, minute, second, entries);
private ScheduleEntry createTime(final Schedule schedule, final int hour, final int minute, final int second, final int fuzzyMinusSeconds, final int fuzzyPlusSeconds, final Map.Entry<String, String>... entries) {
return create(schedule, ScheduleEntryType.TIME, null, hour, minute, second, fuzzyMinusSeconds, fuzzyPlusSeconds, entries);
}
private ScheduleEntry createSunrise(final Schedule schedule, final Zenith zenith, final Map.Entry<String, String>... entries) {
return create(schedule, ScheduleEntryType.SUNRISE, zenith, 0, 0, 0, entries);
private ScheduleEntry createSunrise(final Schedule schedule, final Zenith zenith, final int fuzzyMinusSeconds, final int fuzzyPlusSeconds, final Map.Entry<String, String>... entries) {
return create(schedule, ScheduleEntryType.SUNRISE, zenith, 0, 0, 0, fuzzyMinusSeconds, fuzzyPlusSeconds, entries);
}
private ScheduleEntry createSunset(final Schedule schedule, final Zenith zenith, final Map.Entry<String, String>... entries) {
return create(schedule, ScheduleEntryType.SUNSET, zenith, 0, 0, 0, entries);
private ScheduleEntry createSunset(final Schedule schedule, final Zenith zenith, final int fuzzyMinusSeconds, final int fuzzyPlusSeconds, final Map.Entry<String, String>... entries) {
return create(schedule, ScheduleEntryType.SUNSET, zenith, 0, 0, 0, fuzzyMinusSeconds, fuzzyPlusSeconds, entries);
}
private ScheduleEntry create(final Schedule schedule, final ScheduleEntryType type, final Zenith zenith, final int hour, final int minute, final int second, final Map.Entry<String, String>... entries) {
private ScheduleEntry create(final Schedule schedule, final ScheduleEntryType type, final Zenith zenith, final int hour, final int minute, final int second, final int fuzzyMinusSeconds, final int fuzzyPlusSeconds, final Map.Entry<String, String>... entries) {
final ScheduleEntry entry = new ScheduleEntry();
entry.setType(type);
if (zenith != null) {
@ -107,6 +123,8 @@ public class DemoDataService {
entry.setHour(hour);
entry.setMinute(minute);
entry.setSecond(second);
entry.setFuzzyMinusSeconds(fuzzyMinusSeconds);
entry.setFuzzyPlusSeconds(fuzzyPlusSeconds);
Arrays.stream(entries).forEach(p -> entry.getProperties().put(p.getKey(), p.getValue()));
schedule.getEntries().add(entry);
return entry;

View File

@ -0,0 +1,43 @@
package de.ph87.homeautomation.knx.group;
import lombok.Data;
import tuwien.auto.calimero.GroupAddress;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
@Data
public class KnxGroupDto {
public final long id;
public final int addressRaw;
public final String addressStr;
public final String dpt;
public final String name;
public final Boolean booleanValue;
public final BigDecimal numberValue;
public final ZonedDateTime valueTimestamp;
public KnxGroupDto(final KnxGroup knxGroup) {
id = knxGroup.getId();
addressRaw = knxGroup.getAddressRaw();
addressStr = knxGroup.getAddressStr();
dpt = knxGroup.getDpt();
name = knxGroup.getName();
booleanValue = knxGroup.getBooleanValue();
numberValue = knxGroup.getNumberValue();
valueTimestamp = knxGroup.getValueTimestamp();
}
public GroupAddress getAddress() {
return new GroupAddress(addressRaw);
}
}

View File

@ -53,13 +53,13 @@ public class KnxGroupWriteService {
knxGroupRepository.findAllByRead_AbleTrue().forEach(knxGroup -> knxGroup.getRead().setNextTimestamp(ZonedDateTime.now()));
}
public void create(final String name, final GroupAddress address, final String dpt, final boolean readable) {
public KnxGroupDto create(final String name, final GroupAddress address, final String dpt, final boolean readable) {
final KnxGroup trans = new KnxGroup();
trans.setAddress(address);
trans.setDpt(dpt);
trans.setName(name);
trans.getRead().setAble(readable);
knxGroupRepository.save(trans);
return new KnxGroupDto(knxGroupRepository.save(trans));
}
public boolean setSendValue(final GroupAddress groupAddress, final String value) throws KnxGroupFormatException {

View File

@ -17,11 +17,6 @@ public class PropertyEntry implements Map.Entry<String, String> {
this.value = value;
}
public PropertyEntry(final String propertyName, final String value) {
this.key = propertyName;
this.value = value;
}
public String setValue(final String value) {
this.value = value;
return value;

View File

@ -45,14 +45,14 @@ public class ScheduleController {
private ScheduleEntryNextDto(final Schedule schedule, final ScheduleEntry entry) {
this.name = schedule.getName();
this.nextTimestamp = entry.getNextDateTime();
this.nextTimestamp = entry.getNextFuzzyTimestamp();
this.properties = new HashMap<>(entry.getProperties());
}
public static Optional<ScheduleEntryNextDto> create(final Schedule schedule, final ZonedDateTime now) {
return schedule.getEntries().stream()
.filter(entry -> entry.getNextDateTime() != null && entry.getNextDateTime().isAfter(now))
.min(Comparator.comparing(ScheduleEntry::getNextDateTime))
.filter(entry -> entry.getNextFuzzyTimestamp() != null && entry.getNextFuzzyTimestamp().isAfter(now))
.min(Comparator.comparing(ScheduleEntry::getNextFuzzyTimestamp))
.map(scheduleEntry -> new ScheduleEntryNextDto(schedule, scheduleEntry));
}

View File

@ -49,9 +49,10 @@ public class ScheduleWriteService {
private void executeLastDue(final Schedule schedule, final ZonedDateTime now) {
schedule.getEntries().stream()
.filter(entry -> entry.getNextDateTime() != null && !entry.getNextDateTime().isAfter(now))
.max(Comparator.comparing(ScheduleEntry::getNextDateTime))
.filter(entry -> entry.getNextFuzzyTimestamp() != null && !entry.getNextFuzzyTimestamp().isAfter(now))
.max(Comparator.comparing(ScheduleEntry::getNextFuzzyTimestamp))
.ifPresent(entry -> {
entry.setLastClearTimestamp(entry.getNextClearTimestamp());
log.info("Executing ScheduleEntry {}", entry);
entry.getProperties().forEach(this::applyPropertyMapEntry);
calculateSchedule(schedule, now);
@ -69,15 +70,15 @@ public class ScheduleWriteService {
private void calculateSchedule(final Schedule schedule, final ZonedDateTime now) {
schedule.getEntries().forEach(scheduleEntry -> calculateEntry(schedule, scheduleEntry, now));
schedule.getEntries().stream()
.filter(entry -> entry.getNextDateTime() != null && entry.getNextDateTime().isAfter(now))
.min(Comparator.comparing(ScheduleEntry::getNextDateTime))
.ifPresent(scheduleEntry -> log.info("Next schedule for \"{}\": {}", schedule.getName(), scheduleEntry.getNextDateTime()));
.filter(entry -> entry.getNextFuzzyTimestamp() != null && entry.getNextFuzzyTimestamp().isAfter(now))
.min(Comparator.comparing(ScheduleEntry::getNextFuzzyTimestamp))
.ifPresent(scheduleEntry -> log.info("Next schedule for \"{}\": {}", schedule.getName(), scheduleEntry.getNextFuzzyTimestamp()));
}
private void calculateEntry(final Schedule schedule, final ScheduleEntry entry, final ZonedDateTime now) {
log.debug("calculateNext \"{}\", {}:", schedule.getName(), entry);
if (!entry.isEnabled() || !isAnyWeekdayEnabled(entry)) {
entry.setNextDateTime(null);
entry.setNextClearTimestamp(null);
return;
}
ZonedDateTime midnight = now.withHour(0).withMinute(0).withSecond(0).withNano(0);
@ -88,7 +89,7 @@ public class ScheduleWriteService {
next = calculateEntryForDay(entry, midnight);
}
log.debug(" => {}", next);
entry.setNextDateTime(next);
entry.setNextClearTimestamp(next);
}
private ZonedDateTime calculateEntryForDay(final ScheduleEntry entry, final ZonedDateTime midnight) {
@ -151,7 +152,7 @@ public class ScheduleWriteService {
}
public Optional<ZonedDateTime> getNextTimestamp() {
return scheduleEntryRepository.findFirstNextDateTimeByNextDateTimeNotNullOrderByNextDateTimeAsc().map(ScheduleEntry::getNextDateTime);
return scheduleEntryRepository.findFirstByNextFuzzyTimestampNotNullOrderByNextFuzzyTimestampAsc().map(ScheduleEntry::getNextFuzzyTimestamp);
}
}

View File

@ -9,12 +9,15 @@ import javax.persistence.*;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@Getter
@Setter
@Entity
public class ScheduleEntry {
private static final Random RANDOM = new Random(System.currentTimeMillis());
@Id
@GeneratedValue
@Setter(AccessLevel.NONE)
@ -47,11 +50,31 @@ public class ScheduleEntry {
private int second;
private ZonedDateTime nextDateTime;
private int fuzzyMinusSeconds = 0;
private int fuzzyPlusSeconds = 0;
private ZonedDateTime nextClearTimestamp;
private ZonedDateTime lastClearTimestamp;
@Setter(AccessLevel.NONE)
private ZonedDateTime nextFuzzyTimestamp;
@ElementCollection(fetch = FetchType.EAGER)
private Map<String, String> properties = new HashMap<>();
public void setNextClearTimestamp(final ZonedDateTime next) {
nextClearTimestamp = next;
if (nextClearTimestamp != null && (lastClearTimestamp == null || nextClearTimestamp.compareTo(lastClearTimestamp) != 0)) {
final int secondsRange = fuzzyPlusSeconds + fuzzyMinusSeconds;
final int fuzzySeconds = secondsRange > 0 ? RANDOM.nextInt(secondsRange) - fuzzyMinusSeconds : 0;
nextFuzzyTimestamp = nextClearTimestamp.plusSeconds(fuzzySeconds);
} else {
nextFuzzyTimestamp = null;
}
}
public void setWorkday(final boolean enabled) {
monday = enabled;
tuesday = enabled;

View File

@ -56,7 +56,7 @@ public class ScheduleEntryDto {
hour = scheduleEntry.getHour();
minute = scheduleEntry.getMinute();
second = scheduleEntry.getSecond();
nextDateTime = scheduleEntry.getNextDateTime();
nextDateTime = scheduleEntry.getNextFuzzyTimestamp();
properties = new HashMap<>(scheduleEntry.getProperties());
}

View File

@ -6,6 +6,6 @@ import java.util.Optional;
public interface ScheduleEntryRepository extends CrudRepository<ScheduleEntry, Long> {
Optional<ScheduleEntry> findFirstNextDateTimeByNextDateTimeNotNullOrderByNextDateTimeAsc();
Optional<ScheduleEntry> findFirstByNextFuzzyTimestampNotNullOrderByNextFuzzyTimestampAsc();
}