implemented Device* + WebConfig

This commit is contained in:
Patrick Haßel 2021-10-01 16:23:57 +02:00
parent 82313a1932
commit d073ca7070
22 changed files with 440 additions and 30 deletions

View File

@ -0,0 +1,24 @@
package de.ph87.homeautomation.device;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.*;
@Getter
@Setter
@ToString
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Device {
@Id
@GeneratedValue
@Setter(AccessLevel.NONE)
private Long id;
private String name;
}

View File

@ -0,0 +1,22 @@
package de.ph87.homeautomation.device;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("device")
@RequiredArgsConstructor
public class DeviceController {
private final DeviceReadService deviceReadService;
@GetMapping("findAll")
public List<DeviceDto> findAll() {
return deviceReadService.findAll();
}
}

View File

@ -0,0 +1,17 @@
package de.ph87.homeautomation.device;
public abstract class DeviceDto {
public final long id;
public final String name;
public final String type;
public DeviceDto(final Device device) {
this.id = device.getId();
this.name = device.getName();
this.type = device.getClass().getSimpleName();
}
}

View File

@ -0,0 +1,34 @@
package de.ph87.homeautomation.device;
import de.ph87.homeautomation.property.PropertyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class DeviceReadService {
private final DeviceRepository deviceRepository;
private final PropertyService propertyService;
public List<DeviceDto> findAll() {
return deviceRepository.findAll().stream().map(this::toDto).collect(Collectors.toList());
}
private DeviceDto toDto(final Device device) {
if (device instanceof DeviceSwitch) {
final DeviceSwitch deviceSwitch = (DeviceSwitch) device;
return new DeviceSwitchDto(deviceSwitch, propertyService.readBoolean(deviceSwitch.getStatePropertyName()));
}
throw new RuntimeException("Not imeplemented: toDto(" + device + ")");
}
}

View File

@ -0,0 +1,11 @@
package de.ph87.homeautomation.device;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface DeviceRepository extends CrudRepository<Device, Long> {
List<Device> findAll();
}

View File

@ -0,0 +1,19 @@
package de.ph87.homeautomation.device;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
@Getter
@Setter
@ToString
@Entity
public class DeviceSwitch extends Device {
private String switchPropertyName;
private String statePropertyName;
}

View File

@ -0,0 +1,12 @@
package de.ph87.homeautomation.device;
public class DeviceSwitchDto extends DeviceDto {
public final Boolean state;
public DeviceSwitchDto(final DeviceSwitch device, final Boolean state) {
super(device);
this.state = state;
}
}

View File

@ -0,0 +1,29 @@
package de.ph87.homeautomation.device;
import de.ph87.homeautomation.property.PropertyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.transaction.Transactional;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class DeviceWriteService {
private final DeviceRepository deviceRepository;
private final PropertyService propertyService;
@PostConstruct
public void postConstruct() {
final DeviceSwitch deviceSwitch = new DeviceSwitch();
deviceSwitch.setName("TEST");
deviceSwitch.setStatePropertyName("knx.group.0/3/6");
deviceRepository.save(deviceSwitch);
}
}

View File

@ -7,6 +7,7 @@ import lombok.ToString;
import tuwien.auto.calimero.GroupAddress;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.ZonedDateTime;
@ -29,8 +30,14 @@ public class KnxGroup {
private String dpt;
private String name;
private byte[] value;
private Boolean booleanValue;
private BigDecimal numberValue;
private ZonedDateTime valueTimestamp;
private byte[] sendValue;

View File

@ -10,6 +10,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import tuwien.auto.calimero.GroupAddress;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -25,12 +26,14 @@ public class KnxGroupSetService implements IPropertyOwner {
private final KnxGroupWriteService knxGroupWriteService;
private final KnxGroupRepository knxGroupRepository;
@EventListener(ApplicationStartedEvent.class)
public void applicationStarted() {
knxGroupWriteService.knxGroupCreate(0, 0, 1, "1.001", true);
knxGroupWriteService.knxGroupCreate(0, 3, 6, "1.001", true);
knxGroupWriteService.knxGroupCreate(0, 4, 24, "5.001", false);
knxGroupWriteService.knxGroupCreate("Bad Dusche Status", 0, 3, 6, "1.001", true);
knxGroupWriteService.knxGroupCreate("Wohnzimmer Rollladen", 0, 4, 24, "5.001", false);
knxGroupWriteService.knxGroupCreate("Schlafzimmer Rollladen", 0, 3, 3, "5.001", false);
knxGroupWriteService.knxGroupCreate("Flur Rollladen", 0, 5, 13, "5.001", false);
requestAll();
}
@ -41,15 +44,26 @@ public class KnxGroupSetService implements IPropertyOwner {
@Override
public void setProperty(final String propertyName, final String value) {
final Matcher matcher = propertyNamePattern.matcher(propertyName);
if (matcher.matches()) {
final GroupAddress groupAddress = new GroupAddress(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(3)));
findGroupAddress(propertyName).ifPresent(groupAddress -> {
if (knxGroupWriteService.setSendValue(groupAddress, value)) {
knxLinkService.notifyActionPending();
} else {
log.error("No such KnxGroup.address = {}", groupAddress);
}
});
}
@Override
public Boolean readBoolean(final String propertyName) {
return findGroupAddress(propertyName).flatMap(groupAddress -> knxGroupRepository.findByAddressRaw(groupAddress.getRawAddress())).map(KnxGroup::getBooleanValue).orElse(null);
}
private Optional<GroupAddress> findGroupAddress(final String propertyName) {
final Matcher matcher = propertyNamePattern.matcher(propertyName);
if (matcher.matches()) {
return Optional.of(new GroupAddress(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(3))));
}
return Optional.empty();
}
}

View File

@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.KNXFormatException;
import tuwien.auto.calimero.dptxlator.DPTXlator;
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
@ -26,6 +27,15 @@ public class KnxGroupWriteService {
public void updateOrCreate(final int rawAddress, final byte[] data) {
final KnxGroup knxGroup = getOrCreate(rawAddress);
knxGroup.setValue(data);
knxGroup.setBooleanValue(null);
knxGroup.setNumberValue(null);
findTranslator(knxGroup).ifPresent(translator -> {
translator.setData(data);
if (translator instanceof DPTXlatorBoolean) {
knxGroup.setBooleanValue(((DPTXlatorBoolean) translator).getValueBoolean());
}
// TODO implement all DPTXlator...
});
knxGroup.setValueTimestamp(ZonedDateTime.now());
log.debug("KnxGroup updated: {}", knxGroup);
}
@ -44,10 +54,11 @@ public class KnxGroupWriteService {
knxGroupRepository.findAllByRead_AbleTrue().forEach(knxGroup -> knxGroup.getRead().setNextTimestamp(ZonedDateTime.now()));
}
public void knxGroupCreate(final int main, final int middle, final int sub, final String dpt, final boolean readable) {
public void knxGroupCreate(final String name, final int main, final int middle, final int sub, final String dpt, final boolean readable) {
final KnxGroup trans = new KnxGroup();
trans.setAddress(main, middle, sub);
trans.setDpt(dpt);
trans.setName(name);
trans.getRead().setAble(readable);
knxGroupRepository.save(trans);
}
@ -58,23 +69,33 @@ public class KnxGroupWriteService {
return false;
}
final KnxGroup knxGroup = knxGroupOptional.get();
getTranslator(knxGroup, value).ifPresent(translator -> knxGroup.setSendValue(translator.getData()));
knxGroup.getSend().setNextTimestamp(ZonedDateTime.now());
return true;
}
private Optional<DPTXlator> getTranslator(final KnxGroup knxGroup, final String value) {
final int mainNumber = Integer.parseInt(knxGroup.getDpt().split("\\.")[0]);
final Optional<DPTXlator> translatorOptional = findTranslator(knxGroup);
if (translatorOptional.isEmpty()) {
return false;
}
final DPTXlator translator = translatorOptional.get();
try {
final DPTXlator translator = TranslatorTypes.createTranslator(mainNumber, knxGroup.getDpt());
if (translator instanceof DPTXlatorBoolean) {
((DPTXlatorBoolean) translator).setValue(Objects.equals(value, "true"));
} else if (translator instanceof DPTXlator8BitUnsigned) {
((DPTXlator8BitUnsigned) translator).setValue(Integer.parseInt(value));
} else {
} else { // TODO implement all DPTXlator...
translator.setValue(value);
}
return Optional.of(translator);
knxGroup.setSendValue(translator.getData());
knxGroup.getSend().setNextTimestamp(ZonedDateTime.now());
return true;
} catch (KNXFormatException e) {
log.error(e.toString());
}
return false;
}
private Optional<DPTXlator> findTranslator(final KnxGroup knxGroup) {
final int mainNumber = Integer.parseInt(knxGroup.getDpt().split("\\.")[0]);
try {
return Optional.of(TranslatorTypes.createTranslator(mainNumber, knxGroup.getDpt()));
} catch (KNXException e) {
log.error(e.toString());
return Optional.empty();

View File

@ -8,4 +8,6 @@ public interface IPropertyOwner {
void setProperty(final String propertyName, final String value);
Boolean readBoolean(String propertyName);
}

View File

@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.Set;
@Slf4j
@ -15,13 +14,19 @@ public class PropertyService {
private final Set<IPropertyOwner> propertyOwners;
public void set(final String propertyName, final String value) {
log.info("Setting property \"{}\" => {}", propertyName, value);
final Optional<IPropertyOwner> iPropertyOwnerOptional = propertyOwners.stream().filter(iPropertyOwner -> iPropertyOwner.getPropertyNamePattern().matcher(propertyName).matches()).findFirst();
if (iPropertyOwnerOptional.isEmpty()) {
log.error("No IPropertyOwner found for name: {}", propertyName);
return;
log.debug("Setting property \"{}\" => {}", propertyName, value);
getOwnerOrThrow(propertyName).setProperty(propertyName, value);
}
iPropertyOwnerOptional.get().setProperty(propertyName, value);
public Boolean readBoolean(final String propertyName) {
return getOwnerOrThrow(propertyName).readBoolean(propertyName);
}
private IPropertyOwner getOwnerOrThrow(final String propertyName) {
return propertyOwners.stream()
.filter(iPropertyOwner -> iPropertyOwner.getPropertyNamePattern().matcher(propertyName).matches())
.findFirst()
.orElseThrow(() -> new RuntimeException("No IPropertyOwner found for propertyName: " + propertyName));
}
}

View File

@ -12,8 +12,8 @@ public class PropertyEntry implements Map.Entry<String, String> {
private String value;
public PropertyEntry(final int rawGroupAddress, final String value) {
this.key = "knx.group." + new GroupAddress(rawGroupAddress);
public PropertyEntry(final GroupAddress groupAddress, final String value) {
this.key = "knx.group." + groupAddress;
this.value = value;
}

View File

@ -14,6 +14,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import tuwien.auto.calimero.GroupAddress;
import javax.annotation.PostConstruct;
import javax.transaction.Transactional;
@ -28,11 +29,11 @@ import java.util.*;
@RequiredArgsConstructor
public class ScheduleService {
public static final int WOHNZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = 1048;
private static final GroupAddress WOHNZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = new GroupAddress(0, 4, 24);
public static final int SCHLAFZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = 771;
private static final GroupAddress SCHLAFZIMMER_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = new GroupAddress(0, 3, 3);
public static final int FLUR_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = 1293;
private static final GroupAddress FLUR_ROLLLADEN_POSITION_ANFAHREN_ADDRESS = new GroupAddress(0, 5, 13);
private final Config config;

View File

@ -0,0 +1,16 @@
package de.ph87.office.web;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class BadRequestException extends ResponseStatusException {
public BadRequestException(final String message) {
super(HttpStatus.BAD_REQUEST, message);
}
public BadRequestException(final String format, final Object... args) {
this(String.format(format, args));
}
}

View File

@ -0,0 +1,16 @@
package de.ph87.office.web;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class ForbiddenException extends ResponseStatusException {
public ForbiddenException(final String message) {
super(HttpStatus.FORBIDDEN, message);
}
public ForbiddenException(final String format, final Object... args) {
this(String.format(format, args));
}
}

View File

@ -0,0 +1,16 @@
package de.ph87.office.web;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class NotFoundException extends ResponseStatusException {
public NotFoundException(final String message) {
super(HttpStatus.NOT_FOUND, message);
}
public NotFoundException(final String format, final Object... args) {
this(String.format(format, args));
}
}

View File

@ -0,0 +1,49 @@
package de.ph87.homeautomation.web;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
http.csrf().disable();
http.authorizeRequests().anyRequest().permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowCredentials(true).allowedOrigins("http://localhost:4200").allowedMethods("*");
}
// @Override
// public void addResourceHandlers(final ResourceHandlerRegistry registry) {
// registry
// .addResourceHandler("/**")
// .addResourceLocations("classpath:/resources/")
// .resourceChain(true)
// .addResolver(new PathResourceResolver() {
// protected Resource getResource(String resourcePath, Resource location) throws IOException {
// Resource requestedResource = location.createRelative(resourcePath);
// return requestedResource.exists() && requestedResource.isReadable() ? requestedResource : new ClassPathResource("/resources/index.html");
// }
// });
// }
}

View File

@ -0,0 +1,27 @@
package de.ph87.office.web;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@CrossOrigin
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
public static final String DESTINATION_PREFIX = "/updates";
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").setAllowedOrigins("*");
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker(DESTINATION_PREFIX);
}
}

View File

@ -0,0 +1,51 @@
package de.ph87.office.web;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class WebSocketService {
private final SimpMessageSendingOperations simpMessageSendingOperations;
public void send(@NonNull final Serializable payload, final boolean existing) {
send(payload.getClass().getSimpleName(), payload, existing);
}
public void send(@NonNull final String type, @NonNull final Serializable payload, final boolean existing) {
final Message message = new Message(type, payload, existing);
log.debug("Sending message via websocket: {}", message);
simpMessageSendingOperations.convertAndSend(WebSocketConfig.DESTINATION_PREFIX, message);
}
private static class Message {
public final String type;
public final boolean existing;
public final Object payload;
public Message(@NonNull final String type, @NonNull final Serializable payload, final boolean existing) {
this.type = type;
this.existing = existing;
this.payload = payload;
}
@Override
public String toString() {
return payload.toString();
}
}
}

View File

@ -0,0 +1,17 @@
import org.junit.jupiter.api.Test;
import tuwien.auto.calimero.GroupAddress;
public class GroupAddressTranslatorTest {
@Test
public void test() {
getPrintln(1048);
getPrintln(771);
getPrintln(1293);
}
private void getPrintln(final int i) {
System.out.printf("%d: %s\n", i, new GroupAddress(i));
}
}