Reading Property-Channels at startup

This commit is contained in:
Patrick Haßel 2021-11-08 14:03:00 +01:00
parent b3d5b3cdd2
commit 3a5cfad535
10 changed files with 85 additions and 50 deletions

View File

@ -1,8 +1,11 @@
package de.ph87.homeautomation.channel; package de.ph87.homeautomation.channel;
import de.ph87.homeautomation.property.Property; import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.property.PropertyReadService;
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.context.event.EventListener;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -17,6 +20,20 @@ public class ChannelService {
private final List<IChannelOwner> channelOwners; private final List<IChannelOwner> channelOwners;
private final PropertyReadService propertyReadService;
@EventListener(ApplicationStartedEvent.class)
public void readAllPropertyChannels() {
propertyReadService.findAllByReadChannelNotNull().forEach(property -> {
final Optional<IChannelOwner> ownerOptional = findByChannel(property.getReadChannel());
if (ownerOptional.isPresent()) {
ownerOptional.get().read(property.getReadChannel());
} else {
log.error("No Owner for Property: {}", property);
}
});
}
public Optional<IChannelOwner> findByChannel(final Channel channel) { public Optional<IChannelOwner> findByChannel(final Channel channel) {
return channelOwners.stream().filter(owner -> channel.getChannelOwnerClass().isInstance(owner)).findFirst(); return channelOwners.stream().filter(owner -> channel.getChannelOwnerClass().isInstance(owner)).findFirst();
} }
@ -30,7 +47,7 @@ public class ChannelService {
if (channel == null) { if (channel == null) {
return; return;
} }
getByChannel(channel).write(property, value); getByChannel(channel).write(property.getWriteChannel(), value);
} }
} }

View File

@ -1,9 +1,9 @@
package de.ph87.homeautomation.channel; package de.ph87.homeautomation.channel;
import de.ph87.homeautomation.property.Property;
public interface IChannelOwner { public interface IChannelOwner {
void write(final Property property, final double value); void read(final Channel channel);
void write(final Channel channel, final double value);
} }

View File

@ -31,6 +31,8 @@ import java.time.ZonedDateTime;
@RequiredArgsConstructor @RequiredArgsConstructor
public class KnxThreadService extends AbstractThreadService implements NetworkLinkListener, ProcessListener { public class KnxThreadService extends AbstractThreadService implements NetworkLinkListener, ProcessListener {
public static final Duration RESPONSE_TIMEOUT = Duration.ofMillis(500);
private final KnxGroupWriteService knxGroupWriteService; private final KnxGroupWriteService knxGroupWriteService;
private final KnxGroupLinkService knxGroupLinkService; private final KnxGroupLinkService knxGroupLinkService;
@ -89,10 +91,12 @@ public class KnxThreadService extends AbstractThreadService implements NetworkLi
private void connect() throws KNXException, InterruptedException, IOException { private void connect() throws KNXException, InterruptedException, IOException {
final Inet4Address localAddress = Router.getLocalInet4AddressForRemoteAddress(remoteAddress); final Inet4Address localAddress = Router.getLocalInet4AddressForRemoteAddress(remoteAddress);
log.debug("Connecting KNX link: {} -> {}", localAddress, 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); link.addLinkListener(this);
processCommunicator = new ProcessCommunicatorImpl(link); processCommunicator = new ProcessCommunicatorImpl(link);
processCommunicator.addProcessListener(this); processCommunicator.addProcessListener(this);
processCommunicator.responseTimeout(RESPONSE_TIMEOUT);
log.info("KNX link established."); log.info("KNX link established.");
} }

View File

@ -1,7 +1,7 @@
package de.ph87.homeautomation.knx.group; package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.channel.Channel;
import de.ph87.homeautomation.channel.IChannelOwner; import de.ph87.homeautomation.channel.IChannelOwner;
import de.ph87.homeautomation.property.Property;
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;
@ -16,12 +16,21 @@ public class KnxGroupChannelOwnerService implements IChannelOwner {
private final KnxGroupWriteService knxGroupWriteService; private final KnxGroupWriteService knxGroupWriteService;
@Override @Override
public void write(final Property property, final double value) { public void read(final Channel channel) {
if (!(property.getWriteChannel() instanceof KnxGroup)) { if (!(channel instanceof KnxGroup)) {
throw new RuntimeException(); throw new RuntimeException();
} }
final KnxGroup knxGroup = (KnxGroup) property.getWriteChannel(); final KnxGroup knxGroup = (KnxGroup) channel;
knxGroupWriteService.setSendValue(knxGroup, value); 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);
} }
} }

View File

@ -3,18 +3,18 @@ package de.ph87.homeautomation.knx.group;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@Slf4j
@Getter @Getter
@Setter @Setter
@ToString @ToString
@Embeddable @Embeddable
public class KnxGroupLinkInfo { public class KnxGroupLinkInfo {
private boolean able = true;
private ZonedDateTime nextTimestamp = null; private ZonedDateTime nextTimestamp = null;
private int errorCount = 0; private int errorCount = 0;

View File

@ -7,12 +7,11 @@ import org.springframework.transaction.annotation.Transactional;
import tuwien.auto.calimero.GroupAddress; import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.KNXException; import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.KNXFormatException; import tuwien.auto.calimero.KNXFormatException;
import tuwien.auto.calimero.KNXTimeoutException;
import tuwien.auto.calimero.datapoint.StateDP; import tuwien.auto.calimero.datapoint.StateDP;
import tuwien.auto.calimero.dptxlator.TranslatorTypes; import tuwien.auto.calimero.dptxlator.TranslatorTypes;
import tuwien.auto.calimero.process.ProcessCommunicatorImpl; import tuwien.auto.calimero.process.ProcessCommunicatorImpl;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Optional; import java.util.Optional;
@ -22,38 +21,43 @@ import java.util.Optional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class KnxGroupLinkService { public class KnxGroupLinkService {
public static final long ERROR_DELAY_FACTOR = 500 * 1000 * 1000L;
private final KnxGroupRepository knxGroupRepository; private final KnxGroupRepository knxGroupRepository;
public boolean sendNext(final ProcessCommunicatorImpl processCommunicator) throws KNXException { public boolean sendNext(final ProcessCommunicatorImpl processCommunicator) throws KNXException {
final Optional<KnxGroup> knxGroupOptional = knxGroupRepository.findFirstBySend_NextTimestampNotNullOrderBySend_NextTimestampAsc(); final Optional<KnxGroup> nextOptional = knxGroupRepository.findFirstBySend_NextTimestampNotNullOrderBySend_NextTimestampAsc();
if (knxGroupOptional.isEmpty()) { if (nextOptional.isEmpty()) {
return false; return false;
} }
return send(processCommunicator, knxGroupOptional.get()); return send(processCommunicator, nextOptional.get());
} }
public boolean readNext(final ProcessCommunicatorImpl processCommunicator) throws KNXException, InterruptedException { public boolean readNext(final ProcessCommunicatorImpl processCommunicator) throws KNXException, InterruptedException {
final Optional<KnxGroup> knxGroupOptional = knxGroupRepository.findFirstByRead_NextTimestampLessThanEqualOrderByRead_NextTimestampAsc(ZonedDateTime.now()); final Optional<KnxGroup> nextOptional = knxGroupRepository.findFirstByRead_NextTimestampLessThanEqualOrderByRead_NextTimestampAsc(ZonedDateTime.now());
if (knxGroupOptional.isEmpty()) { if (nextOptional.isEmpty()) {
return false; return false;
} }
return read(processCommunicator, knxGroupOptional.get()); return read(processCommunicator, nextOptional.get());
} }
private boolean send(final ProcessCommunicatorImpl processCommunicator, final KnxGroup knxGroup) throws KNXException { private boolean send(final ProcessCommunicatorImpl processCommunicator, final KnxGroup knxGroup) throws KNXException {
try { try {
log.debug("Sending KnxGroup: {}", knxGroup); log.debug("Sending KnxGroup: {}", knxGroup);
processCommunicator.write(knxGroup.getAddress(), TranslatorTypes.createTranslator(knxGroup.getDptMain(), knxGroup.getDptSub(), knxGroup.getSendValue())); 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().setErrorCount(0);
knxGroup.getSend().setErrorMessage(null); knxGroup.getSend().setErrorMessage(null);
knxGroup.getSend().setNextTimestamp(null); knxGroup.getSend().setNextTimestamp(null);
log.info("Successfully sent KnxGroup: {}", knxGroup); log.debug("Successfully sent KnxGroup: {}", knxGroup);
return true; return true;
} catch (KNXFormatException e) { } catch (KNXTimeoutException | KNXFormatException e) {
log.error(e.toString()); log.error("Failed to send KnxGroup {}", knxGroup);
knxGroup.getSend().setErrorCount(knxGroup.getSend().getErrorCount() + 1); knxGroup.getSend().setErrorCount(knxGroup.getSend().getErrorCount() + 1);
knxGroup.getSend().setErrorMessage(e.toString()); knxGroup.getSend().setErrorMessage(e.getMessage());
knxGroup.getSend().setNextTimestamp(ZonedDateTime.now().plusSeconds(knxGroup.getSend().getErrorCount())); knxGroup.getSend().setNextTimestamp(ZonedDateTime.now().plusNanos(knxGroup.getSend().getErrorCount() * knxGroup.getSend().getErrorCount() * ERROR_DELAY_FACTOR));
} }
return false; return false;
} }
@ -62,26 +66,23 @@ public class KnxGroupLinkService {
try { try {
log.debug("Reading KnxGroup: {}", knxGroup); log.debug("Reading KnxGroup: {}", knxGroup);
processCommunicator.read(createStateDP(knxGroup)); processCommunicator.read(createStateDP(knxGroup));
if (knxGroup.getRead().getErrorCount() > 0) {
log.info("KnxGroup recovered from read-error: {}", knxGroup);
}
knxGroup.getRead().setErrorCount(0); knxGroup.getRead().setErrorCount(0);
knxGroup.getRead().setErrorMessage(null); knxGroup.getRead().setErrorMessage(null);
knxGroup.getRead().setNextTimestamp(null); knxGroup.getRead().setNextTimestamp(null);
log.debug("Successfully sent KnxGroup: {}", knxGroup);
return true; return true;
} catch (KNXFormatException e) { } catch (KNXTimeoutException | KNXFormatException e) {
log.error(e.toString()); log.error("Failed to read KnxGroup {}", knxGroup);
knxGroup.getRead().setErrorCount(knxGroup.getRead().getErrorCount() + 1); knxGroup.getRead().setErrorCount(knxGroup.getRead().getErrorCount() + 1);
knxGroup.getRead().setErrorMessage(e.toString()); knxGroup.getRead().setErrorMessage(e.getMessage());
knxGroup.getRead().setNextTimestamp(ZonedDateTime.now().plusSeconds(knxGroup.getRead().getErrorCount())); knxGroup.getRead().setNextTimestamp(ZonedDateTime.now().plusNanos(knxGroup.getRead().getErrorCount() * knxGroup.getRead().getErrorCount() * ERROR_DELAY_FACTOR));
} }
return false; 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) { private StateDP createStateDP(final KnxGroup knxGroup) {
final GroupAddress groupAddress = knxGroup.getAddress(); final GroupAddress groupAddress = knxGroup.getAddress();
return new StateDP(groupAddress, groupAddress.toString(), knxGroup.getDptMain(), knxGroup.getDpt()); return new StateDP(groupAddress, groupAddress.toString(), knxGroup.getDptMain(), knxGroup.getDpt());

View File

@ -26,7 +26,13 @@ public class KnxGroupWriteService {
private final ApplicationEventPublisher applicationEventPublisher; 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 -> { findTranslator(knxGroup).ifPresent(translator -> {
try { try {
if (translator instanceof DPTXlatorBoolean) { if (translator instanceof DPTXlatorBoolean) {
@ -38,7 +44,7 @@ public class KnxGroupWriteService {
} }
knxGroup.getSend().setNextTimestamp(ZonedDateTime.now()); knxGroup.getSend().setNextTimestamp(ZonedDateTime.now());
knxGroup.setSendValue(translator.getData()); knxGroup.setSendValue(translator.getData());
applicationEventPublisher.publishEvent(new KnxThreadWakeUpEvent()); sendThreadWakeUpEvent();
} catch (KNXFormatException e) { } catch (KNXFormatException e) {
log.error("Failed set value \"{}\" to DptXlator {} for KnxGroup {}", value, translator, knxGroup); log.error("Failed set value \"{}\" to DptXlator {} for KnxGroup {}", value, translator, knxGroup);
} }
@ -56,6 +62,10 @@ public class KnxGroupWriteService {
translate(knxGroup); translate(knxGroup);
} }
private void sendThreadWakeUpEvent() {
applicationEventPublisher.publishEvent(new KnxThreadWakeUpEvent());
}
private void translate(final KnxGroup knxGroup) { private void translate(final KnxGroup knxGroup) {
findTranslator(knxGroup).ifPresent(translator -> { findTranslator(knxGroup).ifPresent(translator -> {
translator.setData(knxGroup.getLastTelegram().getData()); translator.setData(knxGroup.getLastTelegram().getData());

View File

@ -42,4 +42,8 @@ public class PropertyReadService {
return propertyRepository.findAll().stream().map(propertyMapper::toDto).collect(Collectors.toList()); return propertyRepository.findAll().stream().map(propertyMapper::toDto).collect(Collectors.toList());
} }
public List<Property> findAllByReadChannelNotNull() {
return propertyRepository.findAllByReadChannelNotNull();
}
} }

View File

@ -17,4 +17,6 @@ public interface PropertyRepository extends CrudRepository<Property, Long> {
boolean existsByName(String name); boolean existsByName(String name);
List<Property> findAllByReadChannelNotNull();
} }

View File

@ -1,12 +0,0 @@
package de.ph87.homeautomation.property;
import lombok.Data;
@Data
public class PropertySetDto {
private final String name;
private final double value;
}