implemented Schedule

This commit is contained in:
Patrick Haßel 2021-10-01 12:47:23 +02:00
parent 28cf4eed2e
commit 82313a1932
16 changed files with 693 additions and 52 deletions

View File

@ -10,8 +10,8 @@
<packaging>war</packaging> <packaging>war</packaging>
<properties> <properties>
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>15</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>15</maven.compiler.target>
</properties> </properties>
<parent> <parent>
@ -56,6 +56,11 @@
<artifactId>calimero-core</artifactId> <artifactId>calimero-core</artifactId>
<version>2.5-M1</version> <version>2.5-M1</version>
</dependency> </dependency>
<dependency>
<groupId>com.luckycatlabs</groupId>
<artifactId>SunriseSunsetCalculator</artifactId>
<version>1.2</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -1,54 +1,15 @@
package de.ph87.homeautomation; 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 lombok.RequiredArgsConstructor;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import javax.annotation.PostConstruct;
@SpringBootApplication @SpringBootApplication
@RequiredArgsConstructor @RequiredArgsConstructor
public class BackendApplication { public class BackendApplication {
private final KnxGroupRepository knxGroupRepository;
private final KnxLinkService knxLinkService;
private final KnxGroupWriteService knxGroupWriteService;
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(BackendApplication.class); 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() {
}
} }

View 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";
}

View File

@ -2,6 +2,7 @@ package de.ph87.homeautomation.knx;
import de.ph87.homeautomation.knx.group.KnxGroupLinkService; import de.ph87.homeautomation.knx.group.KnxGroupLinkService;
import de.ph87.homeautomation.knx.group.KnxGroupWriteService; import de.ph87.homeautomation.knx.group.KnxGroupWriteService;
import de.ph87.network.router.Router;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.boot.context.event.ApplicationStartedEvent;
@ -19,6 +20,7 @@ import tuwien.auto.calimero.process.ProcessEvent;
import tuwien.auto.calimero.process.ProcessListener; import tuwien.auto.calimero.process.ProcessListener;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.io.IOException;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -31,15 +33,13 @@ import java.time.ZonedDateTime;
@RequiredArgsConstructor @RequiredArgsConstructor
public class KnxLinkService implements NetworkLinkListener, ProcessListener { public class KnxLinkService implements NetworkLinkListener, ProcessListener {
public static final int ERROR_DELAY_MS = 3000;
private final KnxGroupWriteService knxGroupWriteService; private final KnxGroupWriteService knxGroupWriteService;
private final KnxGroupLinkService knxGroupLinkService; 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; private boolean stop = false;
@ -51,7 +51,7 @@ public class KnxLinkService implements NetworkLinkListener, ProcessListener {
@EventListener(ApplicationStartedEvent.class) @EventListener(ApplicationStartedEvent.class)
public void afterStartup() throws UnknownHostException { public void afterStartup() throws UnknownHostException {
remoteAddress = Inet4Address.getByName("10.0.0.102"); remoteAddress = (Inet4Address) Inet4Address.getByName("10.0.0.102");
thread.start(); thread.start();
} }
@ -73,7 +73,7 @@ public class KnxLinkService implements NetworkLinkListener, ProcessListener {
if (link == null) { if (link == null) {
try { try {
connect(); connect();
} catch (KNXException e) { } catch (KNXException | IOException e) {
error(e); error(e);
} }
} else { } 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..."); 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); link.addLinkListener(this);
processCommunicator = new ProcessCommunicatorImpl(link); processCommunicator = new ProcessCommunicatorImpl(link);
processCommunicator.addProcessListener(this); 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()); log.error(e.toString());
cleanUp(); cleanUp();
int ERROR_DELAY_MS = 3000;
doWait(ERROR_DELAY_MS); doWait(ERROR_DELAY_MS);
} }
@ -169,7 +171,8 @@ public class KnxLinkService implements NetworkLinkListener, ProcessListener {
@Override @Override
public void groupWrite(final ProcessEvent processEvent) { public void groupWrite(final ProcessEvent processEvent) {
// ignore log.debug("{}", processEvent);
knxGroupWriteService.updateOrCreate(processEvent.getDestination().getRawAddress(), processEvent.getASDU());
} }
@Override @Override

View File

@ -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);
}
}
}
}

View File

@ -3,13 +3,21 @@ package de.ph87.homeautomation.knx.group;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; 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 javax.transaction.Transactional;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.Optional;
@Slf4j @Slf4j
@Service @Service
@Transactional @Transactional(Transactional.TxType.REQUIRES_NEW)
@RequiredArgsConstructor @RequiredArgsConstructor
public class KnxGroupWriteService { public class KnxGroupWriteService {
@ -36,4 +44,41 @@ public class KnxGroupWriteService {
knxGroupRepository.findAllByRead_AbleTrue().forEach(knxGroup -> knxGroup.getRead().setNextTimestamp(ZonedDateTime.now())); 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();
}
}
} }

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View 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<>();
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,5 @@
package de.ph87.homeautomation.schedule.entry;
public enum ScheduleEntryType {
TIME, SUNRISE, SUNSET
}

View 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();
}
}

View 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());
}
}