diff --git a/src/main/java/de/ph87/homeautomation/channel/ChannelService.java b/src/main/java/de/ph87/homeautomation/channel/ChannelService.java index b7eff5e..a3dbeb9 100644 --- a/src/main/java/de/ph87/homeautomation/channel/ChannelService.java +++ b/src/main/java/de/ph87/homeautomation/channel/ChannelService.java @@ -1,8 +1,11 @@ package de.ph87.homeautomation.channel; import de.ph87.homeautomation.property.Property; +import de.ph87.homeautomation.property.PropertyReadService; 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 org.springframework.transaction.annotation.Transactional; @@ -17,6 +20,20 @@ public class ChannelService { private final List channelOwners; + private final PropertyReadService propertyReadService; + + @EventListener(ApplicationStartedEvent.class) + public void readAllPropertyChannels() { + propertyReadService.findAllByReadChannelNotNull().forEach(property -> { + final Optional ownerOptional = findByChannel(property.getReadChannel()); + if (ownerOptional.isPresent()) { + ownerOptional.get().read(property.getReadChannel()); + } else { + log.error("No Owner for Property: {}", property); + } + }); + } + public Optional findByChannel(final Channel channel) { return channelOwners.stream().filter(owner -> channel.getChannelOwnerClass().isInstance(owner)).findFirst(); } @@ -30,7 +47,7 @@ public class ChannelService { if (channel == null) { return; } - getByChannel(channel).write(property, value); + getByChannel(channel).write(property.getWriteChannel(), value); } } diff --git a/src/main/java/de/ph87/homeautomation/channel/IChannelOwner.java b/src/main/java/de/ph87/homeautomation/channel/IChannelOwner.java index d948d42..7c5a8db 100644 --- a/src/main/java/de/ph87/homeautomation/channel/IChannelOwner.java +++ b/src/main/java/de/ph87/homeautomation/channel/IChannelOwner.java @@ -1,9 +1,9 @@ package de.ph87.homeautomation.channel; -import de.ph87.homeautomation.property.Property; - public interface IChannelOwner { - void write(final Property property, final double value); + void read(final Channel channel); + + void write(final Channel channel, final double value); } diff --git a/src/main/java/de/ph87/homeautomation/knx/KnxThreadService.java b/src/main/java/de/ph87/homeautomation/knx/KnxThreadService.java index e5ca4e0..b42f432 100644 --- a/src/main/java/de/ph87/homeautomation/knx/KnxThreadService.java +++ b/src/main/java/de/ph87/homeautomation/knx/KnxThreadService.java @@ -31,6 +31,8 @@ import java.time.ZonedDateTime; @RequiredArgsConstructor public class KnxThreadService extends AbstractThreadService implements NetworkLinkListener, ProcessListener { + public static final Duration RESPONSE_TIMEOUT = Duration.ofMillis(500); + private final KnxGroupWriteService knxGroupWriteService; private final KnxGroupLinkService knxGroupLinkService; @@ -89,10 +91,12 @@ public class KnxThreadService extends AbstractThreadService implements NetworkLi private void connect() throws KNXException, InterruptedException, IOException { final Inet4Address localAddress = Router.getLocalInet4AddressForRemoteAddress(remoteAddress); log.debug("Connecting KNX link: {} -> {}", localAddress, remoteAddress); - link = KNXNetworkLinkIP.newTunnelingLink(new InetSocketAddress(localAddress, 0), new InetSocketAddress(remoteAddress, 3671), false, new TPSettings()); + final TPSettings settings = new TPSettings(); + link = KNXNetworkLinkIP.newTunnelingLink(new InetSocketAddress(localAddress, 0), new InetSocketAddress(remoteAddress, 3671), false, settings); link.addLinkListener(this); processCommunicator = new ProcessCommunicatorImpl(link); processCommunicator.addProcessListener(this); + processCommunicator.responseTimeout(RESPONSE_TIMEOUT); log.info("KNX link established."); } diff --git a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupChannelOwnerService.java b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupChannelOwnerService.java index dacdd1b..b23cc8d 100644 --- a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupChannelOwnerService.java +++ b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupChannelOwnerService.java @@ -1,7 +1,7 @@ package de.ph87.homeautomation.knx.group; +import de.ph87.homeautomation.channel.Channel; import de.ph87.homeautomation.channel.IChannelOwner; -import de.ph87.homeautomation.property.Property; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -16,12 +16,21 @@ public class KnxGroupChannelOwnerService implements IChannelOwner { private final KnxGroupWriteService knxGroupWriteService; @Override - public void write(final Property property, final double value) { - if (!(property.getWriteChannel() instanceof KnxGroup)) { + public void read(final Channel channel) { + if (!(channel instanceof KnxGroup)) { throw new RuntimeException(); } - final KnxGroup knxGroup = (KnxGroup) property.getWriteChannel(); - knxGroupWriteService.setSendValue(knxGroup, value); + final KnxGroup knxGroup = (KnxGroup) channel; + knxGroupWriteService.requestRead(knxGroup); + } + + @Override + public void write(final Channel channel, final double value) { + if (!(channel instanceof KnxGroup)) { + throw new RuntimeException(); + } + final KnxGroup knxGroup = (KnxGroup) channel; + knxGroupWriteService.requestWrite(knxGroup, value); } } diff --git a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupLinkInfo.java b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupLinkInfo.java index 11d591d..e410107 100644 --- a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupLinkInfo.java +++ b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupLinkInfo.java @@ -3,18 +3,18 @@ package de.ph87.homeautomation.knx.group; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import lombok.extern.slf4j.Slf4j; import javax.persistence.Embeddable; import java.time.ZonedDateTime; +@Slf4j @Getter @Setter @ToString @Embeddable public class KnxGroupLinkInfo { - private boolean able = true; - private ZonedDateTime nextTimestamp = null; private int errorCount = 0; diff --git a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupLinkService.java b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupLinkService.java index 2cccca2..5e9d1fd 100644 --- a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupLinkService.java +++ b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupLinkService.java @@ -7,12 +7,11 @@ import org.springframework.transaction.annotation.Transactional; import tuwien.auto.calimero.GroupAddress; import tuwien.auto.calimero.KNXException; import tuwien.auto.calimero.KNXFormatException; +import tuwien.auto.calimero.KNXTimeoutException; import tuwien.auto.calimero.datapoint.StateDP; import tuwien.auto.calimero.dptxlator.TranslatorTypes; import tuwien.auto.calimero.process.ProcessCommunicatorImpl; -import java.time.Instant; -import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Optional; @@ -22,38 +21,43 @@ import java.util.Optional; @RequiredArgsConstructor public class KnxGroupLinkService { + public static final long ERROR_DELAY_FACTOR = 500 * 1000 * 1000L; + private final KnxGroupRepository knxGroupRepository; public boolean sendNext(final ProcessCommunicatorImpl processCommunicator) throws KNXException { - final Optional knxGroupOptional = knxGroupRepository.findFirstBySend_NextTimestampNotNullOrderBySend_NextTimestampAsc(); - if (knxGroupOptional.isEmpty()) { + final Optional nextOptional = knxGroupRepository.findFirstBySend_NextTimestampNotNullOrderBySend_NextTimestampAsc(); + if (nextOptional.isEmpty()) { return false; } - return send(processCommunicator, knxGroupOptional.get()); + return send(processCommunicator, nextOptional.get()); } public boolean readNext(final ProcessCommunicatorImpl processCommunicator) throws KNXException, InterruptedException { - final Optional knxGroupOptional = knxGroupRepository.findFirstByRead_NextTimestampLessThanEqualOrderByRead_NextTimestampAsc(ZonedDateTime.now()); - if (knxGroupOptional.isEmpty()) { + final Optional nextOptional = knxGroupRepository.findFirstByRead_NextTimestampLessThanEqualOrderByRead_NextTimestampAsc(ZonedDateTime.now()); + if (nextOptional.isEmpty()) { return false; } - return read(processCommunicator, knxGroupOptional.get()); + return read(processCommunicator, nextOptional.get()); } private boolean send(final ProcessCommunicatorImpl processCommunicator, final KnxGroup knxGroup) throws KNXException { try { log.debug("Sending KnxGroup: {}", knxGroup); processCommunicator.write(knxGroup.getAddress(), TranslatorTypes.createTranslator(knxGroup.getDptMain(), knxGroup.getDptSub(), knxGroup.getSendValue())); + if (knxGroup.getSend().getErrorCount() > 0) { + log.info("KnxGroup recovered from send-error: {}", knxGroup); + } knxGroup.getSend().setErrorCount(0); knxGroup.getSend().setErrorMessage(null); knxGroup.getSend().setNextTimestamp(null); - log.info("Successfully sent KnxGroup: {}", knxGroup); + log.debug("Successfully sent KnxGroup: {}", knxGroup); return true; - } catch (KNXFormatException e) { - log.error(e.toString()); + } catch (KNXTimeoutException | KNXFormatException e) { + log.error("Failed to send KnxGroup {}", knxGroup); knxGroup.getSend().setErrorCount(knxGroup.getSend().getErrorCount() + 1); - knxGroup.getSend().setErrorMessage(e.toString()); - knxGroup.getSend().setNextTimestamp(ZonedDateTime.now().plusSeconds(knxGroup.getSend().getErrorCount())); + knxGroup.getSend().setErrorMessage(e.getMessage()); + knxGroup.getSend().setNextTimestamp(ZonedDateTime.now().plusNanos(knxGroup.getSend().getErrorCount() * knxGroup.getSend().getErrorCount() * ERROR_DELAY_FACTOR)); } return false; } @@ -62,26 +66,23 @@ public class KnxGroupLinkService { try { log.debug("Reading KnxGroup: {}", knxGroup); processCommunicator.read(createStateDP(knxGroup)); + if (knxGroup.getRead().getErrorCount() > 0) { + log.info("KnxGroup recovered from read-error: {}", knxGroup); + } knxGroup.getRead().setErrorCount(0); knxGroup.getRead().setErrorMessage(null); knxGroup.getRead().setNextTimestamp(null); + log.debug("Successfully sent KnxGroup: {}", knxGroup); return true; - } catch (KNXFormatException e) { - log.error(e.toString()); + } catch (KNXTimeoutException | KNXFormatException e) { + log.error("Failed to read KnxGroup {}", knxGroup); knxGroup.getRead().setErrorCount(knxGroup.getRead().getErrorCount() + 1); - knxGroup.getRead().setErrorMessage(e.toString()); - knxGroup.getRead().setNextTimestamp(ZonedDateTime.now().plusSeconds(knxGroup.getRead().getErrorCount())); + knxGroup.getRead().setErrorMessage(e.getMessage()); + knxGroup.getRead().setNextTimestamp(ZonedDateTime.now().plusNanos(knxGroup.getRead().getErrorCount() * knxGroup.getRead().getErrorCount() * ERROR_DELAY_FACTOR)); } return false; } - private ZonedDateTime align(final int interval) { - final ZonedDateTime now = ZonedDateTime.now(); - final long nextEpochAlignment = (long) Math.ceil(now.toEpochSecond() / (double) interval) * interval; - final ZonedDateTime nextUTC = ZonedDateTime.ofInstant(Instant.ofEpochSecond(nextEpochAlignment, 0), ZoneId.of("Z")); - return nextUTC.withZoneSameInstant(now.getZone()); - } - private StateDP createStateDP(final KnxGroup knxGroup) { final GroupAddress groupAddress = knxGroup.getAddress(); return new StateDP(groupAddress, groupAddress.toString(), knxGroup.getDptMain(), knxGroup.getDpt()); diff --git a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupWriteService.java b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupWriteService.java index aa7644f..f454697 100644 --- a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupWriteService.java +++ b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupWriteService.java @@ -26,7 +26,13 @@ public class KnxGroupWriteService { private final ApplicationEventPublisher applicationEventPublisher; - public void setSendValue(final KnxGroup knxGroup, final double value) { + public void requestRead(final KnxGroup knxGroup) { + knxGroup.getRead().setNextTimestamp(ZonedDateTime.now()); + log.debug("Requesting read for KnxGroup: {}", knxGroup); + sendThreadWakeUpEvent(); + } + + public void requestWrite(final KnxGroup knxGroup, final double value) { findTranslator(knxGroup).ifPresent(translator -> { try { if (translator instanceof DPTXlatorBoolean) { @@ -38,7 +44,7 @@ public class KnxGroupWriteService { } knxGroup.getSend().setNextTimestamp(ZonedDateTime.now()); knxGroup.setSendValue(translator.getData()); - applicationEventPublisher.publishEvent(new KnxThreadWakeUpEvent()); + sendThreadWakeUpEvent(); } catch (KNXFormatException e) { log.error("Failed set value \"{}\" to DptXlator {} for KnxGroup {}", value, translator, knxGroup); } @@ -56,6 +62,10 @@ public class KnxGroupWriteService { translate(knxGroup); } + private void sendThreadWakeUpEvent() { + applicationEventPublisher.publishEvent(new KnxThreadWakeUpEvent()); + } + private void translate(final KnxGroup knxGroup) { findTranslator(knxGroup).ifPresent(translator -> { translator.setData(knxGroup.getLastTelegram().getData()); diff --git a/src/main/java/de/ph87/homeautomation/property/PropertyReadService.java b/src/main/java/de/ph87/homeautomation/property/PropertyReadService.java index 329aaf2..fab98b9 100644 --- a/src/main/java/de/ph87/homeautomation/property/PropertyReadService.java +++ b/src/main/java/de/ph87/homeautomation/property/PropertyReadService.java @@ -42,4 +42,8 @@ public class PropertyReadService { return propertyRepository.findAll().stream().map(propertyMapper::toDto).collect(Collectors.toList()); } + public List findAllByReadChannelNotNull() { + return propertyRepository.findAllByReadChannelNotNull(); + } + } diff --git a/src/main/java/de/ph87/homeautomation/property/PropertyRepository.java b/src/main/java/de/ph87/homeautomation/property/PropertyRepository.java index 395982f..9c45daa 100644 --- a/src/main/java/de/ph87/homeautomation/property/PropertyRepository.java +++ b/src/main/java/de/ph87/homeautomation/property/PropertyRepository.java @@ -17,4 +17,6 @@ public interface PropertyRepository extends CrudRepository { boolean existsByName(String name); + List findAllByReadChannelNotNull(); + } diff --git a/src/main/java/de/ph87/homeautomation/property/PropertySetDto.java b/src/main/java/de/ph87/homeautomation/property/PropertySetDto.java deleted file mode 100644 index c82d90f..0000000 --- a/src/main/java/de/ph87/homeautomation/property/PropertySetDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.ph87.homeautomation.property; - -import lombok.Data; - -@Data -public class PropertySetDto { - - private final String name; - - private final double value; - -}