package de.ph87.home.knx.property; import de.ph87.home.knx.Group; import de.ph87.home.knx.GroupLoaded; import de.ph87.home.knx.GroupService; import de.ph87.home.knx.link.KnxLinkService; import de.ph87.home.property.PropertyNotFound; import de.ph87.home.property.PropertyNotOwned; import de.ph87.home.property.PropertyService; import de.ph87.home.property.PropertyTypeMismatch; import jakarta.annotation.Nullable; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tuwien.auto.calimero.GroupAddress; import tuwien.auto.calimero.KNXException; import tuwien.auto.calimero.dptxlator.DPTXlator; import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean; import tuwien.auto.calimero.process.ProcessEvent; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @Slf4j @Service @Transactional @EnableScheduling @RequiredArgsConstructor public class KnxPropertyService { private final KnxPropertyRepository knxPropertyRepository; private final PropertyService propertyService; private final GroupService groupService; private final KnxLinkService knxLinkService; @Scheduled(initialDelay = 1, fixedDelay = 1, timeUnit = TimeUnit.HOURS) public void readAll() { knxPropertyRepository.findAll().forEach(this::read); } @EventListener(GroupLoaded.class) public void onGroupLoad(@NonNull final GroupLoaded groupLoaded) { findAllByAddress(groupLoaded.getGroup().getAddress()).forEach(this::read); } public void create(@NonNull final String id, @NonNull final KnxPropertyType type, @Nullable final GroupAddress read, @Nullable final GroupAddress write) { final KnxProperty knxProperty = knxPropertyRepository.save(new KnxProperty(id, type, read, write)); updateOrCreateProperty(knxProperty); } private void updateOrCreateProperty(@NonNull final KnxProperty knxProperty) { try { propertyService.createOrUpdate(this, knxProperty.getId(), knxProperty.getType().clazz, (p, value) -> write(knxProperty, value)); read(knxProperty); } catch (PropertyNotOwned e) { log.error("Failed to register KnxProperty: knxProperty={}, error={}", knxProperty, e.toString()); } } @EventListener(ProcessEvent.class) public void onProcessEvent(@NonNull final ProcessEvent event) { findAllByAddress(event.getDestination()).forEach(knxProperty -> onProcessEvent(knxProperty, event)); } @NonNull private List findAllByAddress(@NonNull final GroupAddress address) { return knxPropertyRepository.findDistinctByReadOrWrite(address, address); } private void onProcessEvent(@NonNull final KnxProperty knxProperty, @NonNull final ProcessEvent event) { log.debug("onProcessEvent: knxProperty={}, event={}", knxProperty, event); groupService.findByAddress(event.getDestination()).ifPresent(group -> onProcessEvent(knxProperty, event, group)); } private void onProcessEvent(@NonNull final KnxProperty knxProperty, @NonNull final ProcessEvent event, @NonNull final Group group) { log.debug("onProcessEvent: knxProperty={}, group={}, event={}", knxProperty, group, event); try { final DPTXlator translator = group.getDpt().createTranslator(); translator.setData(event.getASDU()); log.debug("translator: {}", translator); switch (knxProperty.getType()) { case BOOLEAN -> { if (!(translator instanceof final DPTXlatorBoolean booleanTranslator)) { throw new RuntimeException("DPTXlator type should be DPTXlatorBoolean for property.type = BOOLEAN but is: " + translator.getClass().getSimpleName()); } propertyService.update(this, knxProperty.getId(), Boolean.class, booleanTranslator.getValueBoolean(), booleanTranslator.getValue()); } case DOUBLE -> propertyService.update(this, knxProperty.getId(), Double.class, translator.getNumericValue(), translator.getValue()); } } catch (KNXException | PropertyNotFound | PropertyTypeMismatch | PropertyNotOwned e) { log.error("Failed to handle ProcessEvent: knxProperty={}, error={}", knxProperty, e.toString()); } } private void read(@NonNull final KnxProperty knxProperty) { final Optional property = knxPropertyRepository.findById(knxProperty.getId()); final Optional address = property.map(KnxProperty::getRead); final Optional group = address.map(groupService::findByAddress).filter(Optional::isPresent).map(Optional::get); group.ifPresent(knxLinkService::queueRead); } private void write(@NonNull final KnxProperty knxProperty, @NonNull final Object value) { knxPropertyRepository.findById(knxProperty.getId()).map(KnxProperty::getWrite).map(groupService::findByAddress).filter(Optional::isPresent).map(Optional::get).ifPresent(group -> write(knxProperty, group, value)); } private void write(@NonNull final KnxProperty knxProperty, @NonNull final Group group, @NonNull final Object value) { try { if (!knxProperty.getType().clazz.isInstance(value)) { throw new RuntimeException("Cannot write invalid value type: type=%s, value=%s, knxProperty=%s".formatted(value.getClass(), value, knxProperty)); } final DPTXlator translator = group.getDpt().createTranslator(); switch (knxProperty.getType()) { case BOOLEAN -> { if (!(translator instanceof final DPTXlatorBoolean booleanTranslator)) { throw new RuntimeException("DPTXlator type should be DPTXlatorBoolean for property.type = BOOLEAN but is: " + translator.getClass().getSimpleName()); } booleanTranslator.setValue((boolean) value); } case DOUBLE -> translator.setValue((double) value); } knxLinkService.queueWrite(group, translator); } catch (KNXException e) { log.error("Failed to write KnxProperty: knxProperty={}, error={}", knxProperty, e.toString()); } } }