package de.ph87.homeautomation.knx.group; import de.ph87.homeautomation.property.PropertyType; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import tuwien.auto.calimero.GroupAddress; import tuwien.auto.calimero.IndividualAddress; import tuwien.auto.calimero.KNXException; import tuwien.auto.calimero.KNXFormatException; import tuwien.auto.calimero.dptxlator.*; import javax.annotation.PostConstruct; import java.io.IOException; import java.net.*; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @Slf4j @Service @Transactional(propagation = Propagation.REQUIRES_NEW) @RequiredArgsConstructor public class KnxGroupWriteService { private final KnxGroupRepository knxGroupRepository; private DatagramSocket broadcastDatagramSocket; private InetAddress broadcastAddress; private final int broadcastPort = 1987; @PostConstruct public void postConstruct() throws SocketException, UnknownHostException { broadcastDatagramSocket = new DatagramSocket(); broadcastDatagramSocket.setBroadcast(true); broadcastAddress = InetAddress.getByName("255.255.255.255"); } public void updateIfExists(final int rawAddress, final byte[] data, final IndividualAddress knxDeviceAddress) { final Optional knxGroupOptional = knxGroupRepository.findByAddressRaw(rawAddress); if (knxGroupOptional.isEmpty()) { log.debug("No KnxGroup with address={}", new GroupAddress(rawAddress)); } knxGroupOptional.ifPresent(knxGroup -> { knxGroup.setValue(data); knxGroup.setLastDeviceAddressRaw(knxDeviceAddress.getRawAddress()); knxGroup.setLastDeviceAddressString(knxDeviceAddress.toString()); knxGroup.setValueTimestamp(ZonedDateTime.now()); knxGroup.setBooleanValue(null); knxGroup.setNumberValue(null); try { final DPTXlator translator = findTranslator(knxGroup); translator.setData(data); translate(DPTXlatorBoolean.class, translator, value -> setBooleanValue(knxGroup, value), DPTXlatorBoolean::getValueBoolean); translate(DPTXlator8BitUnsigned.class, translator, knxGroup::setNumberValue, DPTXlator8BitUnsigned::getNumericValue); translate(DPTXlator2ByteFloat.class, translator, knxGroup::setNumberValue, DPTXlator2ByteFloat::getNumericValue); translate(DPTXlator2ByteUnsigned.class, translator, knxGroup::setNumberValue, DPTXlator2ByteUnsigned::getNumericValue); translate(DPTXlator4ByteFloat.class, translator, knxGroup::setNumberValue, DPTXlator4ByteFloat::getNumericValue); translate(DPTXlator4ByteSigned.class, translator, knxGroup::setNumberValue, DPTXlator4ByteSigned::getNumericValue); translate(DPTXlator4ByteUnsigned.class, translator, knxGroup::setNumberValue, DPTXlator4ByteUnsigned::getNumericValue); translate(DPTXlator8BitSigned.class, translator, knxGroup::setNumberValue, DPTXlator8BitSigned::getNumericValue); translate(DPTXlator64BitSigned.class, translator, knxGroup::setNumberValue, DPTXlator64BitSigned::getNumericValue); translate(DPTXlatorSceneNumber.class, translator, knxGroup::setNumberValue, DPTXlatorSceneNumber::getNumericValue); // TODO implement all DPTXlator... broadcast(knxGroup); } catch (NoTranslatorException e) { log.error(e.getMessage()); } log.debug("KnxGroup updated: {}", knxGroup); }); } private void setBooleanValue(final KnxGroup knxGroup, final Boolean value) { knxGroup.setBooleanValue(value); if (value == null) { knxGroup.setNumberValue(null); } else { knxGroup.setNumberValue(value ? 1.0 : 0.0); } } private void broadcast(final KnxGroup knxGroup) { if (knxGroup.getNumberValue() != null && !knxGroup.getNumberValue().isNaN()) { final String message = knxGroup.getPropertyName() + (knxGroup.isMultiGroup() ? ".from." + knxGroup.getLastDeviceAddressString() : "") + " " + knxGroup.getNumberValue(); try { log.debug("UDP Broadcast {}:{}: {}", broadcastAddress, broadcastPort, message); final byte[] bytes = message.getBytes(StandardCharsets.UTF_8); broadcastDatagramSocket.send(new DatagramPacket(bytes, bytes.length, broadcastAddress, broadcastPort)); } catch (IOException e) { log.error("Failed to broadcast property change of {}: {}", knxGroup, e.getMessage()); } } } private void translate(final Class dptXlatorClass, final DPTXlator translator, final Consumer setter, final Function getter) { if (dptXlatorClass.isInstance(translator)) { setter.accept(getter.apply(dptXlatorClass.cast(translator))); } } public void markAllForRead() { knxGroupRepository.findAllByRead_AbleTrue().forEach(knxGroup -> knxGroup.getRead().setNextTimestamp(ZonedDateTime.now())); } public KnxGroupDto create(final String name, final GroupAddress address, final String dpt, final PropertyType type, final boolean readable, final boolean multiGroup) { final KnxGroup trans = new KnxGroup(); trans.setAddress(address); trans.setDpt(dpt); trans.setMultiGroup(multiGroup); trans.setTitle(name); trans.setPropertyType(type); trans.getRead().setAble(readable); return new KnxGroupDto(knxGroupRepository.save(trans)); } public boolean setSendValue(final GroupAddress groupAddress, final double value) throws KnxGroupFormatException { final Optional knxGroupOptional = knxGroupRepository.findByAddressRaw(groupAddress.getRawAddress()); if (knxGroupOptional.isEmpty()) { return false; } final KnxGroup knxGroup = knxGroupOptional.get(); try { final DPTXlator translator = findTranslator(knxGroup); if (translator instanceof DPTXlatorBoolean) { ((DPTXlatorBoolean) translator).setValue(value == 1.0); } else if (translator instanceof DPTXlator8BitUnsigned) { ((DPTXlator8BitUnsigned) translator).setValue((int) value); } else { // TODO implement all DPTXlator... translator.setValue("" + value); } knxGroup.setSendValue(translator.getData()); knxGroup.getSend().setNextTimestamp(ZonedDateTime.now()); return true; } catch (NoTranslatorException | KNXFormatException e) { throw new KnxGroupFormatException(knxGroup, value, e.getMessage()); } } private DPTXlator findTranslator(final KnxGroup knxGroup) throws NoTranslatorException { if (knxGroup.getDpt() == null) { throw new NoTranslatorException("Missing DPT"); } final int mainNumber = Integer.parseInt(knxGroup.getDpt().split("\\.")[0]); try { return TranslatorTypes.createTranslator(mainNumber, knxGroup.getDpt()); } catch (KNXException e) { throw new NoTranslatorException(e); } } }