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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<KnxGroup> knxGroupOptional = knxGroupRepository.findFirstBySend_NextTimestampNotNullOrderBySend_NextTimestampAsc();
if (knxGroupOptional.isEmpty()) {
final Optional<KnxGroup> 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<KnxGroup> knxGroupOptional = knxGroupRepository.findFirstByRead_NextTimestampLessThanEqualOrderByRead_NextTimestampAsc(ZonedDateTime.now());
if (knxGroupOptional.isEmpty()) {
final Optional<KnxGroup> 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());

View File

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

View File

@ -42,4 +42,8 @@ public class PropertyReadService {
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);
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;
}