REFACTOR: Property, Channel

This commit is contained in:
Patrick Haßel 2021-11-05 19:45:51 +01:00
parent 875388e07c
commit 39d682b017
55 changed files with 623 additions and 737 deletions

View File

@ -48,9 +48,14 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.20</version> <version>1.18.22</version>
</dependency> </dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
</dependency>
<dependency> <dependency>
<groupId>com.github.calimero</groupId> <groupId>com.github.calimero</groupId>
<artifactId>calimero-core</artifactId> <artifactId>calimero-core</artifactId>

View File

@ -1,4 +1,4 @@
import {validateBooleanAllowNull, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull, validateStringNullToEmpty} from "../validators"; import {validateNumberNotNull, validateStringNotEmptyNotNull} from "../validators";
import {Property} from "../property/property.service"; import {Property} from "../property/property.service";
export abstract class Device { export abstract class Device {
@ -19,18 +19,14 @@ export abstract class Device {
validateNumberNotNull(json['id']), validateNumberNotNull(json['id']),
validateStringNotEmptyNotNull(json['title']), validateStringNotEmptyNotNull(json['title']),
type, type,
validateStringNullToEmpty(json['getState']), Property.fromJsonAllowNull(json['stateProperty']),
validateStringNullToEmpty(json['setState']),
validateBooleanAllowNull(json['state']),
); );
case "DeviceShutter": case "DeviceShutter":
return new DeviceShutter( return new DeviceShutter(
validateNumberNotNull(json['id']), validateNumberNotNull(json['id']),
validateStringNotEmptyNotNull(json['title']), validateStringNotEmptyNotNull(json['title']),
type, type,
validateStringNullToEmpty(json['getPercent']), Property.fromJsonAllowNull(json['positionProperty']),
validateStringNullToEmpty(json['setPercent']),
validateNumberAllowNull(json['percent']),
); );
} }
throw new Error("No such type: " + type); throw new Error("No such type: " + type);
@ -54,16 +50,14 @@ export class DeviceSwitch extends Device {
id: number, id: number,
title: string, title: string,
type: string, type: string,
public getState: string, public stateProperty: Property | null,
public setState: string,
public state: boolean | null,
) { ) {
super(id, title, type); super(id, title, type);
} }
updateProperty(property: Property): void { updateProperty(property: Property): void {
if (this.getState === property.name) { if (this.stateProperty?.name === property.name) {
this.state = property.booleanValue; this.stateProperty = property;
} }
} }
@ -75,16 +69,14 @@ export class DeviceShutter extends Device {
id: number, id: number,
title: string, title: string,
type: string, type: string,
public getPercent: string, public positionProperty: Property | null,
public setPercent: string,
public percent: number | null,
) { ) {
super(id, title, type); super(id, title, type);
} }
updateProperty(property: Property): void { updateProperty(property: Property): void {
if (this.getPercent === property.name) { if (this.positionProperty?.name === property.name) {
this.percent = property.numberValue; this.positionProperty = property;
} }
} }

View File

@ -1,6 +1,6 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {ApiService, NO_OP} from "../api.service"; import {ApiService, NO_OP} from "../api.service";
import {validateBooleanAllowNull, validateDateAllowNull, validateNumberAllowNull, validateStringNotEmptyNotNull} from "../validators"; import {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"; import {Update} from "../Update";
@ -10,10 +10,8 @@ export class Property {
constructor( constructor(
public name: string, public name: string,
public title: string, public title: string,
public propertyType: string, public value: number | null,
public booleanValue: boolean | null, public valueTimestamp: Date | null,
public numberValue: number | null,
public timestamp: Date | null,
) { ) {
// nothing // nothing
} }
@ -29,10 +27,8 @@ export class Property {
return new Property( return new Property(
validateStringNotEmptyNotNull(json['name']), validateStringNotEmptyNotNull(json['name']),
validateStringNotEmptyNotNull(json['title']), validateStringNotEmptyNotNull(json['title']),
validateStringNotEmptyNotNull(json['propertyType']), validateNumberAllowNull(json['value']),
validateBooleanAllowNull(json['booleanValue']), validateDateAllowNull(json['valueTimestamp']),
validateNumberAllowNull(json['numberValue']),
validateDateAllowNull(json['timestamp']),
); );
} }
@ -69,8 +65,8 @@ export class PropertyService implements ISearchService {
this.api.postReturnList("property/searchLike", term, KeyValuePair.fromJson, next, error); this.api.postReturnList("property/searchLike", term, KeyValuePair.fromJson, next, error);
} }
set(name: string, value: number, next: () => void = NO_OP, error: (error: any) => void = NO_OP): void { set(property: Property, value: number, next: () => void = NO_OP, error: (error: any) => void = NO_OP): void {
this.api.postReturnNone("property/set", {name: name, value: value}, next, error) this.api.postReturnNone("property/set", {name: property.name, value: value}, next, error)
} }
} }

View File

@ -20,19 +20,19 @@
<fa-icon [icon]="faEdit"></fa-icon> <fa-icon [icon]="faEdit"></fa-icon>
</div> </div>
<div class="controls"> <div class="controls">
<div class="control button" (click)="setShutterPercent(device, 0)"> <div class="control button" (click)="setShutterPosition(device, 0)">
<span class="center">Auf</span> <span class="center">Auf</span>
</div> </div>
<div class="control button" (click)="setShutterPercent(device, 40)"> <div class="control button" (click)="setShutterPosition(device, 40)">
<span class="center">50%</span> <span class="center">50%</span>
</div> </div>
<div class="control button" (click)="setShutterPercent(device, 75)"> <div class="control button" (click)="setShutterPosition(device, 75)">
<span class="center">90%</span> <span class="center">90%</span>
</div> </div>
<div class="control button" (click)="setShutterPercent(device, 85)"> <div class="control button" (click)="setShutterPosition(device, 85)">
<span class="center">Schlitze</span> <span class="center">Schlitze</span>
</div> </div>
<div class="control button" (click)="setShutterPercent(device, 100)"> <div class="control button" (click)="setShutterPosition(device, 100)">
<span class="center">Zu</span> <span class="center">Zu</span>
</div> </div>
</div> </div>

View File

@ -41,18 +41,18 @@ export class DeviceListComponent implements OnInit {
setSwitchState(d: Device, value: boolean): void { setSwitchState(d: Device, value: boolean): void {
const device: DeviceSwitch = d as DeviceSwitch; const device: DeviceSwitch = d as DeviceSwitch;
if (!device.setState) { if (!device.stateProperty) {
throw new Error("Property 'setState' not set for: " + device); throw new Error("Property 'setState' not set for: " + device);
} }
this.propertyService.set(device.setState, value ? 1 : 0); this.propertyService.set(device.stateProperty, value ? 1 : 0);
} }
setShutterPercent(d: Device, value: number): void { setShutterPosition(d: Device, value: number): void {
const device: DeviceShutter = d as DeviceShutter; const device: DeviceShutter = d as DeviceShutter;
if (!device.setPercent) { if (!device.positionProperty) {
throw new Error("Property 'setPercent' not set for: " + device); throw new Error("Property 'setPosition' not set for: " + device);
} }
this.propertyService.set(device.setPercent, value); this.propertyService.set(device.positionProperty, value);
} }
create(): void { create(): void {
@ -75,16 +75,16 @@ export class DeviceListComponent implements OnInit {
} }
getSwitchClassList(device: Device): object { getSwitchClassList(device: Device): object {
const value: boolean | null | undefined = (device as DeviceSwitch).state; const value: number | null | undefined = (device as DeviceSwitch).stateProperty?.value;
return { return {
switchOn: value === true, switchOn: value === 1,
switchOff: value === false, switchOff: value === 0,
switchUnknown: value === null || value === undefined, switchUnknown: value === null || value === undefined,
}; };
} }
getShutterClassList(device: Device): object { getShutterClassList(device: Device): object {
const value: number | null | undefined = (device as DeviceShutter).percent; const value: number | null | undefined = (device as DeviceShutter).positionProperty?.value;
return { return {
shutterOpen: value === 0, shutterOpen: value === 0,
shutterBetween: value !== null && value !== undefined && value > 0 && value < 100, shutterBetween: value !== null && value !== undefined && value > 0 && value < 100,

View File

@ -11,15 +11,9 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Zustand Lesen</th> <th>Eigenschaft</th>
<td> <td>
<app-search [searchService]="propertyService" [initial]="deviceSwitch.getState" [showKey]="true" (valueChange)="setDeviceSwitch('getState', $event)"></app-search> <app-search [searchService]="propertyService" [initial]="deviceSwitch.stateProperty?.name" [showKey]="true" (valueChange)="setDeviceSwitch('stateProperty', $event)"></app-search>
</td>
</tr>
<tr>
<th>Zustand Schreiben</th>
<td>
<app-search [searchService]="propertyService" [initial]="deviceSwitch.setState" [showKey]="true" (valueChange)="setDeviceSwitch('setState', $event)"></app-search>
</td> </td>
</tr> </tr>
</table> </table>
@ -34,15 +28,9 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Position Lesen</th> <th>Eigenschaft</th>
<td> <td>
<app-search [searchService]="propertyService" [initial]="deviceShutter.getPercent" [showKey]="true" (valueChange)="setDeviceShutter('getPercent', $event)"></app-search> <app-search [searchService]="propertyService" [initial]="deviceShutter.positionProperty?.name" [showKey]="true" (valueChange)="setDeviceShutter('positionProperty', $event)"></app-search>
</td>
</tr>
<tr>
<th>Position Schreiben</th>
<td>
<app-search [searchService]="propertyService" [initial]="deviceShutter.setPercent" [showKey]="true" (valueChange)="setDeviceShutter('setPercent', $event)"></app-search>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -18,7 +18,7 @@
</td> </td>
<td colspan="9"> <td colspan="9">
<select [(ngModel)]="schedule.propertyType" (ngModelChange)="set(null,'propertyType', schedule.propertyType)"> <select [(ngModel)]="schedule.propertyType" (ngModelChange)="set(null,'propertyType', schedule.propertyType)">
<option value="ON_OFF">An / Aus</option> <option value="SWITCH">An / Aus</option>
<option value="PERCENT">Prozent</option> <option value="PERCENT">Prozent</option>
<option value="SHUTTER">Rollladen</option> <option value="SHUTTER">Rollladen</option>
<option value="SCENE">Szene</option> <option value="SCENE">Szene</option>
@ -137,7 +137,7 @@
<td class="empty last"></td> <td class="empty last"></td>
</ng-container> </ng-container>
<td *ngIf="schedule.propertyType === 'ON_OFF'" [class.true]="entry.value" [class.false]="!entry.value" (click)="set(entry, 'value', entry.value > 0 ? 0 : 1)"> <td *ngIf="schedule.propertyType === 'SWITCH'" [class.true]="entry.value" [class.false]="!entry.value" (click)="set(entry, 'value', entry.value > 0 ? 0 : 1)">
{{entry.value ? "An" : "Aus"}} {{entry.value ? "An" : "Aus"}}
</td> </td>
<td *ngIf="schedule.propertyType === 'PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100"> <td *ngIf="schedule.propertyType === 'PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">

View File

@ -24,7 +24,7 @@ export class SearchComponent<T> implements OnInit {
searchService!: ISearchService; searchService!: ISearchService;
@Input() @Input()
initial!: string; initial?: string;
@Input() @Input()
showKey: boolean = false; showKey: boolean = false;
@ -65,7 +65,7 @@ export class SearchComponent<T> implements OnInit {
} }
start(): void { start(): void {
this.term = this.initial; this.term = this.initial || "";
if (this.resultList && this.input) { if (this.resultList && this.input) {
this.resultList.style.left = this.input.style.left; this.resultList.style.left = this.input.style.left;
} }

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation; package de.ph87.homeautomation;
import de.ph87.homeautomation.knx.group.KnxGroupImportService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -12,12 +13,15 @@ public class BackendApplication {
private final DemoDataService demoDataService; private final DemoDataService demoDataService;
private final KnxGroupImportService knxGroupImportService;
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(BackendApplication.class); SpringApplication.run(BackendApplication.class);
} }
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
knxGroupImportService.importGroups();
demoDataService.insertDemoData(); demoDataService.insertDemoData();
} }

View File

@ -1,15 +1,15 @@
package de.ph87.homeautomation; package de.ph87.homeautomation;
import com.luckycatlabs.sunrisesunset.Zenith; import com.luckycatlabs.sunrisesunset.Zenith;
import de.ph87.homeautomation.channel.Channel;
import de.ph87.homeautomation.device.DeviceRepository; import de.ph87.homeautomation.device.DeviceRepository;
import de.ph87.homeautomation.device.DeviceWriteService; import de.ph87.homeautomation.device.DeviceWriteService;
import de.ph87.homeautomation.device.devices.DeviceDto;
import de.ph87.homeautomation.knx.group.KnxGroup; import de.ph87.homeautomation.knx.group.KnxGroup;
import de.ph87.homeautomation.knx.group.KnxGroupRepository; import de.ph87.homeautomation.knx.group.KnxGroupReadService;
import de.ph87.homeautomation.knx.group.KnxGroupWriteService;
import de.ph87.homeautomation.property.Property; import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.property.PropertyDto; import de.ph87.homeautomation.property.PropertyRepository;
import de.ph87.homeautomation.schedule.Schedule; import de.ph87.homeautomation.schedule.Schedule;
import de.ph87.homeautomation.schedule.SchedulePropertyType;
import de.ph87.homeautomation.schedule.ScheduleRepository; import de.ph87.homeautomation.schedule.ScheduleRepository;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry; import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryType; import de.ph87.homeautomation.schedule.entry.ScheduleEntryType;
@ -17,7 +17,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
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 java.time.ZonedDateTime; import java.time.ZonedDateTime;
@ -32,52 +31,39 @@ public class DemoDataService {
private static final Zenith BETWEEN_OFFICIAL_AND_CIVIL = new Zenith(93.0); private static final Zenith BETWEEN_OFFICIAL_AND_CIVIL = new Zenith(93.0);
private final KnxGroupWriteService knxGroupWriteService;
private final ScheduleRepository scheduleRepository; private final ScheduleRepository scheduleRepository;
private final KnxGroupRepository knxGroupRepository;
private final DeviceWriteService deviceWriteService; private final DeviceWriteService deviceWriteService;
private final DeviceRepository deviceRepository; private final DeviceRepository deviceRepository;
private final PropertyRepository propertyRepository;
private final KnxGroupReadService knxGroupReadService;
public void insertDemoData() { public void insertDemoData() {
final KnxGroup eg_flur_licht_schalten = createKnxGroupIfNotExists("EG Flur Licht Schalten", 0, 5, 14, "1.001", false, false); final Property ambiente_eg = createProperty("ambiente.eg", "Ambiente EG", knx(0, 3, 81), knx(0, 3, 80));
final Property ambiente_og = createProperty("ambiente.og", "Ambiente OG", knx(0, 6, 2), knx(0, 6, 3));
final KnxGroup ambiente_eg_status = createKnxGroupIfNotExists("Ambiente EG Status", 0, 3, 81, "1.001", true, false); final Property bad_licht = createProperty("bad.licht", "Bad Licht", knx(0, 5, 19), knx(0, 3, 73));
final KnxGroup ambiente_eg_schalten = createKnxGroupIfNotExists("Ambiente EG Schalten", 0, 3, 80, "1.001", false, false); final Property bad_licht_mitte = createProperty("bad.licht.mitte", "Bad Licht Mitte", knx(0, 3, 30), knx(0, 3, 29));
final Property flur_eg_licht = createProperty("flur.eg.licht", "Flur EG Licht", knx(0, 4, 8), knx(0, 5, 14));
final KnxGroup ambiente_og_status = createKnxGroupIfNotExists("Ambiente OG Status", 0, 6, 2, "1.001", true, false); final Property wohnzimmer_rollladen = createProperty("wohnzimmer.rollladen", "Wohnzimmer Rollladen", null, knx(0, 4, 24));
final KnxGroup ambiente_og_schalten = createKnxGroupIfNotExists("Ambiente OG Schalten", 0, 6, 3, "1.001", false, false); final Property schlafzimmer_rollladen = createProperty("schlafzimmer_rollladen", "Schlafzimmer Rollladen", null, knx(0, 3, 3));
final Property flur_og_rollladen = createProperty("flur_og_rollladen", "Flur OG Rollladen", null, knx(0, 5, 13));
final KnxGroup wohnzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Wohnzimmer Rollladen Position Anfahren", 0, 4, 24, "5.001", false, false); final Property helligkeit = createProperty("helligkeit", "Helligkeit", knx(0, 5, 6), null);
final Property szene_haus = createProperty("szene_haus", "Szene Haus ", null, knx(0, 0, 21));
final KnxGroup schlafzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Schlafzimmer Rollladen Position Anfahren", 0, 3, 3, "5.001", false, false);
final KnxGroup flur_og_rollladen_position_anfahren = createKnxGroupIfNotExists("Flur Rollladen Position Anfahren", 0, 5, 13, "5.001", false, false);
final KnxGroup bad_licht_status = createKnxGroupIfNotExists("Bad Licht Status", 0, 5, 19, "1.001", true, false);
final KnxGroup bad_licht_schalten = createKnxGroupIfNotExists("Bad Licht Schalten", 0, 3, 73, "1.001", false, false);
final KnxGroup bad_licht_mitte_status = createKnxGroupIfNotExists("Bad Licht Mitte Status", 0, 3, 30, "1.001", true, false);
final KnxGroup bad_licht_mitte_schalten = createKnxGroupIfNotExists("Bad Licht Mitte Schalten", 0, 3, 29, "1.001", false, false);
final KnxGroup helligkeit = createKnxGroupIfNotExists("Helligkeit", 0, 5, 6, "9.004", false, true);
final KnxGroup szene_haus = createKnxGroupIfNotExists("Szene Haus", 0, 0, 21, "17.004", false, true);
if (deviceRepository.count() == 0) { if (deviceRepository.count() == 0) {
createDeviceSwitch("Ambiente EG", ambiente_eg_status, ambiente_eg_schalten); deviceWriteService.createDeviceSwitch("Ambiente EG", ambiente_eg);
createDeviceSwitch("Ambiente OG", ambiente_og_status, ambiente_og_schalten); deviceWriteService.createDeviceSwitch("Ambiente OG", ambiente_og);
createDeviceSwitch("Bad Licht", bad_licht_status, bad_licht_schalten); deviceWriteService.createDeviceSwitch("Bad Licht", bad_licht);
createDeviceShutter("Wohnzimmer Rollladen", null, wohnzimmer_rollladen_position_anfahren); deviceWriteService.createDeviceShutter("Wohnzimmer Rollladen", wohnzimmer_rollladen);
createDeviceShutter("Schlafzimmer Rollladen", null, schlafzimmer_rollladen_position_anfahren); deviceWriteService.createDeviceShutter("Schlafzimmer Rollladen", schlafzimmer_rollladen);
createDeviceShutter("Flur Rollladen", null, flur_og_rollladen_position_anfahren); deviceWriteService.createDeviceShutter("Flur Rollladen", flur_og_rollladen);
} }
if (scheduleRepository.count() == 0) { if (scheduleRepository.count() == 0) {
final Schedule scheduleEgFlurLicht = createSchedule(true, "EG Flur Licht", eg_flur_licht_schalten); final Schedule scheduleEgFlurLicht = createSchedule(true, "EG Flur Licht", flur_eg_licht, SchedulePropertyType.SWITCH);
createTime(scheduleEgFlurLicht, true, 1, 0, 0, MIN30, true); createTime(scheduleEgFlurLicht, true, 1, 0, 0, MIN30, true);
createTime(scheduleEgFlurLicht, true, 2, 0, 0, MIN30, false); createTime(scheduleEgFlurLicht, true, 2, 0, 0, MIN30, false);
createTime(scheduleEgFlurLicht, true, 7, 30, 0, MIN30, true); createTime(scheduleEgFlurLicht, true, 7, 30, 0, MIN30, true);
@ -88,36 +74,36 @@ public class DemoDataService {
createTime(scheduleEgFlurLicht, true, 20, 0, 0, MIN30, false); createTime(scheduleEgFlurLicht, true, 20, 0, 0, MIN30, false);
scheduleRepository.save(scheduleEgFlurLicht); scheduleRepository.save(scheduleEgFlurLicht);
final Schedule scheduleEgAmbiente = createSchedule(false, "Ambiente EG", ambiente_eg_schalten); final Schedule scheduleEgAmbiente = createSchedule(false, "Ambiente EG", ambiente_eg, SchedulePropertyType.SWITCH);
createTime(scheduleEgAmbiente, true, 7, 15, 0, MIN30, true); createTime(scheduleEgAmbiente, true, 7, 15, 0, MIN30, true);
createTime(scheduleEgAmbiente, true, 9, 30, 0, MIN30, false); createTime(scheduleEgAmbiente, true, 9, 30, 0, MIN30, false);
createSunset(scheduleEgAmbiente, true, Zenith.OFFICIAL, MIN30, true); createSunset(scheduleEgAmbiente, true, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleEgAmbiente, true, Zenith.ASTRONOMICAL, MIN30, false); createSunset(scheduleEgAmbiente, true, Zenith.ASTRONOMICAL, MIN30, false);
scheduleRepository.save(scheduleEgAmbiente); scheduleRepository.save(scheduleEgAmbiente);
final Schedule scheduleOgAmbiente = createSchedule(false, "Ambiente OG", ambiente_og_schalten); final Schedule scheduleOgAmbiente = createSchedule(false, "Ambiente OG", ambiente_og, SchedulePropertyType.SWITCH);
createTime(scheduleOgAmbiente, true, 7, 15, 0, MIN30, true); createTime(scheduleOgAmbiente, true, 7, 15, 0, MIN30, true);
createTime(scheduleOgAmbiente, true, 9, 30, 0, MIN30, false); createTime(scheduleOgAmbiente, true, 9, 30, 0, MIN30, false);
createSunset(scheduleOgAmbiente, true, Zenith.OFFICIAL, MIN30, true); createSunset(scheduleOgAmbiente, true, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleOgAmbiente, true, Zenith.ASTRONOMICAL, MIN30, false); createSunset(scheduleOgAmbiente, true, Zenith.ASTRONOMICAL, MIN30, false);
scheduleRepository.save(scheduleOgAmbiente); scheduleRepository.save(scheduleOgAmbiente);
final Schedule scheduleWohnzimmerRollladen = createSchedule(true, "Rollläden Wohnzimmer", wohnzimmer_rollladen_position_anfahren); final Schedule scheduleWohnzimmerRollladen = createSchedule(true, "Rollläden Wohnzimmer", wohnzimmer_rollladen, SchedulePropertyType.SHUTTER);
createSunrise(scheduleWohnzimmerRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0); createSunrise(scheduleWohnzimmerRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0);
createSunset(scheduleWohnzimmerRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100); createSunset(scheduleWohnzimmerRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
scheduleRepository.save(scheduleWohnzimmerRollladen); scheduleRepository.save(scheduleWohnzimmerRollladen);
final Schedule scheduleSchlafzimmerRollladen = createSchedule(true, "Rollläden Schlafzimmer", schlafzimmer_rollladen_position_anfahren); final Schedule scheduleSchlafzimmerRollladen = createSchedule(true, "Rollläden Schlafzimmer", schlafzimmer_rollladen, SchedulePropertyType.SHUTTER);
createTime(scheduleSchlafzimmerRollladen, true, 7, 0, 0, 0, 0); createTime(scheduleSchlafzimmerRollladen, true, 7, 0, 0, 0, 0);
createSunset(scheduleSchlafzimmerRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100); createSunset(scheduleSchlafzimmerRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
scheduleRepository.save(scheduleSchlafzimmerRollladen); scheduleRepository.save(scheduleSchlafzimmerRollladen);
final Schedule scheduleFlurRollladen = createSchedule(true, "Rollladen Flur", flur_og_rollladen_position_anfahren); final Schedule scheduleFlurRollladen = createSchedule(true, "Rollladen Flur", flur_og_rollladen, SchedulePropertyType.SHUTTER);
createSunrise(scheduleFlurRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0); createSunrise(scheduleFlurRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0);
createSunset(scheduleFlurRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100); createSunset(scheduleFlurRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
scheduleRepository.save(scheduleFlurRollladen); scheduleRepository.save(scheduleFlurRollladen);
final Schedule scheduleBadLichtMitte = createSchedule(false, "Bad Licht Mitte", bad_licht_mitte_schalten); final Schedule scheduleBadLichtMitte = createSchedule(false, "Bad Licht Mitte", bad_licht_mitte, SchedulePropertyType.SWITCH);
createTime(scheduleBadLichtMitte, true, 10, 30, 0, MIN30, true); createTime(scheduleBadLichtMitte, true, 10, 30, 0, MIN30, true);
createTime(scheduleBadLichtMitte, true, 11, 30, 0, MIN30, false); createTime(scheduleBadLichtMitte, true, 11, 30, 0, MIN30, false);
createTime(scheduleBadLichtMitte, true, 15, 30, 0, MIN30, true); createTime(scheduleBadLichtMitte, true, 15, 30, 0, MIN30, true);
@ -128,7 +114,7 @@ public class DemoDataService {
createTime(scheduleBadLichtMitte, true, 1, 0, 0, MIN30, false); createTime(scheduleBadLichtMitte, true, 1, 0, 0, MIN30, false);
scheduleRepository.save(scheduleBadLichtMitte); scheduleRepository.save(scheduleBadLichtMitte);
final Schedule scheduleSzeneHaus = createSchedule(true, "Dekoration", szene_haus); final Schedule scheduleSzeneHaus = createSchedule(true, "Dekoration", szene_haus, SchedulePropertyType.SCENE);
createTime(scheduleSzeneHaus, true, 6, 0, 0, 0, 31); createTime(scheduleSzeneHaus, true, 6, 0, 0, 0, 31);
createTime(scheduleSzeneHaus, true, 8, 30, 0, 0, 30); createTime(scheduleSzeneHaus, true, 8, 30, 0, 0, 30);
createSunset(scheduleSzeneHaus, true, Zenith.OFFICIAL, 0, 31); createSunset(scheduleSzeneHaus, true, Zenith.OFFICIAL, 0, 31);
@ -137,28 +123,27 @@ public class DemoDataService {
} }
} }
private DeviceDto createDeviceSwitch(final String title, final Property getPercent, final Property setPercent) { private KnxGroup knx(final int main, final int mid, final int sub) {
return deviceWriteService.createDeviceSwitch(title, getPercent, setPercent); return knxGroupReadService.getByAddress(main, mid, sub);
} }
private DeviceDto createDeviceShutter(final String title, final Property getPercent, final Property setPercent) { private Property createProperty(final String name, final String title, final Channel readChannel, final Channel writeChannel) {
return deviceWriteService.createDeviceShutter(title, getPercent, setPercent); final Property property = propertyRepository.findByName(name).orElseGet(() -> propertyRepository.save(new Property(name)));
property.setTitle(title);
property.setReadChannel(readChannel);
property.setWriteChannel(writeChannel);
return property;
} }
private Schedule createSchedule(final boolean enabled, final String title, final PropertyDto propertyDto) { private Schedule createSchedule(final boolean enabled, final String title, final Property property, final SchedulePropertyType propertyType) {
final Schedule schedule = new Schedule(); final Schedule schedule = new Schedule();
schedule.setEnabled(enabled); schedule.setEnabled(enabled);
schedule.setTitle(title); schedule.setTitle(title);
schedule.setPropertyName(propertyDto.getName()); schedule.setProperty(property);
schedule.setPropertyType(propertyDto.getPropertyType()); schedule.setPropertyType(propertyType);
return schedule; return schedule;
} }
private KnxGroup createKnxGroupIfNotExists(final String name, final int main, final int mid, final int sub, final String dpt, final boolean readable, final boolean multiGroup) {
final GroupAddress address = new GroupAddress(main, mid, sub);
return knxGroupRepository.findByAddressRaw(address.getRawAddress()).orElseGet(() -> knxGroupWriteService.create(name, address, dpt, readable, multiGroup));
}
private ScheduleEntry createRelative(final Schedule schedule, final boolean enabled, final int inSeconds, final int fuzzySeconds, final Object value) { private ScheduleEntry createRelative(final Schedule schedule, final boolean enabled, final int inSeconds, final int fuzzySeconds, final Object value) {
final ZonedDateTime now = ZonedDateTime.now().plusSeconds(inSeconds).withNano(0); final ZonedDateTime now = ZonedDateTime.now().plusSeconds(inSeconds).withNano(0);
return createTime(schedule, enabled, now.getHour(), now.getMinute(), now.getSecond(), fuzzySeconds, value); return createTime(schedule, enabled, now.getHour(), now.getMinute(), now.getSecond(), fuzzySeconds, value);

View File

@ -0,0 +1,27 @@
package de.ph87.homeautomation.channel;
import lombok.*;
import javax.persistence.*;
import java.time.ZonedDateTime;
@Getter
@Setter
@ToString
@NoArgsConstructor
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Channel {
@Id
@GeneratedValue
@Setter(AccessLevel.NONE)
private Long id;
public abstract Class<? extends IChannelOwner> getChannelOwnerClass();
public abstract Double getValue();
public abstract ZonedDateTime getValueTimestamp();
}

View File

@ -0,0 +1,14 @@
package de.ph87.homeautomation.channel;
import lombok.Data;
@Data
public class ChannelChangedEvent {
private final long channelId;
public ChannelChangedEvent(final Channel channel) {
this.channelId = channel.getId();
}
}

View File

@ -0,0 +1,36 @@
package de.ph87.homeautomation.channel;
import de.ph87.homeautomation.property.Property;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class ChannelService {
private final List<IChannelOwner> channelOwners;
public Optional<IChannelOwner> findByChannel(final Channel channel) {
return channelOwners.stream().filter(owner -> channel.getChannelOwnerClass().isInstance(owner)).findFirst();
}
public IChannelOwner getByChannel(final Channel channel) {
return findByChannel(channel).orElseThrow(RuntimeException::new);
}
public void write(final Property property, final double value) {
final Channel channel = property.getWriteChannel();
if (channel == null) {
return;
}
getByChannel(channel).write(property, value);
}
}

View File

@ -0,0 +1,9 @@
package de.ph87.homeautomation.channel;
import de.ph87.homeautomation.property.Property;
public interface IChannelOwner {
void write(final Property property, final double value);
}

View File

@ -2,13 +2,14 @@ package de.ph87.homeautomation.device;
import de.ph87.homeautomation.device.devices.Device; import de.ph87.homeautomation.device.devices.Device;
import de.ph87.homeautomation.device.devices.DeviceDto; import de.ph87.homeautomation.device.devices.DeviceDto;
import de.ph87.homeautomation.device.devices.DeviceShutter; import de.ph87.homeautomation.property.PropertyReadService;
import de.ph87.homeautomation.device.devices.DeviceSwitch;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import static de.ph87.homeautomation.shared.Helpers.mapIfNotNull;
@RestController @RestController
@RequestMapping("device") @RequestMapping("device")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -18,6 +19,8 @@ public class DeviceController {
private final DeviceWriteService deviceWriteService; private final DeviceWriteService deviceWriteService;
private final PropertyReadService propertyReadService;
@GetMapping("findAll") @GetMapping("findAll")
public List<DeviceDto> findAll() { public List<DeviceDto> findAll() {
return deviceReadService.findAll(); return deviceReadService.findAll();
@ -48,24 +51,14 @@ public class DeviceController {
return deviceWriteService.set(id, Device::setTitle, title); return deviceWriteService.set(id, Device::setTitle, title);
} }
@PostMapping("set/{id}/DeviceSwitch/setState") @PostMapping("set/{id}/DeviceSwitch/stateProperty")
public DeviceDto setDeviceSwitchSetState(@PathVariable final long id, @RequestBody(required = false) final String name) { public DeviceDto setDeviceSwitchStateProperty(@PathVariable final long id, @RequestBody(required = false) final String name) {
return deviceWriteService.setDeviceSwitch(id, DeviceSwitch::setSetState, name); return deviceWriteService.setDeviceSwitch(id, (device, v) -> device.setStateProperty(mapIfNotNull(v, propertyReadService::getByName)), name);
} }
@PostMapping("set/{id}/DeviceSwitch/getState") @PostMapping("set/{id}/DeviceShutter/positionProperty")
public DeviceDto setDeviceSwitchGetState(@PathVariable final long id, @RequestBody(required = false) final String name) { public DeviceDto setDeviceShutterPositionProperty(@PathVariable final long id, @RequestBody(required = false) final String name) {
return deviceWriteService.setDeviceSwitch(id, DeviceSwitch::setGetState, name); return deviceWriteService.setDeviceShutter(id, (device, v) -> device.setPositionProperty(mapIfNotNull(v, propertyReadService::getByName)), name);
}
@PostMapping("set/{id}/DeviceShutter/setPercent")
public DeviceDto setDeviceShutterSetPercent(@PathVariable final long id, @RequestBody(required = false) final String name) {
return deviceWriteService.setDeviceShutter(id, DeviceShutter::setSetPercent, name);
}
@PostMapping("set/{id}/DeviceShutter/getPercent")
public DeviceDto setDeviceShutterGetPercent(@PathVariable final long id, @RequestBody(required = false) final String name) {
return deviceWriteService.setDeviceShutter(id, DeviceShutter::setGetPercent, name);
} }
} }

View File

@ -1,7 +1,7 @@
package de.ph87.homeautomation.device; package de.ph87.homeautomation.device;
import de.ph87.homeautomation.device.devices.*; import de.ph87.homeautomation.device.devices.*;
import de.ph87.homeautomation.property.Property; import de.ph87.homeautomation.property.PropertyMapper;
import de.ph87.homeautomation.web.NotFoundException; import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -21,6 +21,8 @@ public class DeviceReadService {
private final DeviceRepository deviceRepository; private final DeviceRepository deviceRepository;
private final PropertyMapper propertyMapper;
public List<DeviceDto> findAll() { public List<DeviceDto> findAll() {
return deviceRepository.findAll().stream().map(this::toDto).collect(Collectors.toList()); return deviceRepository.findAll().stream().map(this::toDto).collect(Collectors.toList());
} }
@ -28,14 +30,12 @@ public class DeviceReadService {
public DeviceDto toDto(final Device device) { public DeviceDto toDto(final Device device) {
if (device instanceof DeviceSwitch) { if (device instanceof DeviceSwitch) {
final DeviceSwitch deviceSwitch = (DeviceSwitch) device; final DeviceSwitch deviceSwitch = (DeviceSwitch) device;
final Boolean state = mapIfNotNull(deviceSwitch.getGetState(), Property::getBoolean); return new DeviceSwitchDto(deviceSwitch, mapIfNotNull(deviceSwitch.getStateProperty(), propertyMapper::toDto));
return new DeviceSwitchDto(deviceSwitch, mapIfNotNull(deviceSwitch.getGetState(), Property::getName), mapIfNotNull(deviceSwitch.getSetState(), Property::getName), state);
} else if (device instanceof DeviceShutter) { } else if (device instanceof DeviceShutter) {
final DeviceShutter deviceShutter = (DeviceShutter) device; final DeviceShutter deviceShutter = (DeviceShutter) device;
final Double percent = mapIfNotNull(deviceShutter.getGetPercent(), Property::getPercent); return new DeviceShutterDto(deviceShutter, mapIfNotNull(deviceShutter.getPositionProperty(), propertyMapper::toDto));
return new DeviceShutterDto(deviceShutter, mapIfNotNull(deviceShutter.getGetPercent(), Property::getName), mapIfNotNull(deviceShutter.getSetPercent(), Property::getName), percent);
} }
throw new RuntimeException("Not implemented: toDto(" + device + ")"); throw new RuntimeException();
} }
public Device getById(final long id) { public Device getById(final long id) {

View File

@ -5,7 +5,7 @@ import de.ph87.homeautomation.device.devices.DeviceDto;
import de.ph87.homeautomation.device.devices.DeviceShutter; import de.ph87.homeautomation.device.devices.DeviceShutter;
import de.ph87.homeautomation.device.devices.DeviceSwitch; import de.ph87.homeautomation.device.devices.DeviceSwitch;
import de.ph87.homeautomation.property.Property; import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.property.PropertyService; import de.ph87.homeautomation.property.PropertyWriteService;
import de.ph87.homeautomation.schedule.ScheduleWriteService; import de.ph87.homeautomation.schedule.ScheduleWriteService;
import de.ph87.homeautomation.web.BadRequestException; import de.ph87.homeautomation.web.BadRequestException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -23,51 +23,26 @@ public class DeviceWriteService {
private final DeviceRepository deviceRepository; private final DeviceRepository deviceRepository;
private final PropertyService propertyService; private final PropertyWriteService propertyWriteService;
private final DeviceReadService deviceReadService; private final DeviceReadService deviceReadService;
public DeviceDto createDeviceSwitch(final String title, final Property getStatePropertyName, final Property setStatePropertyName) { public DeviceDto createDeviceSwitch(final String title, final Property stateProperty) {
final DeviceSwitch deviceSwitch = new DeviceSwitch(); final DeviceSwitch deviceSwitch = new DeviceSwitch();
deviceSwitch.setTitle(title); deviceSwitch.setTitle(title);
deviceSwitch.setGetState(getStatePropertyName); deviceSwitch.setStateProperty(stateProperty);
deviceSwitch.setSetState(setStatePropertyName);
deviceRepository.save(deviceSwitch); deviceRepository.save(deviceSwitch);
return deviceReadService.toDto(deviceSwitch); return deviceReadService.toDto(deviceSwitch);
} }
public DeviceDto createDeviceShutter(final String title, final Property getPercentPropertyName, final Property setPercentPropertyName) { public DeviceDto createDeviceShutter(final String title, final Property positionProperty) {
final DeviceShutter deviceShutter = new DeviceShutter(); final DeviceShutter deviceShutter = new DeviceShutter();
deviceShutter.setTitle(title); deviceShutter.setTitle(title);
deviceShutter.setGetPercent(getPercentPropertyName); deviceShutter.setPositionProperty(positionProperty);
deviceShutter.setSetPercent(setPercentPropertyName);
deviceRepository.save(deviceShutter); deviceRepository.save(deviceShutter);
return deviceReadService.toDto(deviceShutter); return deviceReadService.toDto(deviceShutter);
} }
public void set(final DeviceSetDto dto) {
final Device device = deviceReadService.getById(dto.getId());
if (device instanceof DeviceSwitch) {
setSwitch((DeviceSwitch) device, dto.getProperty(), dto.getValue());
} else if (device instanceof DeviceShutter) {
setShutter((DeviceShutter) device, dto.getProperty(), dto.getValue());
}
}
private void setSwitch(final DeviceSwitch device, final String property, final double value) {
switch (property) {
case "switch":
propertyService.set(device.getSetState(), value);
}
}
private void setShutter(final DeviceShutter device, final String property, final double value) {
switch (property) {
case "percent":
propertyService.set(device.getSetPercent(), value);
}
}
public <T> DeviceDto set(final long id, final BiConsumer<Device, T> setter, final T value) { public <T> DeviceDto set(final long id, final BiConsumer<Device, T> setter, final T value) {
final Device device = deviceReadService.getById(id); final Device device = deviceReadService.getById(id);
setter.accept(device, value); setter.accept(device, value);
@ -99,9 +74,9 @@ public class DeviceWriteService {
public DeviceDto create(final String type) { public DeviceDto create(final String type) {
switch (type) { switch (type) {
case "DeviceSwitch": case "DeviceSwitch":
return createDeviceSwitch(generateUnusedTitle(), null, null); return createDeviceSwitch(generateUnusedTitle(), null);
case "DeviceShutter": case "DeviceShutter":
return createDeviceShutter(generateUnusedTitle(), null, null); return createDeviceShutter(generateUnusedTitle(), null);
} }
throw new RuntimeException("Not implemented type: " + type); throw new RuntimeException("Not implemented type: " + type);
} }

View File

@ -15,9 +15,6 @@ import javax.persistence.ManyToOne;
public class DeviceShutter extends Device { public class DeviceShutter extends Device {
@ManyToOne @ManyToOne
private Property getPercent; private Property positionProperty;
@ManyToOne
private Property setPercent;
} }

View File

@ -1,21 +1,16 @@
package de.ph87.homeautomation.device.devices; package de.ph87.homeautomation.device.devices;
import de.ph87.homeautomation.property.PropertyDto;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
public class DeviceShutterDto extends DeviceDto { public class DeviceShutterDto extends DeviceDto {
public final String getPercent; public final PropertyDto positionProperty;
public final String setPercent; public DeviceShutterDto(final DeviceShutter device, final PropertyDto positionProperty) {
public final Double percent;
public DeviceShutterDto(final DeviceShutter device, final String getPercent, final String setPercent, final Double percent) {
super(device); super(device);
this.getPercent = getPercent; this.positionProperty = positionProperty;
this.setPercent = setPercent;
this.percent = percent;
} }
} }

View File

@ -15,9 +15,6 @@ import javax.persistence.ManyToOne;
public class DeviceSwitch extends Device { public class DeviceSwitch extends Device {
@ManyToOne @ManyToOne
private Property setState; private Property stateProperty;
@ManyToOne
private Property getState;
} }

View File

@ -1,21 +1,16 @@
package de.ph87.homeautomation.device.devices; package de.ph87.homeautomation.device.devices;
import de.ph87.homeautomation.property.PropertyDto;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
public class DeviceSwitchDto extends DeviceDto { public class DeviceSwitchDto extends DeviceDto {
public final String getState; public final PropertyDto stateProperty;
public final String setState; public DeviceSwitchDto(final DeviceSwitch device, final PropertyDto stateProperty) {
public final Boolean state;
public DeviceSwitchDto(final DeviceSwitch device, final String getState, final String setState, final Boolean state) {
super(device); super(device);
this.getState = getState; this.stateProperty = stateProperty;
this.setState = setState;
this.state = state;
} }
} }

View File

@ -131,14 +131,14 @@ public class KnxThreadService extends AbstractThreadService implements NetworkLi
@Override @Override
public void groupReadResponse(final ProcessEvent processEvent) { public void groupReadResponse(final ProcessEvent processEvent) {
synchronized (databaseAccessLock) { synchronized (databaseAccessLock) {
knxGroupWriteService.updateIfExists(processEvent.getDestination().getRawAddress(), processEvent.getASDU(), processEvent.getSourceAddr()); knxGroupWriteService.setReceivedData(processEvent.getDestination(), processEvent.getASDU(), processEvent.getSourceAddr());
} }
} }
@Override @Override
public void groupWrite(final ProcessEvent processEvent) { public void groupWrite(final ProcessEvent processEvent) {
synchronized (databaseAccessLock) { synchronized (databaseAccessLock) {
knxGroupWriteService.updateIfExists(processEvent.getDestination().getRawAddress(), processEvent.getASDU(), processEvent.getSourceAddr()); knxGroupWriteService.setReceivedData(processEvent.getDestination(), processEvent.getASDU(), processEvent.getSourceAddr());
} }
} }

View File

@ -1,24 +1,21 @@
package de.ph87.homeautomation.knx.group; package de.ph87.homeautomation.knx.group;
import lombok.AccessLevel; import de.ph87.homeautomation.channel.Channel;
import lombok.Getter; import de.ph87.homeautomation.channel.IChannelOwner;
import lombok.Setter; import lombok.*;
import lombok.ToString;
import tuwien.auto.calimero.GroupAddress; import tuwien.auto.calimero.GroupAddress;
import javax.persistence.*; import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@Getter @Getter
@Setter @Setter
@ToString @ToString
@Entity @Entity
public class KnxGroup { @NoArgsConstructor
public class KnxGroup extends Channel {
@Id
@GeneratedValue
@Setter(AccessLevel.NONE)
private Long id;
@Setter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
@Column(nullable = false, unique = true) @Column(nullable = false, unique = true)
@ -28,45 +25,37 @@ public class KnxGroup {
@Column(nullable = false, unique = true) @Column(nullable = false, unique = true)
private String addressStr; private String addressStr;
@Setter(AccessLevel.NONE) private int dptMain;
@Column(nullable = false, unique = true)
private String propertyName;
@Column(nullable = false) private int dptSub;
private String dpt;
@Column(nullable = false) private String name;
private String title;
@Column(nullable = false) private String description;
@Enumerated(EnumType.STRING)
private PropertyType propertyType;
private byte[] value; private int puid;
private int lastDeviceAddressRaw; private boolean ets;
private String lastDeviceAddressString; @Embedded
private KnxTelegram lastTelegram;
private Boolean booleanValue; private Double value;
private Double numberValue;
private ZonedDateTime valueTimestamp; private ZonedDateTime valueTimestamp;
private byte[] sendValue;
private int readInterval;
@Column(nullable = false)
private boolean multiGroup;
@Embedded @Embedded
private KnxGroupLinkInfo read = new KnxGroupLinkInfo(); private KnxGroupLinkInfo read = new KnxGroupLinkInfo();
private byte[] sendValue;
@Embedded @Embedded
private KnxGroupLinkInfo send = new KnxGroupLinkInfo(); private KnxGroupLinkInfo send = new KnxGroupLinkInfo();
public KnxGroup(final GroupAddress address) {
setAddress(address);
}
public void setAddress(final int rawAddress) { public void setAddress(final int rawAddress) {
setAddress(new GroupAddress(rawAddress)); setAddress(new GroupAddress(rawAddress));
} }
@ -74,11 +63,19 @@ public class KnxGroup {
public void setAddress(final GroupAddress groupAddress) { public void setAddress(final GroupAddress groupAddress) {
this.addressRaw = groupAddress.getRawAddress(); this.addressRaw = groupAddress.getRawAddress();
this.addressStr = groupAddress.toString(); this.addressStr = groupAddress.toString();
this.propertyName = "knx.group." + groupAddress.getMainGroup() + "." + groupAddress.getMiddleGroup() + "." + groupAddress.getSubGroup8();
} }
public GroupAddress getAddress() { public GroupAddress getAddress() {
return new GroupAddress(addressRaw); return new GroupAddress(addressRaw);
} }
@Override
public Class<? extends IChannelOwner> getChannelOwnerClass() {
return KnxGroupChannelOwnerService.class;
}
public String getDpt() {
return String.format("%d.%03d", dptMain, dptSub);
}
} }

View File

@ -0,0 +1,27 @@
package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.channel.IChannelOwner;
import de.ph87.homeautomation.property.Property;
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 KnxGroupChannelOwnerService implements IChannelOwner {
private final KnxGroupWriteService knxGroupWriteService;
@Override
public void write(final Property property, final double value) {
if (!(property.getWriteChannel() instanceof KnxGroup)) {
throw new RuntimeException();
}
final KnxGroup knxGroup = (KnxGroup) property.getWriteChannel();
knxGroupWriteService.setSendValue(knxGroup, value);
}
}

View File

@ -1,27 +0,0 @@
package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.property.PropertyDto;
import lombok.Getter;
@Getter
public class KnxGroupDto {
public final long id;
public final PropertyDto property;
public final int addressRaw;
public final String addressStr;
public final String dpt;
public KnxGroupDto(final KnxGroup knxGroup) {
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();
}
}

View File

@ -1,9 +0,0 @@
package de.ph87.homeautomation.knx.group;
public class KnxGroupFormatException extends Exception {
public KnxGroupFormatException(final KnxGroup knxGroup, final double value, final String reason) {
super(String.format("Cannot use value %f (%s) to set KnxGroup: %s", value, reason, knxGroup));
}
}

View File

@ -0,0 +1,52 @@
package de.ph87.homeautomation.knx.group;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tuwien.auto.calimero.GroupAddress;
import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class KnxGroupImportService {
private final KnxGroupRepository knxGroupRepository;
public void importGroups() {
knxGroupRepository.findAll().forEach(knxGroup -> knxGroup.setEts(false));
try {
Jsoup.parse(new File("/home/patrick/Zuhause-ETS5/G"), "UTF-8").select("GA").forEach(this::importGroup);
} catch (IOException e) {
log.error("Failed to import KnxGroups: {}", e.toString());
}
}
private void importGroup(final Element ga) {
final GroupAddress address = new GroupAddress(Integer.parseInt(ga.attr("Address")));
final KnxGroup knxGroup = knxGroupRepository.findByAddressRaw(address.getRawAddress()).orElseGet(() -> knxGroupRepository.save(new KnxGroup(address)));
setDpt(knxGroup, ga.attr("DatapointType"));
knxGroup.setName(ga.attr("Name"));
knxGroup.setDescription(ga.attr("Description"));
knxGroup.setPuid(Integer.parseInt(ga.attr("Puid")));
knxGroup.setEts(true);
}
private void setDpt(final KnxGroup knxGroup, final String dptString) {
final Matcher matcher = Pattern.compile("^DPST-(?<main>\\d+)-(?<sub>\\d+)$").matcher(dptString);
if (!matcher.matches()) {
throw new RuntimeException();
}
knxGroup.setDptMain(Integer.parseInt(matcher.group("main")));
knxGroup.setDptSub(Integer.parseInt(matcher.group("sub")));
}
}

View File

@ -43,7 +43,7 @@ public class KnxGroupLinkService {
private boolean send(final ProcessCommunicatorImpl processCommunicator, final KnxGroup knxGroup) throws KNXException { private boolean send(final ProcessCommunicatorImpl processCommunicator, final KnxGroup knxGroup) throws KNXException {
try { try {
log.debug("Sending KnxGroup: {}", knxGroup); log.debug("Sending KnxGroup: {}", knxGroup);
processCommunicator.write(knxGroup.getAddress(), TranslatorTypes.createTranslator(knxGroup.getDpt(), knxGroup.getSendValue())); processCommunicator.write(knxGroup.getAddress(), TranslatorTypes.createTranslator(knxGroup.getDptMain(), knxGroup.getDptSub(), knxGroup.getSendValue()));
knxGroup.getSend().setErrorCount(0); knxGroup.getSend().setErrorCount(0);
knxGroup.getSend().setErrorMessage(null); knxGroup.getSend().setErrorMessage(null);
knxGroup.getSend().setNextTimestamp(null); knxGroup.getSend().setNextTimestamp(null);
@ -64,11 +64,7 @@ public class KnxGroupLinkService {
processCommunicator.read(createStateDP(knxGroup)); processCommunicator.read(createStateDP(knxGroup));
knxGroup.getRead().setErrorCount(0); knxGroup.getRead().setErrorCount(0);
knxGroup.getRead().setErrorMessage(null); knxGroup.getRead().setErrorMessage(null);
if (knxGroup.getReadInterval() > 0) { knxGroup.getRead().setNextTimestamp(null);
knxGroup.getRead().setNextTimestamp(align(knxGroup.getReadInterval()));
} else {
knxGroup.getRead().setNextTimestamp(null);
}
return true; return true;
} catch (KNXFormatException e) { } catch (KNXFormatException e) {
log.error(e.toString()); log.error(e.toString());
@ -88,8 +84,7 @@ public class KnxGroupLinkService {
private StateDP createStateDP(final KnxGroup knxGroup) { private StateDP createStateDP(final KnxGroup knxGroup) {
final GroupAddress groupAddress = knxGroup.getAddress(); final GroupAddress groupAddress = knxGroup.getAddress();
final int mainNumber = Integer.parseInt(knxGroup.getDpt().split("\\.", 2)[0]); return new StateDP(groupAddress, groupAddress.toString(), knxGroup.getDptMain(), knxGroup.getDpt());
return new StateDP(groupAddress, groupAddress.toString(), mainNumber, knxGroup.getDpt());
} }
public ZonedDateTime getNextTimestamp() { public ZonedDateTime getNextTimestamp() {

View File

@ -1,19 +0,0 @@
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

@ -1,90 +0,0 @@
package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.property.IPropertyOwner;
import de.ph87.homeautomation.property.PropertyDto;
import de.ph87.homeautomation.property.PropertySetException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tuwien.auto.calimero.GroupAddress;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static de.ph87.homeautomation.shared.Helpers.quoteOrNull;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class KnxGroupOwnerService implements IPropertyOwner {
@Getter
private final Pattern propertyNamePattern = Pattern.compile("^knx\\.group\\.(\\d+)\\.(\\d+)\\.(\\d+)$");
private final KnxGroupWriteService knxGroupWriteService;
private final KnxGroupRepository knxGroupRepository;
private final ApplicationEventPublisher eventPublisher;
private final KnxGroupMapperService knxGroupMapperService;
@Override
public void setProperty(final String propertyName, final double value) throws PropertySetException {
final GroupAddress groupAddress = parseGroupAddress(propertyName);
try {
if (knxGroupWriteService.setSendValue(groupAddress, value)) {
eventPublisher.publishEvent(new KnxThreadWakeUpEvent());
} else {
log.error("No such KnxGroup.address = {}", groupAddress);
}
} catch (KnxGroupFormatException e) {
throw new PropertySetException(propertyName, value, e);
}
}
@Override
public Boolean readBoolean(final String propertyName) {
return knxGroupRepository.findByAddressRaw(parseGroupAddress(propertyName).getRawAddress()).map(KnxGroup::getBooleanValue).orElse(null);
}
@Override
public Double readNumber(final String propertyName) {
return knxGroupRepository.findByAddressRaw(parseGroupAddress(propertyName).getRawAddress()).map(KnxGroup::getNumberValue).orElse(null);
}
@Override
public List<PropertyDto> findAllProperties() {
return knxGroupRepository.findAll().stream().map(knxGroupMapperService::toPropertyDto).collect(Collectors.toList());
}
@Override
public List<PropertyDto> findAllPropertiesLike(final String like) {
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) {
final Matcher matcher = propertyNamePattern.matcher(propertyName);
if (matcher.matches()) {
return new GroupAddress(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(3)));
}
throw new RuntimeException("Cannot parse GroupAddress from propertyName: " + quoteOrNull(propertyName));
}
}

View File

@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
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;
@Slf4j @Slf4j
@Service @Service
@ -11,4 +12,10 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class KnxGroupReadService { public class KnxGroupReadService {
private final KnxGroupRepository knxGroupRepository;
public KnxGroup getByAddress(final int main, final int mid, final int sub) {
return knxGroupRepository.findByAddressRaw(new GroupAddress(main, mid, sub).getRawAddress()).orElseThrow(RuntimeException::new);
}
} }

View File

@ -6,7 +6,7 @@ import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface KnxGroupRepository extends CrudRepository<KnxGroup, Long> { public interface KnxGroupRepository extends CrudRepository<KnxGroup, String> {
Optional<KnxGroup> findByAddressRaw(int rawAddress); Optional<KnxGroup> findByAddressRaw(int rawAddress);
@ -16,14 +16,8 @@ public interface KnxGroupRepository extends CrudRepository<KnxGroup, Long> {
Optional<KnxGroup> findFirstByRead_NextTimestampLessThanEqualOrderByRead_NextTimestampAsc(ZonedDateTime timestamp); Optional<KnxGroup> findFirstByRead_NextTimestampLessThanEqualOrderByRead_NextTimestampAsc(ZonedDateTime timestamp);
List<KnxGroup> findAllByRead_AbleTrue();
Optional<KnxGroup> findFirstByRead_NextTimestampNotNullOrderByRead_NextTimestampAsc(); Optional<KnxGroup> findFirstByRead_NextTimestampNotNullOrderByRead_NextTimestampAsc();
boolean existsByAddressRaw(int rawAddress); boolean existsByAddressRaw(int rawAddress);
List<KnxGroup> findAllByPropertyNameLikeIgnoreCaseOrTitleLikeIgnoreCase(String propertyNameLike, final String titleLike);
Optional<KnxGroup> findByPropertyName(String propertyName);
} }

View File

@ -1,12 +1,10 @@
package de.ph87.homeautomation.knx.group; package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.channel.ChannelChangedEvent;
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.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import tuwien.auto.calimero.GroupAddress; import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.IndividualAddress; import tuwien.auto.calimero.IndividualAddress;
@ -16,12 +14,11 @@ import tuwien.auto.calimero.dptxlator.*;
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.Function; import java.util.function.Function;
@Slf4j @Slf4j
@Service @Service
@Transactional(propagation = Propagation.REQUIRES_NEW) @Transactional
@RequiredArgsConstructor @RequiredArgsConstructor
public class KnxGroupWriteService { public class KnxGroupWriteService {
@ -29,107 +26,77 @@ public class KnxGroupWriteService {
private final ApplicationEventPublisher applicationEventPublisher; private final ApplicationEventPublisher applicationEventPublisher;
private final ApplicationEventPublisher eventPublisher; public void setSendValue(final KnxGroup knxGroup, final double value) {
findTranslator(knxGroup).ifPresent(translator -> {
private final KnxGroupMapperService knxGroupMapperService;
@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) {
final Optional<KnxGroup> 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 { try {
final DPTXlator translator = findTranslator(knxGroup); if (translator instanceof DPTXlatorBoolean) {
translator.setData(data); ((DPTXlatorBoolean) translator).setValue(value == 1.0);
translate(DPTXlatorBoolean.class, translator, value -> setBooleanValue(knxGroup, value), DPTXlatorBoolean::getValueBoolean); } else if (translator instanceof DPTXlator8BitUnsigned) {
translate(DPTXlator8BitUnsigned.class, translator, knxGroup::setNumberValue, DPTXlator8BitUnsigned::getNumericValue); ((DPTXlator8BitUnsigned) translator).setValue((int) value);
translate(DPTXlator2ByteFloat.class, translator, knxGroup::setNumberValue, DPTXlator2ByteFloat::getNumericValue); } else { // TODO implement all DPTXlator...
translate(DPTXlator2ByteUnsigned.class, translator, knxGroup::setNumberValue, DPTXlator2ByteUnsigned::getNumericValue); translator.setValue("" + value);
translate(DPTXlator4ByteFloat.class, translator, knxGroup::setNumberValue, DPTXlator4ByteFloat::getNumericValue); }
translate(DPTXlator4ByteSigned.class, translator, knxGroup::setNumberValue, DPTXlator4ByteSigned::getNumericValue); knxGroup.getSend().setNextTimestamp(ZonedDateTime.now());
translate(DPTXlator4ByteUnsigned.class, translator, knxGroup::setNumberValue, DPTXlator4ByteUnsigned::getNumericValue); knxGroup.setSendValue(translator.getData());
translate(DPTXlator8BitSigned.class, translator, knxGroup::setNumberValue, DPTXlator8BitSigned::getNumericValue); applicationEventPublisher.publishEvent(new KnxThreadWakeUpEvent());
translate(DPTXlator64BitSigned.class, translator, knxGroup::setNumberValue, DPTXlator64BitSigned::getNumericValue); } catch (KNXFormatException e) {
translate(DPTXlatorSceneNumber.class, translator, knxGroup::setNumberValue, DPTXlatorSceneNumber::getNumericValue); log.error("Failed set value \"{}\" to DptXlator {} for KnxGroup {}", value, translator, knxGroup);
// TODO implement all DPTXlator...
} catch (NoTranslatorException e) {
log.error(e.getMessage());
} }
log.debug("KnxGroup updated: {}", knxGroup);
}); });
} }
private void setBooleanValue(final KnxGroup knxGroup, final Boolean value) { public void setReceivedData(final GroupAddress groupAddress, final byte[] data, final IndividualAddress knxDeviceAddress) {
knxGroup.setBooleanValue(value);
if (value == null) {
knxGroup.setNumberValue(null);
} else {
knxGroup.setNumberValue(value ? 1.0 : 0.0);
}
}
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)) {
setter.accept(getter.apply(dptXlatorClass.cast(translator)));
}
}
public KnxGroup create(final String name, final GroupAddress address, final String dpt, final boolean readable, final boolean multiGroup) {
final KnxGroup trans = new KnxGroup();
trans.setAddress(address);
trans.setDpt(dpt);
trans.setMultiGroup(multiGroup);
trans.setTitle(name);
trans.getRead().setAble(readable);
return knxGroupRepository.save(trans);
}
public boolean setSendValue(final GroupAddress groupAddress, final double value) throws KnxGroupFormatException {
final Optional<KnxGroup> knxGroupOptional = knxGroupRepository.findByAddressRaw(groupAddress.getRawAddress()); final Optional<KnxGroup> knxGroupOptional = knxGroupRepository.findByAddressRaw(groupAddress.getRawAddress());
if (knxGroupOptional.isEmpty()) { if (knxGroupOptional.isEmpty()) {
return false; log.error("No KnxGroup with address={}", groupAddress);
return;
} }
final KnxGroup knxGroup = knxGroupOptional.get(); final KnxGroup knxGroup = knxGroupOptional.get();
try { knxGroup.setLastTelegram(new KnxTelegram(data, ZonedDateTime.now(), knxDeviceAddress));
final DPTXlator translator = findTranslator(knxGroup); translate(knxGroup);
if (translator instanceof DPTXlatorBoolean) { }
((DPTXlatorBoolean) translator).setValue(value == 1.0);
} else if (translator instanceof DPTXlator8BitUnsigned) { private void translate(final KnxGroup knxGroup) {
((DPTXlator8BitUnsigned) translator).setValue((int) value); findTranslator(knxGroup).ifPresent(translator -> {
} else { // TODO implement all DPTXlator... translator.setData(knxGroup.getLastTelegram().getData());
translator.setValue("" + value); final Optional<Double> valueOptional = translate(DPTXlatorBoolean.class, translator, DPTXlatorBoolean::getNumericValue)
.or(() -> translate(DPTXlator8BitUnsigned.class, translator, DPTXlator8BitUnsigned::getNumericValue))
.or(() -> translate(DPTXlator2ByteFloat.class, translator, DPTXlator2ByteFloat::getNumericValue))
.or(() -> translate(DPTXlator2ByteUnsigned.class, translator, DPTXlator2ByteUnsigned::getNumericValue))
.or(() -> translate(DPTXlator4ByteFloat.class, translator, DPTXlator4ByteFloat::getNumericValue))
.or(() -> translate(DPTXlator4ByteSigned.class, translator, DPTXlator4ByteSigned::getNumericValue))
.or(() -> translate(DPTXlator4ByteUnsigned.class, translator, DPTXlator4ByteUnsigned::getNumericValue))
.or(() -> translate(DPTXlator8BitSigned.class, translator, DPTXlator8BitSigned::getNumericValue))
.or(() -> translate(DPTXlator64BitSigned.class, translator, DPTXlator64BitSigned::getNumericValue))
.or(() -> translate(DPTXlatorSceneNumber.class, translator, DPTXlatorSceneNumber::getNumericValue));
// TODO implement all DPTXlator...
if (valueOptional.isPresent()) {
knxGroup.setValue(valueOptional.get());
knxGroup.setValueTimestamp(ZonedDateTime.now());
log.debug("KnxGroup updated: {}", knxGroup);
applicationEventPublisher.publishEvent(new ChannelChangedEvent(knxGroup));
} else {
log.error("Failed to get value from DptXlator {} for KnxGroup {}", translator, knxGroup);
} }
knxGroup.setSendValue(translator.getData()); });
knxGroup.getSend().setNextTimestamp(ZonedDateTime.now()); }
return true;
} catch (NoTranslatorException | KNXFormatException e) { private Optional<DPTXlator> findTranslator(final KnxGroup knxGroup) {
throw new KnxGroupFormatException(knxGroup, value, e.getMessage()); try {
return Optional.of(TranslatorTypes.createTranslator(knxGroup.getDptMain(), knxGroup.getDpt()));
} catch (KNXException e) {
log.error("No DptXlator found for KnxGroup: {}", knxGroup);
return Optional.empty();
} }
} }
private DPTXlator findTranslator(final KnxGroup knxGroup) throws NoTranslatorException { private <Xlator extends DPTXlator, Value> Optional<Value> translate(final Class<Xlator> dptXlatorClass, final DPTXlator translator, final Function<Xlator, Value> getValueFromXlator) {
if (knxGroup.getDpt() == null) { if (dptXlatorClass.isInstance(translator)) {
throw new NoTranslatorException("Missing DPT"); final Xlator castXlator = dptXlatorClass.cast(translator);
} final Value value = getValueFromXlator.apply(castXlator);
final int mainNumber = Integer.parseInt(knxGroup.getDpt().split("\\.")[0]); return Optional.of(value);
try {
return TranslatorTypes.createTranslator(mainNumber, knxGroup.getDpt());
} catch (KNXException e) {
throw new NoTranslatorException(e);
} }
return Optional.empty();
} }
} }

View File

@ -0,0 +1,32 @@
package de.ph87.homeautomation.knx.group;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import tuwien.auto.calimero.IndividualAddress;
import javax.persistence.Embeddable;
import java.time.ZonedDateTime;
@Getter
@ToString
@Embeddable
@NoArgsConstructor
public class KnxTelegram {
private byte[] data;
private ZonedDateTime timestamp;
private Integer deviceAddressRaw;
private String deviceAddressString;
public KnxTelegram(final byte[] data, final ZonedDateTime timestamp, final IndividualAddress deviceAddress) {
this.data = data;
this.timestamp = timestamp;
this.deviceAddressRaw = deviceAddress.getRawAddress();
this.deviceAddressString = deviceAddress.toString();
}
}

View File

@ -1,5 +1,8 @@
package de.ph87.homeautomation.knx.group; package de.ph87.homeautomation.knx.group;
import lombok.Data;
@Data
public class KnxThreadWakeUpEvent { public class KnxThreadWakeUpEvent {
} }

View File

@ -1,15 +0,0 @@
package de.ph87.homeautomation.knx.group;
import tuwien.auto.calimero.KNXException;
public class NoTranslatorException extends Exception {
public NoTranslatorException(final String message) {
super("Cannot create translator: " + message);
}
public NoTranslatorException(final KNXException e) {
super("Cannot create translator: " + e.toString());
}
}

View File

@ -1,21 +1,16 @@
package de.ph87.homeautomation.property; package de.ph87.homeautomation.property;
import lombok.AccessLevel; import de.ph87.homeautomation.channel.Channel;
import lombok.Getter; import lombok.*;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Column; import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Objects;
@Getter @Getter
@Setter @Setter
@ToString @ToString
@Entity @Entity
@NoArgsConstructor
public final class Property { public final class Property {
@Id @Id
@ -23,7 +18,7 @@ public final class Property {
@Setter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
private Long id; private Long id;
@Column(nullable = false, unique = true) @Column(nullable = false)
private String name; private String name;
@Column(unique = true) @Column(unique = true)
@ -33,20 +28,14 @@ public final class Property {
private Double value; private Double value;
public Boolean getBoolean() { @ManyToOne
final boolean isTrue = Objects.equals(value, 1.0); private Channel readChannel;
final boolean isFalse = Objects.equals(value, 0.0);
if (value == null || (!isTrue && !isFalse)) {
return null;
}
return isTrue;
}
public Double getPercent() { @ManyToOne
if (value == null || value < 0.0 || 100.0 < value) { private Channel writeChannel;
return null;
} public Property(final String name) {
return value; this.name = name;
} }
} }

View File

@ -0,0 +1,45 @@
package de.ph87.homeautomation.property;
import de.ph87.homeautomation.shared.ISearchController;
import de.ph87.homeautomation.shared.KeyValuePair;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("property")
@RequiredArgsConstructor
public class PropertyController implements ISearchController {
private final PropertyWriteService propertyWriteService;
private final PropertyReadService propertyReadService;
@PostMapping("set")
public void set(@RequestBody final PropertySetDto dto) {
propertyWriteService.write(dto.getName(), dto.getValue());
}
@Override
@PostMapping("getById")
public KeyValuePair getById(@RequestBody final String name) {
final PropertyDto propertyDto = propertyReadService.getDtoByName(name);
return toKeyValuePair(propertyDto);
}
@Override
@PostMapping("searchLike")
public List<KeyValuePair> searchLike(@RequestBody final String term) {
return propertyReadService.findAllDtoLike("%" + term + "%").stream().map(this::toKeyValuePair).collect(Collectors.toList());
}
private KeyValuePair toKeyValuePair(final PropertyDto propertyDto) {
return new KeyValuePair(propertyDto.getName(), propertyDto.getTitle());
}
}

View File

@ -2,10 +2,11 @@ package de.ph87.homeautomation.property;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@Data @Data
public final class PropertyDto { public final class PropertyDto implements Serializable {
private final long id; private final long id;
@ -13,16 +14,16 @@ public final class PropertyDto {
private final String title; private final String title;
private final ZonedDateTime timestamp;
private final Double value; private final Double value;
private final ZonedDateTime valueTimestamp;
public PropertyDto(final Property property) { public PropertyDto(final Property property) {
this.id = property.getId(); this.id = property.getId();
this.name = property.getName(); this.name = property.getName();
this.title = property.getTitle(); this.title = property.getTitle();
this.timestamp = property.getTimestamp();
this.value = property.getValue(); this.value = property.getValue();
this.valueTimestamp = property.getTimestamp();
} }
} }

View File

@ -0,0 +1,37 @@
package de.ph87.homeautomation.property;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class PropertyReadService {
private final PropertyRepository propertyRepository;
private final PropertyMapper propertyMapper;
public Property getByName(final String name) {
return propertyRepository.findByName(name).orElseThrow(RuntimeException::new);
}
public List<Property> findAllByReadChannel_Id(final long readChannelId) {
return propertyRepository.findAllByReadChannel_Id(readChannelId);
}
public List<PropertyDto> findAllDtoLike(final String like) {
return propertyRepository.findAllByNameLike(like).stream().map(propertyMapper::toDto).collect(Collectors.toList());
}
public PropertyDto getDtoByName(final String name) {
return propertyMapper.toDto(getByName(name));
}
}

View File

@ -2,10 +2,15 @@ package de.ph87.homeautomation.property;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface PropertyRepository extends CrudRepository<Property, Long> { public interface PropertyRepository extends CrudRepository<Property, Long> {
Optional<Property> findByName(String name); Optional<Property> findByName(String name);
List<Property> findAllByReadChannel_Id(long readChannelId);
List<Property> findAllByNameLike(final String like);
} }

View File

@ -1,36 +0,0 @@
package de.ph87.homeautomation.property;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.Set;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class PropertyService {
private final Set<IPropertyOwner> owners;
private final PropertyRepository propertyRepository;
private final ApplicationEventPublisher applicationEventPublisher;
private final PropertyMapper propertyMapper;
public Optional<PropertyDto> find(final String name) {
return propertyRepository.findByName(name).map(propertyMapper::toDto);
}
public Optional<PropertyDto> set(final String name, final double value) {
final Optional<PropertyDto> propertyOptional = owners.stream().map(owner -> owner.write(name, value)).findFirst().map(propertyMapper::toDto);
propertyOptional.ifPresent(applicationEventPublisher::publishEvent);
return propertyOptional;
}
}

View File

@ -0,0 +1,12 @@
package de.ph87.homeautomation.property;
import lombok.Data;
@Data
public class PropertySetDto {
private final String name;
private final double value;
}

View File

@ -0,0 +1,47 @@
package de.ph87.homeautomation.property;
import de.ph87.homeautomation.channel.ChannelChangedEvent;
import de.ph87.homeautomation.channel.ChannelService;
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.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class PropertyWriteService {
private final PropertyReadService propertyReadService;
private final ChannelService channelService;
private final PropertyMapper propertyMapper;
private final WebSocketService webSocketService;
public void write(final String name, final double value) {
write(propertyReadService.getByName(name), value);
}
public void write(final Property property, final double value) {
channelService.write(property, value);
}
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void channelChangedEventListener(final ChannelChangedEvent event) {
propertyReadService.findAllByReadChannel_Id(event.getChannelId())
.forEach(property -> {
property.setValue(property.getReadChannel().getValue());
property.setTimestamp(property.getReadChannel().getValueTimestamp());
log.debug("Updated Property from Channel: {}", property);
webSocketService.send(propertyMapper.toDto(property), true);
}
);
}
}

View File

@ -1,9 +0,0 @@
package de.ph87.homeautomation.property2;
public interface IPropertyOwner {
String getPropertyOwnerName();
Property write(final String name, final double value);
}

View File

@ -1,35 +0,0 @@
package de.ph87.homeautomation.property2;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.time.ZonedDateTime;
@Getter
@Setter
@ToString
@Entity
public final class Property {
@Id
@GeneratedValue
@Setter(AccessLevel.NONE)
private Long id;
@Column(nullable = false, unique = true)
private String name;
@Column(unique = true)
private String title;
private ZonedDateTime timestamp;
private Double value;
}

View File

@ -1,44 +0,0 @@
package de.ph87.homeautomation.property2;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class PropertyOwnerValidatorService {
private final Set<IPropertyOwner> owners;
@PostConstruct
public void postConstruct() {
final Map<IPropertyOwner, List<IPropertyOwner>> overlapping = owners.stream().collect(Collectors.toMap(owner -> owner, this::getOverlappingList));
if (!overlapping.values().stream().allMatch(List::isEmpty)) {
throw new RuntimeException(
String.format(
"Following IPropertyOwners have overlapping names:\n\t%s",
overlapping.entrySet().stream()
.map(entry -> {
final String prefixClassName = entry.getKey().getClass().getCanonicalName();
final String prefixPropertyName = entry.getKey().getPropertyOwnerName();
final String overlappingList = entry.getValue().stream().map(owner -> owner.getClass().getCanonicalName() + "(" + owner.getPropertyOwnerName() + ")").collect(Collectors.joining("\n\t\t"));
return String.format("%s(%s):\n\t\t%s", prefixClassName, prefixPropertyName, overlappingList);
})
.collect(Collectors.joining("\n\t"))
)
);
}
}
private List<IPropertyOwner> getOverlappingList(final IPropertyOwner a) {
return owners.stream().filter(b -> b.getPropertyOwnerName().startsWith(a.getPropertyOwnerName())).collect(Collectors.toList());
}
}

View File

@ -1,11 +0,0 @@
package de.ph87.homeautomation.property2;
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
public interface PropertyRepository extends CrudRepository<Property, Long> {
Optional<Property> findByName(String name);
}

View File

@ -1,29 +0,0 @@
package de.ph87.homeautomation.property2;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.Set;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class PropertyService {
private final Set<IPropertyOwner> owners;
private final PropertyRepository propertyRepository;
public Optional<Property> write(final String name, final double value) {
return owners.stream().map(owner -> owner.write(name, value)).findFirst();
}
public Optional<Property> read(final String name) {
return propertyRepository.findByName(name);
}
}

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.schedule; package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry; import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
@ -26,11 +27,11 @@ public class Schedule {
@Column(nullable = false, unique = true) @Column(nullable = false, unique = true)
private String title; private String title;
private String propertyName; @ManyToOne
private Property property;
@Column(nullable = false)
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private PropertyType propertyType = PropertyType.ON_OFF; private SchedulePropertyType propertyType;
@ToString.Exclude @ToString.Exclude
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)

View File

@ -1,10 +1,13 @@
package de.ph87.homeautomation.schedule; package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.PropertyReadService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import static de.ph87.homeautomation.shared.Helpers.mapIfNotNull;
@RestController @RestController
@RequestMapping("schedule") @RequestMapping("schedule")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -14,6 +17,8 @@ public class ScheduleController {
private final ScheduleWriteService scheduleWriteService; private final ScheduleWriteService scheduleWriteService;
private final PropertyReadService propertyReadService;
@GetMapping("findAll") @GetMapping("findAll")
public List<ScheduleDto> findAll() { public List<ScheduleDto> findAll() {
return scheduleReadService.findAllDtos(); return scheduleReadService.findAllDtos();
@ -39,19 +44,19 @@ public class ScheduleController {
return scheduleWriteService.set(id, Schedule::setEnabled, enabled); return scheduleWriteService.set(id, Schedule::setEnabled, enabled);
} }
@PostMapping("set/{id}/name") @PostMapping("set/{id}/title")
public ScheduleDto setName(@PathVariable final long id, @RequestBody final String name) { public ScheduleDto setTitle(@PathVariable final long id, @RequestBody final String title) {
return scheduleWriteService.set(id, Schedule::setTitle, name); return scheduleWriteService.set(id, Schedule::setTitle, title);
} }
@PostMapping("set/{id}/propertyName") @PostMapping("set/{id}/propertyName")
public ScheduleDto setPropertyName(@PathVariable final long id, @RequestBody final String propertyName) { public ScheduleDto setPropertyName(@PathVariable final long id, @RequestBody(required = false) final String propertyName) {
return scheduleWriteService.set(id, Schedule::setPropertyName, propertyName); return scheduleWriteService.set(id, (schedule, v) -> schedule.setProperty(mapIfNotNull(v, propertyReadService::getByName)), propertyName);
} }
@PostMapping("set/{id}/propertyType") @PostMapping("set/{id}/propertyType")
public ScheduleDto setPropertyType(@PathVariable final long id, @RequestBody final String propertyType) { public ScheduleDto setPropertyType(@PathVariable final long id, @RequestBody final String propertyType) {
return scheduleWriteService.set(id, Schedule::setPropertyType, PropertyType.valueOf(propertyType)); return scheduleWriteService.set(id, Schedule::setPropertyType, SchedulePropertyType.valueOf(propertyType));
} }
} }

View File

@ -1,11 +1,14 @@
package de.ph87.homeautomation.schedule; package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryDto; import de.ph87.homeautomation.schedule.entry.ScheduleEntryDto;
import lombok.Getter; import lombok.Getter;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static de.ph87.homeautomation.shared.Helpers.mapIfNotNull;
@Getter @Getter
public class ScheduleDto { public class ScheduleDto {
@ -17,7 +20,7 @@ public class ScheduleDto {
public final String propertyName; public final String propertyName;
public final PropertyType propertyType; public final SchedulePropertyType propertyType;
public final Set<ScheduleEntryDto> entries; public final Set<ScheduleEntryDto> entries;
@ -25,7 +28,7 @@ public class ScheduleDto {
this.id = schedule.getId(); this.id = schedule.getId();
this.enabled = schedule.isEnabled(); this.enabled = schedule.isEnabled();
this.title = schedule.getTitle(); this.title = schedule.getTitle();
this.propertyName = schedule.getPropertyName(); this.propertyName = mapIfNotNull(schedule.getProperty(), Property::getName);
this.propertyType = schedule.getPropertyType(); this.propertyType = schedule.getPropertyType();
this.entries = schedule.getEntries().stream().map(ScheduleEntryDto::new).collect(Collectors.toSet()); this.entries = schedule.getEntries().stream().map(ScheduleEntryDto::new).collect(Collectors.toSet());
} }

View File

@ -1,7 +1,6 @@
package de.ph87.homeautomation.schedule; package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.PropertyService; import de.ph87.homeautomation.property.PropertyWriteService;
import de.ph87.homeautomation.property.PropertySetException;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry; import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -21,7 +20,7 @@ public class ScheduleExecutionService {
private final ScheduleCalculationService scheduleCalculationService; private final ScheduleCalculationService scheduleCalculationService;
private final PropertyService propertyService; private final PropertyWriteService propertyWriteService;
public void executeAllLastDue() { public void executeAllLastDue() {
final ZonedDateTime now = ZonedDateTime.now(); final ZonedDateTime now = ZonedDateTime.now();
@ -29,7 +28,7 @@ public class ScheduleExecutionService {
} }
private void executeLastDue(final Schedule schedule, final ZonedDateTime now) { private void executeLastDue(final Schedule schedule, final ZonedDateTime now) {
if (schedule.getPropertyName() == null || schedule.getPropertyName().isEmpty()) { if (schedule.getProperty() == null) {
log.error("Cannot execute Schedule {}: No property set!", schedule); log.error("Cannot execute Schedule {}: No property set!", schedule);
return; return;
} }
@ -42,11 +41,7 @@ public class ScheduleExecutionService {
private void executeEntry(final Schedule schedule, final ScheduleEntry entry, final ZonedDateTime now) { private void executeEntry(final Schedule schedule, final ScheduleEntry entry, final ZonedDateTime now) {
entry.setLastClearTimestamp(entry.getNextClearTimestamp()); entry.setLastClearTimestamp(entry.getNextClearTimestamp());
log.info("Executing Schedule \"{}\" Entry {}", schedule.getTitle(), entry); log.info("Executing Schedule \"{}\" Entry {}", schedule.getTitle(), entry);
try { propertyWriteService.write(schedule.getProperty(), entry.getValue());
propertyService.set(schedule.getPropertyName(), entry.getValue());
} catch (PropertySetException e) {
log.error(e.getMessage());
}
scheduleCalculationService.calculateSchedule(schedule, now); scheduleCalculationService.calculateSchedule(schedule, now);
} }

View File

@ -0,0 +1,5 @@
package de.ph87.homeautomation.schedule;
public enum SchedulePropertyType {
SWITCH, PERCENT, SHUTTER, SCENE
}

View File

@ -1,17 +0,0 @@
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));
}
}