websocket property updates

This commit is contained in:
Patrick Haßel 2021-11-03 11:51:05 +01:00
parent 927e8df13a
commit bfa6b7b12b
26 changed files with 277 additions and 83 deletions

View File

@ -1821,6 +1821,11 @@
"jsonc-parser": "3.0.0" "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": { "@tootallnate/once": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",

View File

@ -23,7 +23,8 @@
"@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-regular-svg-icons": "^5.15.4",
"rxjs": "~6.6.0", "rxjs": "~6.6.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.11.4" "zone.js": "~0.11.4",
"@stomp/stompjs": "^6.0.0"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~12.2.0", "@angular-devkit/build-angular": "~12.2.0",

View File

@ -0,0 +1,29 @@
import {validateBooleanNotNull, validateStringNotEmptyNotNull} from "./validators";
export class Update<T> {
constructor(
readonly type: string,
readonly existing: boolean,
readonly payload: T,
) {
// nothing
}
static fromJson(json: any) {
return new Update<any>(
validateStringNotEmptyNotNull(json["type"]),
validateBooleanNotNull(json["existing"]),
json["payload"],
)
}
static convert<T>(update: Update<any>, fromJson: (json: any) => T) {
return new Update<T>(
update.type,
update.existing,
fromJson(update.payload),
)
}
}

View File

@ -1,7 +1,10 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http"; import {HttpClient} from "@angular/common/http";
import {map} from "rxjs/operators"; import {filter, map} from "rxjs/operators";
import {environment} from "../../environments/environment"; import {environment} from "../../environments/environment";
import {Subject} from "rxjs";
import {CompatClient, Stomp} from "@stomp/stompjs";
import {Update} from "./Update";
export function NO_OP() { export function NO_OP() {
} }
@ -22,10 +25,24 @@ function errorInterceptor(errorHandler: (error: any) => void): ((error: any) =>
}) })
export class ApiService { export class ApiService {
private webSocketClient: CompatClient;
private updateSubject = new Subject<Update<object>>();
constructor( 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<T>(type: string, fromJson: (json: object) => T, next: (item: Update<T>) => void) {
this.updateSubject.pipe(filter(update => update.type === type), map(update => Update.convert(update, fromJson))).subscribe(next);
} }
getItem<T>(path: string, fromJson: (json: any) => T, next: (item: T) => void = NO_OP, error: (error: any) => void = NO_OP) { getItem<T>(path: string, fromJson: (json: any) => T, next: (item: T) => void = NO_OP, error: (error: any) => void = NO_OP) {

View File

@ -1,4 +1,5 @@
import {validateBooleanAllowNull, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull, validateStringNullToEmpty} from "../validators"; import {validateBooleanAllowNull, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull, validateStringNullToEmpty} from "../validators";
import {Property} from "../property/property.service";
export abstract class Device { export abstract class Device {
@ -43,6 +44,8 @@ export abstract class Device {
return a.title.localeCompare(b.title); return a.title.localeCompare(b.title);
} }
abstract updateProperty(property: Property): void;
} }
export class DeviceSwitch extends Device { export class DeviceSwitch extends Device {
@ -58,6 +61,12 @@ export class DeviceSwitch extends Device {
super(id, title, type); super(id, title, type);
} }
updateProperty(property: Property): void {
if (this.getState === property.name) {
this.state = property.booleanValue;
}
}
} }
export class DeviceShutter extends Device { export class DeviceShutter extends Device {
@ -73,4 +82,10 @@ export class DeviceShutter extends Device {
super(id, title, type); super(id, title, type);
} }
updateProperty(property: Property): void {
if (this.getPercent === property.name) {
this.percent = property.numberValue;
}
}
} }

View File

@ -3,6 +3,7 @@ import {ApiService, NO_OP} from "../api.service";
import {validateBooleanAllowNull, validateDateAllowNull, validateNumberAllowNull, validateStringNotEmptyNotNull} from "../validators"; import {validateBooleanAllowNull, validateDateAllowNull, validateNumberAllowNull, validateStringNotEmptyNotNull} from "../validators";
import {ISearchService} from "../ISearchService"; import {ISearchService} from "../ISearchService";
import {KeyValuePair} from "../KeyValuePair"; import {KeyValuePair} from "../KeyValuePair";
import {Update} from "../Update";
export class Property { export class Property {
@ -56,6 +57,10 @@ export class PropertyService implements ISearchService {
// nothing // nothing
} }
subscribe(next: (device: Update<Property>) => void): void {
this.api.subscribe("PropertyDto", Property.fromJson, next);
}
get(id: string, next: (results: KeyValuePair) => void, error: (error: any) => void): void { get(id: string, next: (results: KeyValuePair) => void, error: (error: any) => void): void {
this.api.postReturnItem("property/getById", id, KeyValuePair.fromJson, next, error); this.api.postReturnItem("property/getById", id, KeyValuePair.fromJson, next, error);
} }

View File

@ -24,6 +24,7 @@ export class DeviceListComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.propertyService.subscribe(update => this.devices.forEach(d => d.updateProperty(update.payload)));
this.deviceService.findAll(devices => this.devices = devices); this.deviceService.findAll(devices => this.devices = devices);
} }
@ -52,7 +53,22 @@ export class DeviceListComponent implements OnInit {
} }
create(): void { 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 { getSwitchClassList(device: Device): object {

View File

@ -43,20 +43,36 @@ public class DemoDataService {
@PostConstruct @PostConstruct
public void 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_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 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 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_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 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); 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); createDeviceSwitch("Ambiente EG", ambiente_eg_status.getProperty(), ambiente_eg_schalten.getProperty());
createDeviceShutter("Flur OG Rollladen", null, flur_rollladen_position_anfahren); 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) { 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, 11, 30, 0, MIN30, true);
createTime(scheduleEgFlurLicht, 12, 30, 0, MIN30, false); createTime(scheduleEgFlurLicht, 12, 30, 0, MIN30, false);
createTime(scheduleEgFlurLicht, 16, 30, 0, MIN30, true); createTime(scheduleEgFlurLicht, 16, 30, 0, MIN30, true);
@ -67,36 +83,36 @@ public class DemoDataService {
createTime(scheduleEgFlurLicht, 2, 0, 0, MIN30, false); createTime(scheduleEgFlurLicht, 2, 0, 0, MIN30, false);
scheduleRepository.save(scheduleEgFlurLicht); 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, 7, 15, 0, MIN30, true);
createTime(scheduleEgAmbiente, 9, 30, 0, MIN30, false); createTime(scheduleEgAmbiente, 9, 30, 0, MIN30, false);
createSunset(scheduleEgAmbiente, Zenith.OFFICIAL, MIN30, true); createSunset(scheduleEgAmbiente, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleEgAmbiente, Zenith.ASTRONOMICAL, MIN30, false); createSunset(scheduleEgAmbiente, Zenith.ASTRONOMICAL, MIN30, false);
scheduleRepository.save(scheduleEgAmbiente); 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, 7, 15, 0, MIN30, true);
createTime(scheduleOgAmbiente, 9, 30, 0, MIN30, false); createTime(scheduleOgAmbiente, 9, 30, 0, MIN30, false);
createSunset(scheduleOgAmbiente, Zenith.OFFICIAL, MIN30, true); createSunset(scheduleOgAmbiente, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleOgAmbiente, Zenith.ASTRONOMICAL, MIN30, false); createSunset(scheduleOgAmbiente, Zenith.ASTRONOMICAL, MIN30, false);
scheduleRepository.save(scheduleOgAmbiente); 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); createSunrise(scheduleWohnzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0);
createSunset(scheduleWohnzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100); createSunset(scheduleWohnzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
scheduleRepository.save(scheduleWohnzimmerRollladen); 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); createTime(scheduleSchlafzimmerRollladen, 7, 0, 0, 0, 0);
createSunset(scheduleSchlafzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100); createSunset(scheduleSchlafzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
scheduleRepository.save(scheduleSchlafzimmerRollladen); 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); createSunrise(scheduleFlurRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0);
createSunset(scheduleFlurRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100); createSunset(scheduleFlurRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
scheduleRepository.save(scheduleFlurRollladen); 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, 10, 30, 0, MIN30, true);
createTime(scheduleBadLichtMitte, 11, 30, 0, MIN30, false); createTime(scheduleBadLichtMitte, 11, 30, 0, MIN30, false);
createTime(scheduleBadLichtMitte, 15, 30, 0, MIN30, true); createTime(scheduleBadLichtMitte, 15, 30, 0, MIN30, true);

View File

@ -2,7 +2,7 @@ package de.ph87.homeautomation.device;
import de.ph87.homeautomation.device.devices.*; import de.ph87.homeautomation.device.devices.*;
import de.ph87.homeautomation.property.PropertyService; import de.ph87.homeautomation.property.PropertyService;
import de.ph87.office.web.NotFoundException; import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;

View File

@ -7,7 +7,7 @@ import de.ph87.homeautomation.device.devices.DeviceSwitch;
import de.ph87.homeautomation.property.PropertyService; import de.ph87.homeautomation.property.PropertyService;
import de.ph87.homeautomation.property.PropertySetException; import de.ph87.homeautomation.property.PropertySetException;
import de.ph87.homeautomation.schedule.ScheduleWriteService; import de.ph87.homeautomation.schedule.ScheduleWriteService;
import de.ph87.office.web.BadRequestException; import de.ph87.homeautomation.web.BadRequestException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;

View File

@ -4,10 +4,12 @@ import de.ph87.homeautomation.property.PropertyDto;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
public class KnxGroupDto extends PropertyDto { public class KnxGroupDto {
public final long id; public final long id;
public final PropertyDto property;
public final int addressRaw; public final int addressRaw;
public final String addressStr; public final String addressStr;
@ -15,11 +17,11 @@ public class KnxGroupDto extends PropertyDto {
public final String dpt; public final String dpt;
public KnxGroupDto(final KnxGroup knxGroup) { public KnxGroupDto(final KnxGroup knxGroup) {
super(knxGroup.getPropertyName(), knxGroup.getTitle(), knxGroup.getPropertyType(), knxGroup.getBooleanValue(), knxGroup.getNumberValue(), knxGroup.getValueTimestamp()); this.property = new PropertyDto(knxGroup.getPropertyName(), knxGroup.getTitle(), knxGroup.getPropertyType(), knxGroup.getBooleanValue(), knxGroup.getNumberValue(), knxGroup.getValueTimestamp());
id = knxGroup.getId(); this.id = knxGroup.getId();
addressRaw = knxGroup.getAddressRaw(); this.addressRaw = knxGroup.getAddressRaw();
addressStr = knxGroup.getAddressStr(); this.addressStr = knxGroup.getAddressStr();
dpt = knxGroup.getDpt(); this.dpt = knxGroup.getDpt();
} }
} }

View File

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

View File

@ -6,9 +6,7 @@ import de.ph87.homeautomation.property.PropertySetException;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import tuwien.auto.calimero.GroupAddress; import tuwien.auto.calimero.GroupAddress;
@ -25,7 +23,7 @@ import static de.ph87.homeautomation.shared.Helpers.quoteOrNull;
@Service @Service
@Transactional @Transactional
@RequiredArgsConstructor @RequiredArgsConstructor
public class KnxGroupSetService implements IPropertyOwner { public class KnxGroupOwnerService implements IPropertyOwner {
@Getter @Getter
private final Pattern propertyNamePattern = Pattern.compile("^knx\\.group\\.(\\d+)\\.(\\d+)\\.(\\d+)$"); private final Pattern propertyNamePattern = Pattern.compile("^knx\\.group\\.(\\d+)\\.(\\d+)\\.(\\d+)$");
@ -36,11 +34,7 @@ public class KnxGroupSetService implements IPropertyOwner {
private final ApplicationEventPublisher eventPublisher; private final ApplicationEventPublisher eventPublisher;
@EventListener(ApplicationStartedEvent.class) private final KnxGroupMapperService knxGroupMapperService;
public void requestAll() {
knxGroupWriteService.markAllForRead();
eventPublisher.publishEvent(new KnxThreadWakeUpEvent());
}
@Override @Override
public void setProperty(final String propertyName, final double value) throws PropertySetException { 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); return knxGroupRepository.findByAddressRaw(parseGroupAddress(propertyName).getRawAddress()).map(KnxGroup::getNumberValue).orElse(null);
} }
@Override
public Optional<KnxGroupDto> findPropertyByName(final String propertyName) {
return knxGroupRepository.findByPropertyName(propertyName).map(KnxGroupDto::new);
}
@Override @Override
public List<PropertyDto> findAllProperties() { public List<PropertyDto> findAllProperties() {
return knxGroupRepository.findAll().stream().map(KnxGroupDto::new).collect(Collectors.toList()); return knxGroupRepository.findAll().stream().map(knxGroupMapperService::toPropertyDto).collect(Collectors.toList());
} }
@Override @Override
public List<PropertyDto> findAllPropertiesLike(final String like) { public List<PropertyDto> 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<PropertyDto> findPropertyDtoByPropertyName(final String propertyName) {
return findKnxGroupByPropertyName(propertyName).map(knxGroupMapperService::toPropertyDto);
}
private Optional<KnxGroup> findKnxGroupByPropertyName(final String propertyName) {
return knxGroupRepository.findByPropertyName(propertyName);
} }
private GroupAddress parseGroupAddress(final String propertyName) { private GroupAddress parseGroupAddress(final String propertyName) {

View File

@ -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 {
}

View File

@ -1,8 +1,12 @@
package de.ph87.homeautomation.knx.group; package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.property.PropertyChangedEvent;
import de.ph87.homeautomation.property.PropertyType; import de.ph87.homeautomation.property.PropertyType;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; 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.stereotype.Service;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -12,10 +16,6 @@ import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.KNXFormatException; import tuwien.auto.calimero.KNXFormatException;
import tuwien.auto.calimero.dptxlator.*; 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.time.ZonedDateTime;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -29,17 +29,16 @@ public class KnxGroupWriteService {
private final KnxGroupRepository knxGroupRepository; 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 @EventListener(ApplicationStartedEvent.class)
public void postConstruct() throws SocketException, UnknownHostException { public void markAllForRead() {
broadcastDatagramSocket = new DatagramSocket(); knxGroupRepository.findAllByRead_AbleTrue().forEach(knxGroup -> knxGroup.getRead().setNextTimestamp(ZonedDateTime.now()));
broadcastDatagramSocket.setBroadcast(true); eventPublisher.publishEvent(new KnxThreadWakeUpEvent());
broadcastAddress = InetAddress.getByName("255.255.255.255");
} }
public void updateIfExists(final int rawAddress, final byte[] data, final IndividualAddress knxDeviceAddress) { 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(DPTXlator64BitSigned.class, translator, knxGroup::setNumberValue, DPTXlator64BitSigned::getNumericValue);
translate(DPTXlatorSceneNumber.class, translator, knxGroup::setNumberValue, DPTXlatorSceneNumber::getNumericValue); translate(DPTXlatorSceneNumber.class, translator, knxGroup::setNumberValue, DPTXlatorSceneNumber::getNumericValue);
// TODO implement all DPTXlator... // TODO implement all DPTXlator...
broadcast(knxGroup); publish(knxGroup);
} catch (NoTranslatorException e) { } catch (NoTranslatorException e) {
log.error(e.getMessage()); 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) { private void setBooleanValue(final KnxGroup knxGroup, final Boolean value) {
knxGroup.setBooleanValue(value); knxGroup.setBooleanValue(value);
if (value == null) { 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 <X extends DPTXlator, T> void translate(final Class<X> dptXlatorClass, final DPTXlator translator, final Consumer<T> setter, final Function<X, T> getter) { private <X extends DPTXlator, T> void translate(final Class<X> dptXlatorClass, final DPTXlator translator, final Consumer<T> setter, final Function<X, T> getter) {
if (dptXlatorClass.isInstance(translator)) { if (dptXlatorClass.isInstance(translator)) {
setter.accept(getter.apply(dptXlatorClass.cast(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) { 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(); final KnxGroup trans = new KnxGroup();
trans.setAddress(address); trans.setAddress(address);

View File

@ -10,14 +10,14 @@ public interface IPropertyOwner {
void setProperty(final String propertyName, final double value) throws PropertySetException; 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<PropertyDto> findAllProperties(); List<PropertyDto> findAllProperties();
List<PropertyDto> findAllPropertiesLike(final String like); List<PropertyDto> findAllPropertiesLike(final String like);
Optional<? extends PropertyDto> findPropertyByName(final String propertyName); Optional<? extends PropertyDto> findPropertyDtoByPropertyName(final String propertyName);
} }

View File

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

View File

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

View File

@ -2,10 +2,11 @@ package de.ph87.homeautomation.property;
import lombok.Getter; import lombok.Getter;
import java.io.Serializable;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@Getter @Getter
public class PropertyDto { public final class PropertyDto implements Serializable {
public final String name; public final String name;

View File

@ -1,6 +1,6 @@
package de.ph87.homeautomation.property; package de.ph87.homeautomation.property;
import de.ph87.office.web.NotFoundException; import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -51,7 +51,7 @@ public class PropertyService {
} }
public PropertyDto getById(final String propertyName) { 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<IPropertyOwner> findOwner(final String propertyName) { private Optional<IPropertyOwner> findOwner(final String propertyName) {

View File

@ -1,7 +1,7 @@
package de.ph87.homeautomation.schedule; package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry; import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import de.ph87.office.web.NotFoundException; import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;

View File

@ -1,6 +1,6 @@
package de.ph87.homeautomation.schedule.entry; package de.ph87.homeautomation.schedule.entry;
import de.ph87.office.web.NotFoundException; import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;

View File

@ -1,4 +1,4 @@
package de.ph87.office.web; package de.ph87.homeautomation.web;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;

View File

@ -1,4 +1,4 @@
package de.ph87.office.web; package de.ph87.homeautomation.web;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;

View File

@ -1,4 +1,4 @@
package de.ph87.office.web; package de.ph87.homeautomation.web;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;

View File

@ -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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;