implemented Schedule
This commit is contained in:
parent
28cf4eed2e
commit
82313a1932
9
pom.xml
9
pom.xml
@ -10,8 +10,8 @@
|
||||
<packaging>war</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<maven.compiler.source>15</maven.compiler.source>
|
||||
<maven.compiler.target>15</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
@ -56,6 +56,11 @@
|
||||
<artifactId>calimero-core</artifactId>
|
||||
<version>2.5-M1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.luckycatlabs</groupId>
|
||||
<artifactId>SunriseSunsetCalculator</artifactId>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@ -1,54 +1,15 @@
|
||||
package de.ph87.homeautomation;
|
||||
|
||||
import de.ph87.homeautomation.knx.KnxLinkService;
|
||||
import de.ph87.homeautomation.knx.group.KnxGroup;
|
||||
import de.ph87.homeautomation.knx.group.KnxGroupRepository;
|
||||
import de.ph87.homeautomation.knx.group.KnxGroupWriteService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@SpringBootApplication
|
||||
@RequiredArgsConstructor
|
||||
public class BackendApplication {
|
||||
|
||||
private final KnxGroupRepository knxGroupRepository;
|
||||
|
||||
private final KnxLinkService knxLinkService;
|
||||
|
||||
private final KnxGroupWriteService knxGroupWriteService;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BackendApplication.class);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
knxGroupCreate(0, 0, 1, "1.001");
|
||||
knxGroupCreate(0, 3, 6, "1.001");
|
||||
|
||||
requestAll();
|
||||
}
|
||||
|
||||
private void knxGroupCreate(final int main, final int middle, final int sub, final String dpt) {
|
||||
final KnxGroup trans = new KnxGroup();
|
||||
trans.setAddress(main, middle, sub);
|
||||
trans.setDpt(dpt);
|
||||
knxGroupRepository.save(trans);
|
||||
}
|
||||
|
||||
public void requestAll() {
|
||||
knxGroupWriteService.markAllForRead();
|
||||
knxLinkService.notifyActionPending();
|
||||
}
|
||||
|
||||
@EventListener(ApplicationStartedEvent.class)
|
||||
public void applicationStarted() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
18
src/main/java/de/ph87/homeautomation/Config.java
Normal file
18
src/main/java/de/ph87/homeautomation/Config.java
Normal file
@ -0,0 +1,18 @@
|
||||
package de.ph87.homeautomation;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "de.ph87.homeautomation")
|
||||
public class Config {
|
||||
|
||||
private double latitude = 49.4085629;
|
||||
|
||||
private double longitude = 6.9645334;
|
||||
|
||||
private String timezone = "Europe/Berlin";
|
||||
|
||||
}
|
||||
@ -2,6 +2,7 @@ package de.ph87.homeautomation.knx;
|
||||
|
||||
import de.ph87.homeautomation.knx.group.KnxGroupLinkService;
|
||||
import de.ph87.homeautomation.knx.group.KnxGroupWriteService;
|
||||
import de.ph87.network.router.Router;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
@ -19,6 +20,7 @@ import tuwien.auto.calimero.process.ProcessEvent;
|
||||
import tuwien.auto.calimero.process.ProcessListener;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
@ -31,15 +33,13 @@ import java.time.ZonedDateTime;
|
||||
@RequiredArgsConstructor
|
||||
public class KnxLinkService implements NetworkLinkListener, ProcessListener {
|
||||
|
||||
public static final int ERROR_DELAY_MS = 3000;
|
||||
|
||||
private final KnxGroupWriteService knxGroupWriteService;
|
||||
|
||||
private final KnxGroupLinkService knxGroupLinkService;
|
||||
|
||||
private InetAddress remoteAddress = null;
|
||||
private Inet4Address remoteAddress = null;
|
||||
|
||||
private final Thread thread = new Thread(this::run);
|
||||
private final Thread thread = new Thread(this::run, "knx-sync");
|
||||
|
||||
private boolean stop = false;
|
||||
|
||||
@ -51,7 +51,7 @@ public class KnxLinkService implements NetworkLinkListener, ProcessListener {
|
||||
|
||||
@EventListener(ApplicationStartedEvent.class)
|
||||
public void afterStartup() throws UnknownHostException {
|
||||
remoteAddress = Inet4Address.getByName("10.0.0.102");
|
||||
remoteAddress = (Inet4Address) Inet4Address.getByName("10.0.0.102");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ public class KnxLinkService implements NetworkLinkListener, ProcessListener {
|
||||
if (link == null) {
|
||||
try {
|
||||
connect();
|
||||
} catch (KNXException e) {
|
||||
} catch (KNXException | IOException e) {
|
||||
error(e);
|
||||
}
|
||||
} else {
|
||||
@ -87,9 +87,10 @@ public class KnxLinkService implements NetworkLinkListener, ProcessListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void connect() throws KNXException, InterruptedException {
|
||||
private void connect() throws KNXException, InterruptedException, IOException {
|
||||
log.debug("Connecting KNX link...");
|
||||
link = KNXNetworkLinkIP.newTunnelingLink(new InetSocketAddress("10.0.0.132", 0), new InetSocketAddress(remoteAddress, 3671), false, new TPSettings());
|
||||
final InetAddress localAddress = Router.getLocalInetAddressForRemoteAddress(remoteAddress);
|
||||
link = KNXNetworkLinkIP.newTunnelingLink(new InetSocketAddress(localAddress, 0), new InetSocketAddress(remoteAddress, 3671), false, new TPSettings());
|
||||
link.addLinkListener(this);
|
||||
processCommunicator = new ProcessCommunicatorImpl(link);
|
||||
processCommunicator.addProcessListener(this);
|
||||
@ -114,9 +115,10 @@ public class KnxLinkService implements NetworkLinkListener, ProcessListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void error(final KNXException e) throws InterruptedException {
|
||||
private void error(final Exception e) throws InterruptedException {
|
||||
log.error(e.toString());
|
||||
cleanUp();
|
||||
int ERROR_DELAY_MS = 3000;
|
||||
doWait(ERROR_DELAY_MS);
|
||||
}
|
||||
|
||||
@ -169,7 +171,8 @@ public class KnxLinkService implements NetworkLinkListener, ProcessListener {
|
||||
|
||||
@Override
|
||||
public void groupWrite(final ProcessEvent processEvent) {
|
||||
// ignore
|
||||
log.debug("{}", processEvent);
|
||||
knxGroupWriteService.updateOrCreate(processEvent.getDestination().getRawAddress(), processEvent.getASDU());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
package de.ph87.homeautomation.knx.group;
|
||||
|
||||
import de.ph87.homeautomation.knx.KnxLinkService;
|
||||
import de.ph87.homeautomation.property.IPropertyOwner;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class KnxGroupSetService implements IPropertyOwner {
|
||||
|
||||
@Getter
|
||||
private final Pattern propertyNamePattern = Pattern.compile("^knx\\.group\\.(\\d+)/(\\d+)/(\\d+)$");
|
||||
|
||||
private final KnxLinkService knxLinkService;
|
||||
|
||||
private final KnxGroupWriteService knxGroupWriteService;
|
||||
|
||||
@EventListener(ApplicationStartedEvent.class)
|
||||
public void applicationStarted() {
|
||||
knxGroupWriteService.knxGroupCreate(0, 0, 1, "1.001", true);
|
||||
knxGroupWriteService.knxGroupCreate(0, 3, 6, "1.001", true);
|
||||
knxGroupWriteService.knxGroupCreate(0, 4, 24, "5.001", false);
|
||||
|
||||
requestAll();
|
||||
}
|
||||
|
||||
public void requestAll() {
|
||||
knxGroupWriteService.markAllForRead();
|
||||
knxLinkService.notifyActionPending();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(final String propertyName, final String value) {
|
||||
final Matcher matcher = propertyNamePattern.matcher(propertyName);
|
||||
if (matcher.matches()) {
|
||||
final GroupAddress groupAddress = new GroupAddress(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(3)));
|
||||
if (knxGroupWriteService.setSendValue(groupAddress, value)) {
|
||||
knxLinkService.notifyActionPending();
|
||||
} else {
|
||||
log.error("No such KnxGroup.address = {}", groupAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -3,13 +3,21 @@ package de.ph87.homeautomation.knx.group;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
import tuwien.auto.calimero.KNXException;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
import tuwien.auto.calimero.dptxlator.TranslatorTypes;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
@Transactional(Transactional.TxType.REQUIRES_NEW)
|
||||
@RequiredArgsConstructor
|
||||
public class KnxGroupWriteService {
|
||||
|
||||
@ -36,4 +44,41 @@ public class KnxGroupWriteService {
|
||||
knxGroupRepository.findAllByRead_AbleTrue().forEach(knxGroup -> knxGroup.getRead().setNextTimestamp(ZonedDateTime.now()));
|
||||
}
|
||||
|
||||
public void knxGroupCreate(final int main, final int middle, final int sub, final String dpt, final boolean readable) {
|
||||
final KnxGroup trans = new KnxGroup();
|
||||
trans.setAddress(main, middle, sub);
|
||||
trans.setDpt(dpt);
|
||||
trans.getRead().setAble(readable);
|
||||
knxGroupRepository.save(trans);
|
||||
}
|
||||
|
||||
public boolean setSendValue(final GroupAddress groupAddress, final String value) {
|
||||
final Optional<KnxGroup> knxGroupOptional = knxGroupRepository.findByAddressRaw(groupAddress.getRawAddress());
|
||||
if (knxGroupOptional.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final KnxGroup knxGroup = knxGroupOptional.get();
|
||||
getTranslator(knxGroup, value).ifPresent(translator -> knxGroup.setSendValue(translator.getData()));
|
||||
knxGroup.getSend().setNextTimestamp(ZonedDateTime.now());
|
||||
return true;
|
||||
}
|
||||
|
||||
private Optional<DPTXlator> getTranslator(final KnxGroup knxGroup, final String value) {
|
||||
final int mainNumber = Integer.parseInt(knxGroup.getDpt().split("\\.")[0]);
|
||||
try {
|
||||
final DPTXlator translator = TranslatorTypes.createTranslator(mainNumber, knxGroup.getDpt());
|
||||
if (translator instanceof DPTXlatorBoolean) {
|
||||
((DPTXlatorBoolean) translator).setValue(Objects.equals(value, "true"));
|
||||
} else if (translator instanceof DPTXlator8BitUnsigned) {
|
||||
((DPTXlator8BitUnsigned) translator).setValue(Integer.parseInt(value));
|
||||
} else {
|
||||
translator.setValue(value);
|
||||
}
|
||||
return Optional.of(translator);
|
||||
} catch (KNXException e) {
|
||||
log.error(e.toString());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
package de.ph87.homeautomation.property;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public interface IPropertyOwner {
|
||||
|
||||
Pattern getPropertyNamePattern();
|
||||
|
||||
void setProperty(final String propertyName, final String value);
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package de.ph87.homeautomation.property;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PropertyService {
|
||||
|
||||
private final Set<IPropertyOwner> propertyOwners;
|
||||
|
||||
public void set(final String propertyName, final String value) {
|
||||
log.info("Setting property \"{}\" => {}", propertyName, value);
|
||||
final Optional<IPropertyOwner> iPropertyOwnerOptional = propertyOwners.stream().filter(iPropertyOwner -> iPropertyOwner.getPropertyNamePattern().matcher(propertyName).matches()).findFirst();
|
||||
if (iPropertyOwnerOptional.isEmpty()) {
|
||||
log.error("No IPropertyOwner found for name: {}", propertyName);
|
||||
return;
|
||||
}
|
||||
iPropertyOwnerOptional.get().setProperty(propertyName, value);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package de.ph87.homeautomation.schedule;
|
||||
|
||||
import lombok.Data;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class PropertyEntry implements Map.Entry<String, String> {
|
||||
|
||||
private final String key;
|
||||
|
||||
private String value;
|
||||
|
||||
public PropertyEntry(final int rawGroupAddress, final String value) {
|
||||
this.key = "knx.group." + new GroupAddress(rawGroupAddress);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
33
src/main/java/de/ph87/homeautomation/schedule/Schedule.java
Normal file
33
src/main/java/de/ph87/homeautomation/schedule/Schedule.java
Normal file
@ -0,0 +1,33 @@
|
||||
package de.ph87.homeautomation.schedule;
|
||||
|
||||
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@Entity
|
||||
public class Schedule {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@Setter(AccessLevel.NONE)
|
||||
private Long id;
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private String name;
|
||||
|
||||
@ToString.Exclude
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private Set<ScheduleEntry> entries = new HashSet<>();
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package de.ph87.homeautomation.schedule;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ScheduleRepository extends CrudRepository<Schedule, Long> {
|
||||
|
||||
List<Schedule> findAll();
|
||||
|
||||
}
|
||||
@ -0,0 +1,197 @@
|
||||
package de.ph87.homeautomation.schedule;
|
||||
|
||||
import com.luckycatlabs.sunrisesunset.Zenith;
|
||||
import com.luckycatlabs.sunrisesunset.calculator.SolarEventCalculator;
|
||||
import com.luckycatlabs.sunrisesunset.dto.Location;
|
||||
import de.ph87.homeautomation.Config;
|
||||
import de.ph87.homeautomation.property.PropertyService;
|
||||
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.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.transaction.Transactional;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
@EnableScheduling
|
||||
@RequiredArgsConstructor
|
||||
public class ScheduleService {
|
||||
|
||||
public static final int WOHNZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = 1048;
|
||||
|
||||
public static final int SCHLAFZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = 771;
|
||||
|
||||
public static final int FLUR_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = 1293;
|
||||
|
||||
private final Config config;
|
||||
|
||||
private final ScheduleRepository scheduleRepository;
|
||||
|
||||
private final PropertyService propertyService;
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
final Schedule wohnzimmer = new Schedule();
|
||||
wohnzimmer.setEnabled(true);
|
||||
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 schlafzimmer = new Schedule();
|
||||
schlafzimmer.setEnabled(true);
|
||||
schlafzimmer.setName("Rollläden Schlafzimmer");
|
||||
createTime(schlafzimmer, 7, 0, new PropertyEntry(SCHLAFZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "0"));
|
||||
createTime(schlafzimmer, 20, 0, new PropertyEntry(SCHLAFZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS, "100"));
|
||||
scheduleRepository.save(schlafzimmer);
|
||||
|
||||
final Schedule flur = new Schedule();
|
||||
flur.setEnabled(true);
|
||||
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);
|
||||
}
|
||||
|
||||
private ScheduleEntry createTime(final Schedule schedule, final Map.Entry<String, String>... entries) {
|
||||
final ZonedDateTime dateTime = ZonedDateTime.now().plusMinutes(1).withSecond(0).withNano(0);
|
||||
return create(schedule, ScheduleEntryType.TIME, null, dateTime.getHour(), dateTime.getMinute(), entries);
|
||||
}
|
||||
|
||||
private ScheduleEntry createTime(final Schedule schedule, final int hour, final int minute, final Map.Entry<String, String>... entries) {
|
||||
return create(schedule, ScheduleEntryType.TIME, null, hour, minute, entries);
|
||||
}
|
||||
|
||||
private ScheduleEntry createSunrise(final Schedule schedule, final Zenith zenith, final Map.Entry<String, String>... entries) {
|
||||
return create(schedule, ScheduleEntryType.SUNRISE, zenith, 0, 0, entries);
|
||||
}
|
||||
|
||||
private ScheduleEntry createSunset(final Schedule schedule, final Zenith zenith, final Map.Entry<String, String>... entries) {
|
||||
return create(schedule, ScheduleEntryType.SUNSET, zenith, 0, 0, entries);
|
||||
}
|
||||
|
||||
private ScheduleEntry create(final Schedule schedule, final ScheduleEntryType type, final Zenith zenith, final int hour, final int minute, final Map.Entry<String, String>... entries) {
|
||||
final ScheduleEntry entry = new ScheduleEntry();
|
||||
entry.setType(type);
|
||||
if (zenith != null) {
|
||||
entry.setZenith(zenith.degrees().doubleValue());
|
||||
}
|
||||
entry.setHour(hour);
|
||||
entry.setMinute(minute);
|
||||
Arrays.stream(entries).forEach(p -> entry.getProperties().put(p.getKey(), p.getValue()));
|
||||
schedule.getEntries().add(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
@EventListener(ApplicationStartedEvent.class)
|
||||
public void startup() {
|
||||
final ZonedDateTime now = ZonedDateTime.now();
|
||||
scheduleRepository.findAll().forEach(schedule -> schedule.getEntries().forEach(entry -> calculateNext(schedule, entry, now)));
|
||||
}
|
||||
|
||||
@Scheduled(initialDelay = 1000, fixedRate = 1000)
|
||||
public void execute() {
|
||||
final ZonedDateTime now = ZonedDateTime.now();
|
||||
scheduleRepository.findAll().forEach(schedule -> execute(schedule, now));
|
||||
}
|
||||
|
||||
private void execute(final Schedule schedule, final ZonedDateTime now) {
|
||||
schedule.getEntries().stream()
|
||||
.filter(entry -> entry.getNextDateTime() != null && !entry.getNextDateTime().isAfter(now)) // TODO check nextTimestamp with an threshold of +/-30 sec (= half execution interval)!?
|
||||
.max(Comparator.comparing(ScheduleEntry::getNextDateTime))
|
||||
.ifPresent(entry -> {
|
||||
log.info("Executing ScheduleEntry {}", entry);
|
||||
calculateNext(schedule, entry, now);
|
||||
entry.getProperties().forEach(propertyService::set);
|
||||
});
|
||||
}
|
||||
|
||||
private void calculateNext(final Schedule schedule, final ScheduleEntry entry, final ZonedDateTime now) {
|
||||
log.debug("calculateNext \"{}\", {}:", schedule.getName(), entry);
|
||||
if (!entry.isEnabled() || !isAnyWeekdayEnabled(entry)) {
|
||||
entry.setNextDateTime(null);
|
||||
return;
|
||||
}
|
||||
ZonedDateTime midnight = now.withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
ZonedDateTime next = calculateNextForDay(entry, midnight);
|
||||
log.debug(" - {}", next);
|
||||
while (next != null && (!next.isAfter(now) || !isWeekdayValid(entry, next))) { // TODO check nextTimestamp with an threshold of +/-30 sec (= half execution interval)!?
|
||||
midnight = midnight.plusDays(1);
|
||||
next = calculateNextForDay(entry, midnight);
|
||||
log.debug(" - {}", next);
|
||||
}
|
||||
log.debug("done");
|
||||
entry.setNextDateTime(next);
|
||||
}
|
||||
|
||||
private ZonedDateTime calculateNextForDay(final ScheduleEntry entry, final ZonedDateTime midnight) {
|
||||
switch (entry.getType()) {
|
||||
case TIME:
|
||||
return midnight.withHour(entry.getHour()).withMinute(entry.getMinute());
|
||||
case SUNRISE:
|
||||
case SUNSET:
|
||||
return calculateNextModeAstro(entry, midnight);
|
||||
default:
|
||||
log.error("AstroEvent not implemented: {}", entry.getType());
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ZonedDateTime calculateNextModeAstro(final ScheduleEntry entry, ZonedDateTime midnight) {
|
||||
final Location location = new Location(config.getLatitude(), config.getLongitude());
|
||||
final SolarEventCalculator calculator = new SolarEventCalculator(location, config.getTimezone());
|
||||
final Calendar calendar = GregorianCalendar.from(midnight);
|
||||
final Calendar nextCalendar = calculateNextModeAstro(calculator, entry.getType(), new Zenith(entry.getZenith()), calendar);
|
||||
if (nextCalendar == null) {
|
||||
return null;
|
||||
}
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(nextCalendar.getTimeInMillis()), midnight.getZone());
|
||||
}
|
||||
|
||||
private Calendar calculateNextModeAstro(final SolarEventCalculator calculator, final ScheduleEntryType type, final Zenith solarZenith, final Calendar calendar) {
|
||||
switch (type) {
|
||||
case SUNRISE:
|
||||
return calculator.computeSunriseCalendar(solarZenith, calendar);
|
||||
case SUNSET:
|
||||
return calculator.computeSunsetCalendar(solarZenith, calendar);
|
||||
}
|
||||
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 isWeekdayValid(final ScheduleEntry entry, final ZonedDateTime value) {
|
||||
switch (value.getDayOfWeek()) {
|
||||
case MONDAY:
|
||||
return entry.isMonday();
|
||||
case TUESDAY:
|
||||
return entry.isTuesday();
|
||||
case WEDNESDAY:
|
||||
return entry.isWednesday();
|
||||
case THURSDAY:
|
||||
return entry.isThursday();
|
||||
case FRIDAY:
|
||||
return entry.isFriday();
|
||||
case SATURDAY:
|
||||
return entry.isSaturday();
|
||||
case SUNDAY:
|
||||
return entry.isSunday();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
package de.ph87.homeautomation.schedule.entry;
|
||||
|
||||
import com.luckycatlabs.sunrisesunset.Zenith;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
public class ScheduleEntry {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@Setter(AccessLevel.NONE)
|
||||
private Long id;
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private boolean monday = true;
|
||||
|
||||
private boolean tuesday = true;
|
||||
|
||||
private boolean wednesday = true;
|
||||
|
||||
private boolean thursday = true;
|
||||
|
||||
private boolean friday = true;
|
||||
|
||||
private boolean saturday = true;
|
||||
|
||||
private boolean sunday = true;
|
||||
|
||||
@Column(nullable = false)
|
||||
private ScheduleEntryType type = null;
|
||||
|
||||
private double zenith = Zenith.CIVIL.degrees().doubleValue();
|
||||
|
||||
private int hour;
|
||||
|
||||
private int minute;
|
||||
|
||||
private ZonedDateTime nextDateTime;
|
||||
|
||||
@ElementCollection
|
||||
private Map<String, String> properties = new HashMap<>();
|
||||
|
||||
public void setWorkday(final boolean enabled) {
|
||||
monday = enabled;
|
||||
tuesday = enabled;
|
||||
wednesday = enabled;
|
||||
thursday = enabled;
|
||||
friday = enabled;
|
||||
}
|
||||
|
||||
public void setWeekend(final boolean enabled) {
|
||||
saturday = enabled;
|
||||
sunday = enabled;
|
||||
}
|
||||
|
||||
public void setEveryday(final boolean enabled) {
|
||||
setWorkday(enabled);
|
||||
setWeekend(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append(getClass().getSimpleName());
|
||||
builder.append("(id=");
|
||||
builder.append(id);
|
||||
builder.append(", enabled=");
|
||||
builder.append(enabled);
|
||||
builder.append(", type=");
|
||||
builder.append(type);
|
||||
builder.append(", weekdays=");
|
||||
builder.append(monday ? 1 : 0);
|
||||
builder.append(tuesday ? 1 : 0);
|
||||
builder.append(wednesday ? 1 : 0);
|
||||
builder.append(thursday ? 1 : 0);
|
||||
builder.append(friday ? 1 : 0);
|
||||
builder.append(saturday ? 1 : 0);
|
||||
builder.append(sunday ? 1 : 0);
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case TIME:
|
||||
builder.append(", hour=");
|
||||
builder.append(hour);
|
||||
builder.append(", minute=");
|
||||
builder.append(minute);
|
||||
break;
|
||||
case SUNRISE:
|
||||
case SUNSET:
|
||||
builder.append(", zenith=");
|
||||
builder.append(zenith);
|
||||
break;
|
||||
}
|
||||
}
|
||||
builder.append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package de.ph87.homeautomation.schedule.entry;
|
||||
|
||||
public enum ScheduleEntryType {
|
||||
TIME, SUNRISE, SUNSET
|
||||
}
|
||||
90
src/main/java/de/ph87/network/router/Route.java
Normal file
90
src/main/java/de/ph87/network/router/Route.java
Normal file
@ -0,0 +1,90 @@
|
||||
package de.ph87.network.router;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Route {
|
||||
|
||||
private static final Pattern regex = Pattern.compile("^([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\\s+(\\S+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\S+)$");
|
||||
|
||||
public final Inet4Address network;
|
||||
|
||||
public final Inet4Address router;
|
||||
|
||||
public final Inet4Address netmask;
|
||||
|
||||
public final int metric;
|
||||
|
||||
public final String iface;
|
||||
|
||||
public final int networkInt;
|
||||
|
||||
public final int routerInt;
|
||||
|
||||
public final int netmaskInt;
|
||||
|
||||
public final Inet4Address addressFirst;
|
||||
|
||||
public final int addressFirstInt;
|
||||
|
||||
public final Inet4Address addressLast;
|
||||
|
||||
public final int addressLastInt;
|
||||
|
||||
public Route(final Inet4Address network, final Inet4Address router, final Inet4Address netmask, final int metric, final String iface) throws UnknownHostException {
|
||||
this.network = network;
|
||||
this.router = router;
|
||||
this.netmask = netmask;
|
||||
this.metric = metric;
|
||||
this.iface = iface;
|
||||
|
||||
this.networkInt = toInt(network);
|
||||
this.routerInt = toInt(router);
|
||||
this.netmaskInt = toInt(netmask);
|
||||
this.addressFirstInt = networkInt & netmaskInt;
|
||||
this.addressLastInt = networkInt | ~netmaskInt;
|
||||
this.addressFirst = fromInt(addressFirstInt);
|
||||
this.addressLast = fromInt(addressLastInt);
|
||||
}
|
||||
|
||||
public boolean matches(final Inet4Address address) {
|
||||
final int addressInt = toInt(address);
|
||||
return addressFirstInt <= addressInt && addressInt <= addressLastInt;
|
||||
}
|
||||
|
||||
private static int toInt(final Inet4Address address) {
|
||||
return ByteBuffer.wrap(address.getAddress()).getInt();
|
||||
}
|
||||
|
||||
private static Inet4Address fromInt(final int value) throws UnknownHostException {
|
||||
return (Inet4Address) Inet4Address.getByAddress(ByteBuffer.allocate(4).putInt(value).array());
|
||||
}
|
||||
|
||||
public static Optional<Route> parse(final String line) {
|
||||
final Matcher matcher = regex.matcher(line);
|
||||
try {
|
||||
if (matcher.matches()) {
|
||||
final String networkString = matcher.group(1);
|
||||
final Inet4Address network = (Inet4Address) Inet4Address.getByName(Objects.equals(networkString, "default") ? "0.0.0.0" : networkString);
|
||||
final Inet4Address router = (Inet4Address) Inet4Address.getByName(matcher.group(2));
|
||||
final Inet4Address netmask = (Inet4Address) Inet4Address.getByName(matcher.group(3));
|
||||
final int metric = Integer.parseInt(matcher.group(5));
|
||||
final String iface = matcher.group(8);
|
||||
return Optional.of(new Route(network, router, netmask, metric, iface));
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return network.isAnyLocalAddress() && netmask.isAnyLocalAddress();
|
||||
}
|
||||
|
||||
}
|
||||
42
src/main/java/de/ph87/network/router/Router.java
Normal file
42
src/main/java/de/ph87/network/router/Router.java
Normal file
@ -0,0 +1,42 @@
|
||||
package de.ph87.network.router;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Router {
|
||||
|
||||
public static InetAddress getLocalInetAddressForRemoteAddress(final Inet4Address remoteAddress) throws IOException {
|
||||
final Route route = getRoute(remoteAddress);
|
||||
final NetworkInterface networkInterface = NetworkInterface.getByName(route.iface);
|
||||
return networkInterface.getInetAddresses().nextElement();
|
||||
}
|
||||
|
||||
public static Route getRoute(final Inet4Address remoteAddress) throws IOException {
|
||||
final List<Route> routes = execute(Route::parse, "route", "-n");
|
||||
final Optional<Route> routeOptional = routes.stream()
|
||||
.filter(r -> !r.isDefault())
|
||||
.filter(r -> r.matches(remoteAddress))
|
||||
.reduce((a, b) -> a.metric < b.metric ? a : b)
|
||||
.or(() -> routes.stream()
|
||||
.filter(Route::isDefault)
|
||||
.reduce((a, b) -> a.metric < b.metric ? a : b)
|
||||
);
|
||||
if (routeOptional.isEmpty()) {
|
||||
throw new IOException("No route found for remoteAddress: " + remoteAddress);
|
||||
}
|
||||
return routeOptional.get();
|
||||
}
|
||||
|
||||
private static <T> List<T> execute(final Function<String, Optional<T>> parser, final String... commands) throws IOException {
|
||||
final String output = new String(new ProcessBuilder(commands).start().getInputStream().readAllBytes());
|
||||
return Arrays.stream(output.split("\\n")).map(parser).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user