diff --git a/src/main/angular/package-lock.json b/src/main/angular/package-lock.json index d81d54f..4f9905e 100644 --- a/src/main/angular/package-lock.json +++ b/src/main/angular/package-lock.json @@ -1821,6 +1821,11 @@ "jsonc-parser": "3.0.0" } }, + "@stomp/stompjs": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-6.1.2.tgz", + "integrity": "sha512-FHDTrIFM5Ospi4L3Xhj6v2+NzCVAeNDcBe95YjUWhWiRMrBF6uN3I7AUOlRgT6jU/2WQvvYK8ZaIxFfxFp+uHQ==" + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", diff --git a/src/main/angular/package.json b/src/main/angular/package.json index cc5f4a6..bc19afb 100644 --- a/src/main/angular/package.json +++ b/src/main/angular/package.json @@ -23,7 +23,8 @@ "@fortawesome/free-regular-svg-icons": "^5.15.4", "rxjs": "~6.6.0", "tslib": "^2.3.0", - "zone.js": "~0.11.4" + "zone.js": "~0.11.4", + "@stomp/stompjs": "^6.0.0" }, "devDependencies": { "@angular-devkit/build-angular": "~12.2.0", diff --git a/src/main/angular/src/app/api/Update.ts b/src/main/angular/src/app/api/Update.ts new file mode 100644 index 0000000..30bdeb8 --- /dev/null +++ b/src/main/angular/src/app/api/Update.ts @@ -0,0 +1,29 @@ +import {validateBooleanNotNull, validateStringNotEmptyNotNull} from "./validators"; + +export class Update { + + constructor( + readonly type: string, + readonly existing: boolean, + readonly payload: T, + ) { + // nothing + } + + static fromJson(json: any) { + return new Update( + validateStringNotEmptyNotNull(json["type"]), + validateBooleanNotNull(json["existing"]), + json["payload"], + ) + } + + static convert(update: Update, fromJson: (json: any) => T) { + return new Update( + update.type, + update.existing, + fromJson(update.payload), + ) + } + +} diff --git a/src/main/angular/src/app/api/api.service.ts b/src/main/angular/src/app/api/api.service.ts index 36348c5..cd650c4 100644 --- a/src/main/angular/src/app/api/api.service.ts +++ b/src/main/angular/src/app/api/api.service.ts @@ -1,7 +1,10 @@ import {Injectable} from '@angular/core'; import {HttpClient} from "@angular/common/http"; -import {map} from "rxjs/operators"; +import {filter, map} from "rxjs/operators"; import {environment} from "../../environments/environment"; +import {Subject} from "rxjs"; +import {CompatClient, Stomp} from "@stomp/stompjs"; +import {Update} from "./Update"; export function NO_OP() { } @@ -22,10 +25,24 @@ function errorInterceptor(errorHandler: (error: any) => void): ((error: any) => }) export class ApiService { + private webSocketClient: CompatClient; + + private updateSubject = new Subject>(); + constructor( - private readonly http: HttpClient, + private http: HttpClient, ) { - // nothing + this.webSocketClient = Stomp.over(function () { + return new WebSocket(environment.apiBasePath.replace('http', 'ws') + "websocket"); + }); + this.webSocketClient.debug = () => null; + this.webSocketClient.connect({}, () => { + this.webSocketClient.subscribe("/updates", stompMessage => this.updateSubject.next(Update.fromJson(JSON.parse(stompMessage.body)))); + }); + } + + subscribe(type: string, fromJson: (json: object) => T, next: (item: Update) => void) { + this.updateSubject.pipe(filter(update => update.type === type), map(update => Update.convert(update, fromJson))).subscribe(next); } getItem(path: string, fromJson: (json: any) => T, next: (item: T) => void = NO_OP, error: (error: any) => void = NO_OP) { diff --git a/src/main/angular/src/app/api/device/Device.ts b/src/main/angular/src/app/api/device/Device.ts index 860d1fd..e2e20d8 100644 --- a/src/main/angular/src/app/api/device/Device.ts +++ b/src/main/angular/src/app/api/device/Device.ts @@ -1,4 +1,5 @@ import {validateBooleanAllowNull, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull, validateStringNullToEmpty} from "../validators"; +import {Property} from "../property/property.service"; export abstract class Device { @@ -43,6 +44,8 @@ export abstract class Device { return a.title.localeCompare(b.title); } + abstract updateProperty(property: Property): void; + } export class DeviceSwitch extends Device { @@ -58,6 +61,12 @@ export class DeviceSwitch extends Device { super(id, title, type); } + updateProperty(property: Property): void { + if (this.getState === property.name) { + this.state = property.booleanValue; + } + } + } export class DeviceShutter extends Device { @@ -73,4 +82,10 @@ export class DeviceShutter extends Device { super(id, title, type); } + updateProperty(property: Property): void { + if (this.getPercent === property.name) { + this.percent = property.numberValue; + } + } + } diff --git a/src/main/angular/src/app/api/property/property.service.ts b/src/main/angular/src/app/api/property/property.service.ts index 26c7554..47b3cf9 100644 --- a/src/main/angular/src/app/api/property/property.service.ts +++ b/src/main/angular/src/app/api/property/property.service.ts @@ -3,6 +3,7 @@ import {ApiService, NO_OP} from "../api.service"; import {validateBooleanAllowNull, validateDateAllowNull, validateNumberAllowNull, validateStringNotEmptyNotNull} from "../validators"; import {ISearchService} from "../ISearchService"; import {KeyValuePair} from "../KeyValuePair"; +import {Update} from "../Update"; export class Property { @@ -56,6 +57,10 @@ export class PropertyService implements ISearchService { // nothing } + subscribe(next: (device: Update) => void): void { + this.api.subscribe("PropertyDto", Property.fromJson, next); + } + get(id: string, next: (results: KeyValuePair) => void, error: (error: any) => void): void { this.api.postReturnItem("property/getById", id, KeyValuePair.fromJson, next, error); } diff --git a/src/main/angular/src/app/pages/device-list/device-list.component.ts b/src/main/angular/src/app/pages/device-list/device-list.component.ts index edde878..770c429 100644 --- a/src/main/angular/src/app/pages/device-list/device-list.component.ts +++ b/src/main/angular/src/app/pages/device-list/device-list.component.ts @@ -24,6 +24,7 @@ export class DeviceListComponent implements OnInit { } ngOnInit(): void { + this.propertyService.subscribe(update => this.devices.forEach(d => d.updateProperty(update.payload))); this.deviceService.findAll(devices => this.devices = devices); } @@ -52,7 +53,22 @@ export class DeviceListComponent implements OnInit { } create(): void { - this.deviceService.create(this.createType, device => this.devices.push(device)); + this.deviceService.create(this.createType, device => this.updateDevice(device, true)); + } + + private updateDevice(device: Device, existing: boolean): void { + const index: number = this.devices.findIndex(d => d.id === device.id); + if (index >= 0) { + if (existing) { + this.devices[index] = device; + } else { + this.devices.splice(index, 1) + } + } else { + if (existing) { + this.devices.push(device); + } + } } getSwitchClassList(device: Device): object { diff --git a/src/main/java/de/ph87/homeautomation/DemoDataService.java b/src/main/java/de/ph87/homeautomation/DemoDataService.java index be4d0e1..4084160 100644 --- a/src/main/java/de/ph87/homeautomation/DemoDataService.java +++ b/src/main/java/de/ph87/homeautomation/DemoDataService.java @@ -43,20 +43,36 @@ public class DemoDataService { @PostConstruct public void postConstruct() { final KnxGroupDto eg_flur_licht_schalten = createKnxGroupIfNotExists("EG Flur Licht Schalten", 0, 5, 14, "1.001", PropertyType.ON_OFF, false, false); - final KnxGroupDto eg_ambiente_schalten = createKnxGroupIfNotExists("EG Ambiente Schalten", 0, 3, 80, "1.001", PropertyType.ON_OFF, false, false); - final KnxGroupDto og_ambiente_schalten = createKnxGroupIfNotExists("OG Ambiente Schalten", 0, 6, 3, "1.001", PropertyType.ON_OFF, false, false); + + final KnxGroupDto ambiente_eg_schalten = createKnxGroupIfNotExists("Ambiente EG Schalten", 0, 3, 80, "1.001", PropertyType.ON_OFF, false, false); + final KnxGroupDto ambiente_eg_status = createKnxGroupIfNotExists("Ambiente EG Status", 0, 3, 81, "1.001", PropertyType.ON_OFF, true, false); + + final KnxGroupDto ambiente_og_schalten = createKnxGroupIfNotExists("Ambiente OG Schalten", 0, 6, 3, "1.001", PropertyType.ON_OFF, false, false); + final KnxGroupDto ambiente_og_status = createKnxGroupIfNotExists("Ambiente OG Status", 0, 6, 2, "1.001", PropertyType.ON_OFF, true, false); + final KnxGroupDto wohnzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Wohnzimmer Rollladen Position Anfahren", 0, 4, 24, "5.001", PropertyType.PERCENT, false, false); + final KnxGroupDto schlafzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Schlafzimmer Rollladen Position Anfahren", 0, 3, 3, "5.001", PropertyType.PERCENT, false, false); - final KnxGroupDto flur_rollladen_position_anfahren = createKnxGroupIfNotExists("Flur Rollladen Position Anfahren", 0, 5, 13, "5.001", PropertyType.PERCENT, false, false); + + final KnxGroupDto flur_og_rollladen_position_anfahren = createKnxGroupIfNotExists("Flur Rollladen Position Anfahren", 0, 5, 13, "5.001", PropertyType.PERCENT, false, false); + + final KnxGroupDto bad_licht_schalten = createKnxGroupIfNotExists("Bad Licht Schalten", 0, 3, 73, "1.001", PropertyType.ON_OFF, false, false); + final KnxGroupDto bad_licht_status = createKnxGroupIfNotExists("Bad Licht Status", 0, 5, 19, "1.001", PropertyType.ON_OFF, true, false); + final KnxGroupDto bad_licht_mitte_schalten = createKnxGroupIfNotExists("Bad Licht Mitte Schalten", 0, 3, 29, "1.001", PropertyType.ON_OFF, false, false); final KnxGroupDto bad_licht_mitte_status = createKnxGroupIfNotExists("Bad Licht Mitte Status", 0, 3, 30, "1.001", PropertyType.ON_OFF, true, false); + final KnxGroupDto helligkeit = createKnxGroupIfNotExists("Helligkeit", 0, 5, 6, "9.004", PropertyType.LUX, false, true); - createDeviceSwitch("Bad Licht Mitte", bad_licht_mitte_status, bad_licht_mitte_schalten); - createDeviceShutter("Flur OG Rollladen", null, flur_rollladen_position_anfahren); + createDeviceSwitch("Ambiente EG", ambiente_eg_status.getProperty(), ambiente_eg_schalten.getProperty()); + createDeviceSwitch("Ambiente OG", ambiente_og_status.getProperty(), ambiente_og_schalten.getProperty()); + createDeviceSwitch("Bad Licht", bad_licht_status.getProperty(), bad_licht_schalten.getProperty()); + createDeviceShutter("Wohnzimmer Rollladen", null, wohnzimmer_rollladen_position_anfahren.getProperty()); + createDeviceShutter("Schlafzimmer Rollladen", null, schlafzimmer_rollladen_position_anfahren.getProperty()); + createDeviceShutter("Flur Rollladen", null, flur_og_rollladen_position_anfahren.getProperty()); if (scheduleRepository.count() == 0) { - final Schedule scheduleEgFlurLicht = createSchedule("EG Flur Licht", eg_flur_licht_schalten); + final Schedule scheduleEgFlurLicht = createSchedule("EG Flur Licht", eg_flur_licht_schalten.getProperty()); createTime(scheduleEgFlurLicht, 11, 30, 0, MIN30, true); createTime(scheduleEgFlurLicht, 12, 30, 0, MIN30, false); createTime(scheduleEgFlurLicht, 16, 30, 0, MIN30, true); @@ -67,36 +83,36 @@ public class DemoDataService { createTime(scheduleEgFlurLicht, 2, 0, 0, MIN30, false); scheduleRepository.save(scheduleEgFlurLicht); - final Schedule scheduleEgAmbiente = createSchedule("EG Ambiente", eg_ambiente_schalten); + final Schedule scheduleEgAmbiente = createSchedule("Ambiente EG", ambiente_eg_schalten.getProperty()); createTime(scheduleEgAmbiente, 7, 15, 0, MIN30, true); createTime(scheduleEgAmbiente, 9, 30, 0, MIN30, false); createSunset(scheduleEgAmbiente, Zenith.OFFICIAL, MIN30, true); createSunset(scheduleEgAmbiente, Zenith.ASTRONOMICAL, MIN30, false); scheduleRepository.save(scheduleEgAmbiente); - final Schedule scheduleOgAmbiente = createSchedule("OG Ambiente", og_ambiente_schalten); + final Schedule scheduleOgAmbiente = createSchedule("Ambiente OG", ambiente_og_schalten.getProperty()); createTime(scheduleOgAmbiente, 7, 15, 0, MIN30, true); createTime(scheduleOgAmbiente, 9, 30, 0, MIN30, false); createSunset(scheduleOgAmbiente, Zenith.OFFICIAL, MIN30, true); createSunset(scheduleOgAmbiente, Zenith.ASTRONOMICAL, MIN30, false); scheduleRepository.save(scheduleOgAmbiente); - final Schedule scheduleWohnzimmerRollladen = createSchedule("Rollläden Wohnzimmer", wohnzimmer_rollladen_position_anfahren); + final Schedule scheduleWohnzimmerRollladen = createSchedule("Rollläden Wohnzimmer", wohnzimmer_rollladen_position_anfahren.getProperty()); createSunrise(scheduleWohnzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0); createSunset(scheduleWohnzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100); scheduleRepository.save(scheduleWohnzimmerRollladen); - final Schedule scheduleSchlafzimmerRollladen = createSchedule("Rollläden Schlafzimmer", schlafzimmer_rollladen_position_anfahren); + final Schedule scheduleSchlafzimmerRollladen = createSchedule("Rollläden Schlafzimmer", schlafzimmer_rollladen_position_anfahren.getProperty()); createTime(scheduleSchlafzimmerRollladen, 7, 0, 0, 0, 0); createSunset(scheduleSchlafzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100); scheduleRepository.save(scheduleSchlafzimmerRollladen); - final Schedule scheduleFlurRollladen = createSchedule("Rollladen Flur", flur_rollladen_position_anfahren); + final Schedule scheduleFlurRollladen = createSchedule("Rollladen Flur", flur_og_rollladen_position_anfahren.getProperty()); createSunrise(scheduleFlurRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0); createSunset(scheduleFlurRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100); scheduleRepository.save(scheduleFlurRollladen); - final Schedule scheduleBadLichtMitte = createSchedule("Bad Licht Mitte", bad_licht_mitte_schalten); + final Schedule scheduleBadLichtMitte = createSchedule("Bad Licht Mitte", bad_licht_mitte_schalten.getProperty()); createTime(scheduleBadLichtMitte, 10, 30, 0, MIN30, true); createTime(scheduleBadLichtMitte, 11, 30, 0, MIN30, false); createTime(scheduleBadLichtMitte, 15, 30, 0, MIN30, true); diff --git a/src/main/java/de/ph87/homeautomation/device/DeviceReadService.java b/src/main/java/de/ph87/homeautomation/device/DeviceReadService.java index 6d3c7a2..f6cc62f 100644 --- a/src/main/java/de/ph87/homeautomation/device/DeviceReadService.java +++ b/src/main/java/de/ph87/homeautomation/device/DeviceReadService.java @@ -2,7 +2,7 @@ package de.ph87.homeautomation.device; import de.ph87.homeautomation.device.devices.*; import de.ph87.homeautomation.property.PropertyService; -import de.ph87.office.web.NotFoundException; +import de.ph87.homeautomation.web.NotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/de/ph87/homeautomation/device/DeviceWriteService.java b/src/main/java/de/ph87/homeautomation/device/DeviceWriteService.java index 0eb1110..7a2cfd5 100644 --- a/src/main/java/de/ph87/homeautomation/device/DeviceWriteService.java +++ b/src/main/java/de/ph87/homeautomation/device/DeviceWriteService.java @@ -7,7 +7,7 @@ import de.ph87.homeautomation.device.devices.DeviceSwitch; import de.ph87.homeautomation.property.PropertyService; import de.ph87.homeautomation.property.PropertySetException; import de.ph87.homeautomation.schedule.ScheduleWriteService; -import de.ph87.office.web.BadRequestException; +import de.ph87.homeautomation.web.BadRequestException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupDto.java b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupDto.java index 602053a..1668e21 100644 --- a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupDto.java +++ b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupDto.java @@ -4,10 +4,12 @@ import de.ph87.homeautomation.property.PropertyDto; import lombok.Getter; @Getter -public class KnxGroupDto extends PropertyDto { +public class KnxGroupDto { public final long id; + public final PropertyDto property; + public final int addressRaw; public final String addressStr; @@ -15,11 +17,11 @@ public class KnxGroupDto extends PropertyDto { public final String dpt; public KnxGroupDto(final KnxGroup knxGroup) { - super(knxGroup.getPropertyName(), knxGroup.getTitle(), knxGroup.getPropertyType(), knxGroup.getBooleanValue(), knxGroup.getNumberValue(), knxGroup.getValueTimestamp()); - id = knxGroup.getId(); - addressRaw = knxGroup.getAddressRaw(); - addressStr = knxGroup.getAddressStr(); - dpt = knxGroup.getDpt(); + this.property = new PropertyDto(knxGroup.getPropertyName(), knxGroup.getTitle(), knxGroup.getPropertyType(), knxGroup.getBooleanValue(), knxGroup.getNumberValue(), knxGroup.getValueTimestamp()); + this.id = knxGroup.getId(); + this.addressRaw = knxGroup.getAddressRaw(); + this.addressStr = knxGroup.getAddressStr(); + this.dpt = knxGroup.getDpt(); } } diff --git a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupMapperService.java b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupMapperService.java new file mode 100644 index 0000000..e8c34fc --- /dev/null +++ b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupMapperService.java @@ -0,0 +1,19 @@ +package de.ph87.homeautomation.knx.group; + +import de.ph87.homeautomation.property.PropertyDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class KnxGroupMapperService { + + public PropertyDto toPropertyDto(final KnxGroup knxGroup) { + return new PropertyDto(knxGroup.getPropertyName(), knxGroup.getTitle(), knxGroup.getPropertyType(), knxGroup.getBooleanValue(), knxGroup.getNumberValue(), knxGroup.getValueTimestamp()); + } + +} diff --git a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupSetService.java b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupOwnerService.java similarity index 78% rename from src/main/java/de/ph87/homeautomation/knx/group/KnxGroupSetService.java rename to src/main/java/de/ph87/homeautomation/knx/group/KnxGroupOwnerService.java index ec8c525..1a3b5b9 100644 --- a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupSetService.java +++ b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupOwnerService.java @@ -6,9 +6,7 @@ import de.ph87.homeautomation.property.PropertySetException; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tuwien.auto.calimero.GroupAddress; @@ -25,7 +23,7 @@ import static de.ph87.homeautomation.shared.Helpers.quoteOrNull; @Service @Transactional @RequiredArgsConstructor -public class KnxGroupSetService implements IPropertyOwner { +public class KnxGroupOwnerService implements IPropertyOwner { @Getter private final Pattern propertyNamePattern = Pattern.compile("^knx\\.group\\.(\\d+)\\.(\\d+)\\.(\\d+)$"); @@ -36,11 +34,7 @@ public class KnxGroupSetService implements IPropertyOwner { private final ApplicationEventPublisher eventPublisher; - @EventListener(ApplicationStartedEvent.class) - public void requestAll() { - knxGroupWriteService.markAllForRead(); - eventPublisher.publishEvent(new KnxThreadWakeUpEvent()); - } + private final KnxGroupMapperService knxGroupMapperService; @Override public void setProperty(final String propertyName, final double value) throws PropertySetException { @@ -66,19 +60,23 @@ public class KnxGroupSetService implements IPropertyOwner { return knxGroupRepository.findByAddressRaw(parseGroupAddress(propertyName).getRawAddress()).map(KnxGroup::getNumberValue).orElse(null); } - @Override - public Optional findPropertyByName(final String propertyName) { - return knxGroupRepository.findByPropertyName(propertyName).map(KnxGroupDto::new); - } - @Override public List findAllProperties() { - return knxGroupRepository.findAll().stream().map(KnxGroupDto::new).collect(Collectors.toList()); + return knxGroupRepository.findAll().stream().map(knxGroupMapperService::toPropertyDto).collect(Collectors.toList()); } @Override public List findAllPropertiesLike(final String like) { - return knxGroupRepository.findAllByPropertyNameLikeIgnoreCaseOrTitleLikeIgnoreCase(like, like).stream().map(KnxGroupDto::new).collect(Collectors.toList()); + return knxGroupRepository.findAllByPropertyNameLikeIgnoreCaseOrTitleLikeIgnoreCase(like, like).stream().map(knxGroupMapperService::toPropertyDto).collect(Collectors.toList()); + } + + @Override + public Optional findPropertyDtoByPropertyName(final String propertyName) { + return findKnxGroupByPropertyName(propertyName).map(knxGroupMapperService::toPropertyDto); + } + + private Optional findKnxGroupByPropertyName(final String propertyName) { + return knxGroupRepository.findByPropertyName(propertyName); } private GroupAddress parseGroupAddress(final String propertyName) { diff --git a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupReadService.java b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupReadService.java new file mode 100644 index 0000000..2556ef9 --- /dev/null +++ b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupReadService.java @@ -0,0 +1,14 @@ +package de.ph87.homeautomation.knx.group; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class KnxGroupReadService { + +} diff --git a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupWriteService.java b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupWriteService.java index 8f4509b..0a980e7 100644 --- a/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupWriteService.java +++ b/src/main/java/de/ph87/homeautomation/knx/group/KnxGroupWriteService.java @@ -1,8 +1,12 @@ package de.ph87.homeautomation.knx.group; +import de.ph87.homeautomation.property.PropertyChangedEvent; import de.ph87.homeautomation.property.PropertyType; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -12,10 +16,6 @@ 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; @@ -29,17 +29,16 @@ public class KnxGroupWriteService { private final KnxGroupRepository knxGroupRepository; - private DatagramSocket broadcastDatagramSocket; + private final ApplicationEventPublisher applicationEventPublisher; - private InetAddress broadcastAddress; + private final ApplicationEventPublisher eventPublisher; - private final int broadcastPort = 1987; + private final KnxGroupMapperService knxGroupMapperService; - @PostConstruct - public void postConstruct() throws SocketException, UnknownHostException { - broadcastDatagramSocket = new DatagramSocket(); - broadcastDatagramSocket.setBroadcast(true); - broadcastAddress = InetAddress.getByName("255.255.255.255"); + @EventListener(ApplicationStartedEvent.class) + public void markAllForRead() { + knxGroupRepository.findAllByRead_AbleTrue().forEach(knxGroup -> knxGroup.getRead().setNextTimestamp(ZonedDateTime.now())); + eventPublisher.publishEvent(new KnxThreadWakeUpEvent()); } public void updateIfExists(final int rawAddress, final byte[] data, final IndividualAddress knxDeviceAddress) { @@ -68,7 +67,7 @@ public class KnxGroupWriteService { translate(DPTXlator64BitSigned.class, translator, knxGroup::setNumberValue, DPTXlator64BitSigned::getNumericValue); translate(DPTXlatorSceneNumber.class, translator, knxGroup::setNumberValue, DPTXlatorSceneNumber::getNumericValue); // TODO implement all DPTXlator... - broadcast(knxGroup); + publish(knxGroup); } catch (NoTranslatorException e) { log.error(e.getMessage()); } @@ -76,6 +75,10 @@ public class KnxGroupWriteService { }); } + private void publish(final KnxGroup knxGroup) { + applicationEventPublisher.publishEvent(new PropertyChangedEvent(knxGroupMapperService.toPropertyDto(knxGroup), true)); + } + private void setBooleanValue(final KnxGroup knxGroup, final Boolean value) { knxGroup.setBooleanValue(value); if (value == null) { @@ -85,29 +88,12 @@ public class KnxGroupWriteService { } } - 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); diff --git a/src/main/java/de/ph87/homeautomation/property/IPropertyOwner.java b/src/main/java/de/ph87/homeautomation/property/IPropertyOwner.java index 2aaa8b2..b9fb6b2 100644 --- a/src/main/java/de/ph87/homeautomation/property/IPropertyOwner.java +++ b/src/main/java/de/ph87/homeautomation/property/IPropertyOwner.java @@ -10,14 +10,14 @@ public interface IPropertyOwner { void setProperty(final String propertyName, final double value) throws PropertySetException; - Boolean readBoolean(String propertyName); + Boolean readBoolean(final String propertyName); - Double readNumber(String propertyName); + Double readNumber(final String propertyName); List findAllProperties(); List findAllPropertiesLike(final String like); - Optional findPropertyByName(final String propertyName); + Optional findPropertyDtoByPropertyName(final String propertyName); } diff --git a/src/main/java/de/ph87/homeautomation/property/PropertyChangedEvent.java b/src/main/java/de/ph87/homeautomation/property/PropertyChangedEvent.java new file mode 100644 index 0000000..c4089aa --- /dev/null +++ b/src/main/java/de/ph87/homeautomation/property/PropertyChangedEvent.java @@ -0,0 +1,17 @@ +package de.ph87.homeautomation.property; + +import lombok.Getter; + +@Getter +public class PropertyChangedEvent { + + private final PropertyDto propertyDto; + + private final boolean existing; + + public PropertyChangedEvent(final PropertyDto propertyDto, final boolean existing) { + this.propertyDto = propertyDto; + this.existing = existing; + } + +} diff --git a/src/main/java/de/ph87/homeautomation/property/PropertyChangedService.java b/src/main/java/de/ph87/homeautomation/property/PropertyChangedService.java new file mode 100644 index 0000000..bbe2ac4 --- /dev/null +++ b/src/main/java/de/ph87/homeautomation/property/PropertyChangedService.java @@ -0,0 +1,54 @@ +package de.ph87.homeautomation.property; + +import de.ph87.homeautomation.knx.group.KnxGroup; +import de.ph87.homeautomation.web.WebSocketService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.net.*; +import java.nio.charset.StandardCharsets; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class PropertyChangedService { + + private DatagramSocket broadcastDatagramSocket; + + private InetAddress broadcastAddress; + + private final WebSocketService webSocketService; + + @PostConstruct + public void postConstruct() throws SocketException, UnknownHostException { + broadcastDatagramSocket = new DatagramSocket(); + broadcastDatagramSocket.setBroadcast(true); + broadcastAddress = InetAddress.getByName("255.255.255.255"); + } + + @TransactionalEventListener + public void propertyChangedListener(final PropertyChangedEvent event) { + webSocketService.send(event.getPropertyDto(), event.isExisting()); + } + + 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 { + final int broadcastPort = 1987; + 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()); + } + } + } + +} diff --git a/src/main/java/de/ph87/homeautomation/property/PropertyDto.java b/src/main/java/de/ph87/homeautomation/property/PropertyDto.java index 92f3dbd..7b9087a 100644 --- a/src/main/java/de/ph87/homeautomation/property/PropertyDto.java +++ b/src/main/java/de/ph87/homeautomation/property/PropertyDto.java @@ -2,10 +2,11 @@ package de.ph87.homeautomation.property; import lombok.Getter; +import java.io.Serializable; import java.time.ZonedDateTime; @Getter -public class PropertyDto { +public final class PropertyDto implements Serializable { public final String name; diff --git a/src/main/java/de/ph87/homeautomation/property/PropertyService.java b/src/main/java/de/ph87/homeautomation/property/PropertyService.java index 8d45b9e..9bbf9e9 100644 --- a/src/main/java/de/ph87/homeautomation/property/PropertyService.java +++ b/src/main/java/de/ph87/homeautomation/property/PropertyService.java @@ -1,6 +1,6 @@ package de.ph87.homeautomation.property; -import de.ph87.office.web.NotFoundException; +import de.ph87.homeautomation.web.NotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -51,7 +51,7 @@ public class PropertyService { } public PropertyDto getById(final String propertyName) { - return findOwner(propertyName).flatMap(iPropertyOwner -> iPropertyOwner.findPropertyByName(propertyName)).orElseThrow(() -> new NotFoundException("Property.name=%s", propertyName)); + return findOwner(propertyName).flatMap(iPropertyOwner -> iPropertyOwner.findPropertyDtoByPropertyName(propertyName)).orElseThrow(() -> new NotFoundException("Property.name=%s", propertyName)); } private Optional findOwner(final String propertyName) { diff --git a/src/main/java/de/ph87/homeautomation/schedule/ScheduleReadService.java b/src/main/java/de/ph87/homeautomation/schedule/ScheduleReadService.java index 91b6fdd..582d7c7 100644 --- a/src/main/java/de/ph87/homeautomation/schedule/ScheduleReadService.java +++ b/src/main/java/de/ph87/homeautomation/schedule/ScheduleReadService.java @@ -1,7 +1,7 @@ package de.ph87.homeautomation.schedule; import de.ph87.homeautomation.schedule.entry.ScheduleEntry; -import de.ph87.office.web.NotFoundException; +import de.ph87.homeautomation.web.NotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/de/ph87/homeautomation/schedule/entry/ScheduleEntryReadService.java b/src/main/java/de/ph87/homeautomation/schedule/entry/ScheduleEntryReadService.java index 1e19c1d..4fe444f 100644 --- a/src/main/java/de/ph87/homeautomation/schedule/entry/ScheduleEntryReadService.java +++ b/src/main/java/de/ph87/homeautomation/schedule/entry/ScheduleEntryReadService.java @@ -1,6 +1,6 @@ package de.ph87.homeautomation.schedule.entry; -import de.ph87.office.web.NotFoundException; +import de.ph87.homeautomation.web.NotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/de/ph87/homeautomation/web/BadRequestException.java b/src/main/java/de/ph87/homeautomation/web/BadRequestException.java index 7976d9b..e2bac88 100644 --- a/src/main/java/de/ph87/homeautomation/web/BadRequestException.java +++ b/src/main/java/de/ph87/homeautomation/web/BadRequestException.java @@ -1,4 +1,4 @@ -package de.ph87.office.web; +package de.ph87.homeautomation.web; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; diff --git a/src/main/java/de/ph87/homeautomation/web/ForbiddenException.java b/src/main/java/de/ph87/homeautomation/web/ForbiddenException.java index a474089..8451924 100644 --- a/src/main/java/de/ph87/homeautomation/web/ForbiddenException.java +++ b/src/main/java/de/ph87/homeautomation/web/ForbiddenException.java @@ -1,4 +1,4 @@ -package de.ph87.office.web; +package de.ph87.homeautomation.web; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; diff --git a/src/main/java/de/ph87/homeautomation/web/NotFoundException.java b/src/main/java/de/ph87/homeautomation/web/NotFoundException.java index 3e28a24..28781cc 100644 --- a/src/main/java/de/ph87/homeautomation/web/NotFoundException.java +++ b/src/main/java/de/ph87/homeautomation/web/NotFoundException.java @@ -1,4 +1,4 @@ -package de.ph87.office.web; +package de.ph87.homeautomation.web; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; diff --git a/src/main/java/de/ph87/homeautomation/web/WebSocketService.java b/src/main/java/de/ph87/homeautomation/web/WebSocketService.java index 8b30ca5..d20755a 100644 --- a/src/main/java/de/ph87/homeautomation/web/WebSocketService.java +++ b/src/main/java/de/ph87/homeautomation/web/WebSocketService.java @@ -1,6 +1,5 @@ -package de.ph87.office.web; +package de.ph87.homeautomation.web; -import de.ph87.homeautomation.web.WebSocketConfig; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.lang.NonNull;