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>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
</dependency>
<dependency>
<groupId>com.github.calimero</groupId>
<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";
export abstract class Device {
@ -19,18 +19,14 @@ export abstract class Device {
validateNumberNotNull(json['id']),
validateStringNotEmptyNotNull(json['title']),
type,
validateStringNullToEmpty(json['getState']),
validateStringNullToEmpty(json['setState']),
validateBooleanAllowNull(json['state']),
Property.fromJsonAllowNull(json['stateProperty']),
);
case "DeviceShutter":
return new DeviceShutter(
validateNumberNotNull(json['id']),
validateStringNotEmptyNotNull(json['title']),
type,
validateStringNullToEmpty(json['getPercent']),
validateStringNullToEmpty(json['setPercent']),
validateNumberAllowNull(json['percent']),
Property.fromJsonAllowNull(json['positionProperty']),
);
}
throw new Error("No such type: " + type);
@ -54,16 +50,14 @@ export class DeviceSwitch extends Device {
id: number,
title: string,
type: string,
public getState: string,
public setState: string,
public state: boolean | null,
public stateProperty: Property | null,
) {
super(id, title, type);
}
updateProperty(property: Property): void {
if (this.getState === property.name) {
this.state = property.booleanValue;
if (this.stateProperty?.name === property.name) {
this.stateProperty = property;
}
}
@ -75,16 +69,14 @@ export class DeviceShutter extends Device {
id: number,
title: string,
type: string,
public getPercent: string,
public setPercent: string,
public percent: number | null,
public positionProperty: Property | null,
) {
super(id, title, type);
}
updateProperty(property: Property): void {
if (this.getPercent === property.name) {
this.percent = property.numberValue;
if (this.positionProperty?.name === property.name) {
this.positionProperty = property;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@
</td>
<td colspan="9">
<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="SHUTTER">Rollladen</option>
<option value="SCENE">Szene</option>
@ -137,7 +137,7 @@
<td class="empty last"></td>
</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"}}
</td>
<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;
@Input()
initial!: string;
initial?: string;
@Input()
showKey: boolean = false;
@ -65,7 +65,7 @@ export class SearchComponent<T> implements OnInit {
}
start(): void {
this.term = this.initial;
this.term = this.initial || "";
if (this.resultList && this.input) {
this.resultList.style.left = this.input.style.left;
}

View File

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

View File

@ -1,15 +1,15 @@
package de.ph87.homeautomation;
import com.luckycatlabs.sunrisesunset.Zenith;
import de.ph87.homeautomation.channel.Channel;
import de.ph87.homeautomation.device.DeviceRepository;
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.KnxGroupRepository;
import de.ph87.homeautomation.knx.group.KnxGroupWriteService;
import de.ph87.homeautomation.knx.group.KnxGroupReadService;
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.SchedulePropertyType;
import de.ph87.homeautomation.schedule.ScheduleRepository;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryType;
@ -17,7 +17,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tuwien.auto.calimero.GroupAddress;
import java.time.ZonedDateTime;
@ -32,52 +31,39 @@ public class DemoDataService {
private static final Zenith BETWEEN_OFFICIAL_AND_CIVIL = new Zenith(93.0);
private final KnxGroupWriteService knxGroupWriteService;
private final ScheduleRepository scheduleRepository;
private final KnxGroupRepository knxGroupRepository;
private final DeviceWriteService deviceWriteService;
private final DeviceRepository deviceRepository;
private final PropertyRepository propertyRepository;
private final KnxGroupReadService knxGroupReadService;
public void insertDemoData() {
final KnxGroup eg_flur_licht_schalten = createKnxGroupIfNotExists("EG Flur Licht Schalten", 0, 5, 14, "1.001", false, false);
final KnxGroup ambiente_eg_status = createKnxGroupIfNotExists("Ambiente EG Status", 0, 3, 81, "1.001", true, false);
final KnxGroup ambiente_eg_schalten = createKnxGroupIfNotExists("Ambiente EG Schalten", 0, 3, 80, "1.001", false, false);
final KnxGroup ambiente_og_status = createKnxGroupIfNotExists("Ambiente OG Status", 0, 6, 2, "1.001", true, false);
final KnxGroup ambiente_og_schalten = createKnxGroupIfNotExists("Ambiente OG Schalten", 0, 6, 3, "1.001", false, false);
final KnxGroup wohnzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Wohnzimmer Rollladen Position Anfahren", 0, 4, 24, "5.001", false, false);
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);
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 Property bad_licht = createProperty("bad.licht", "Bad Licht", knx(0, 5, 19), knx(0, 3, 73));
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 Property wohnzimmer_rollladen = createProperty("wohnzimmer.rollladen", "Wohnzimmer Rollladen", null, knx(0, 4, 24));
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 Property helligkeit = createProperty("helligkeit", "Helligkeit", knx(0, 5, 6), null);
final Property szene_haus = createProperty("szene_haus", "Szene Haus ", null, knx(0, 0, 21));
if (deviceRepository.count() == 0) {
createDeviceSwitch("Ambiente EG", ambiente_eg_status, ambiente_eg_schalten);
createDeviceSwitch("Ambiente OG", ambiente_og_status, ambiente_og_schalten);
createDeviceSwitch("Bad Licht", bad_licht_status, bad_licht_schalten);
createDeviceShutter("Wohnzimmer Rollladen", null, wohnzimmer_rollladen_position_anfahren);
createDeviceShutter("Schlafzimmer Rollladen", null, schlafzimmer_rollladen_position_anfahren);
createDeviceShutter("Flur Rollladen", null, flur_og_rollladen_position_anfahren);
deviceWriteService.createDeviceSwitch("Ambiente EG", ambiente_eg);
deviceWriteService.createDeviceSwitch("Ambiente OG", ambiente_og);
deviceWriteService.createDeviceSwitch("Bad Licht", bad_licht);
deviceWriteService.createDeviceShutter("Wohnzimmer Rollladen", wohnzimmer_rollladen);
deviceWriteService.createDeviceShutter("Schlafzimmer Rollladen", schlafzimmer_rollladen);
deviceWriteService.createDeviceShutter("Flur Rollladen", flur_og_rollladen);
}
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, 2, 0, 0, MIN30, false);
createTime(scheduleEgFlurLicht, true, 7, 30, 0, MIN30, true);
@ -88,36 +74,36 @@ public class DemoDataService {
createTime(scheduleEgFlurLicht, true, 20, 0, 0, MIN30, false);
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, 9, 30, 0, MIN30, false);
createSunset(scheduleEgAmbiente, true, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleEgAmbiente, true, Zenith.ASTRONOMICAL, MIN30, false);
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, 9, 30, 0, MIN30, false);
createSunset(scheduleOgAmbiente, true, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleOgAmbiente, true, Zenith.ASTRONOMICAL, MIN30, false);
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);
createSunset(scheduleWohnzimmerRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
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);
createSunset(scheduleSchlafzimmerRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
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);
createSunset(scheduleFlurRollladen, true, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
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, 11, 30, 0, MIN30, false);
createTime(scheduleBadLichtMitte, true, 15, 30, 0, MIN30, true);
@ -128,7 +114,7 @@ public class DemoDataService {
createTime(scheduleBadLichtMitte, true, 1, 0, 0, MIN30, false);
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, 8, 30, 0, 0, 30);
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) {
return deviceWriteService.createDeviceSwitch(title, getPercent, setPercent);
private KnxGroup knx(final int main, final int mid, final int sub) {
return knxGroupReadService.getByAddress(main, mid, sub);
}
private DeviceDto createDeviceShutter(final String title, final Property getPercent, final Property setPercent) {
return deviceWriteService.createDeviceShutter(title, getPercent, setPercent);
private Property createProperty(final String name, final String title, final Channel readChannel, final Channel writeChannel) {
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();
schedule.setEnabled(enabled);
schedule.setTitle(title);
schedule.setPropertyName(propertyDto.getName());
schedule.setPropertyType(propertyDto.getPropertyType());
schedule.setProperty(property);
schedule.setPropertyType(propertyType);
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) {
final ZonedDateTime now = ZonedDateTime.now().plusSeconds(inSeconds).withNano(0);
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.DeviceDto;
import de.ph87.homeautomation.device.devices.DeviceShutter;
import de.ph87.homeautomation.device.devices.DeviceSwitch;
import de.ph87.homeautomation.property.PropertyReadService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static de.ph87.homeautomation.shared.Helpers.mapIfNotNull;
@RestController
@RequestMapping("device")
@RequiredArgsConstructor
@ -18,6 +19,8 @@ public class DeviceController {
private final DeviceWriteService deviceWriteService;
private final PropertyReadService propertyReadService;
@GetMapping("findAll")
public List<DeviceDto> findAll() {
return deviceReadService.findAll();
@ -48,24 +51,14 @@ public class DeviceController {
return deviceWriteService.set(id, Device::setTitle, title);
}
@PostMapping("set/{id}/DeviceSwitch/setState")
public DeviceDto setDeviceSwitchSetState(@PathVariable final long id, @RequestBody(required = false) final String name) {
return deviceWriteService.setDeviceSwitch(id, DeviceSwitch::setSetState, name);
@PostMapping("set/{id}/DeviceSwitch/stateProperty")
public DeviceDto setDeviceSwitchStateProperty(@PathVariable final long id, @RequestBody(required = false) final String name) {
return deviceWriteService.setDeviceSwitch(id, (device, v) -> device.setStateProperty(mapIfNotNull(v, propertyReadService::getByName)), name);
}
@PostMapping("set/{id}/DeviceSwitch/getState")
public DeviceDto setDeviceSwitchGetState(@PathVariable final long id, @RequestBody(required = false) final String name) {
return deviceWriteService.setDeviceSwitch(id, DeviceSwitch::setGetState, 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);
@PostMapping("set/{id}/DeviceShutter/positionProperty")
public DeviceDto setDeviceShutterPositionProperty(@PathVariable final long id, @RequestBody(required = false) final String name) {
return deviceWriteService.setDeviceShutter(id, (device, v) -> device.setPositionProperty(mapIfNotNull(v, propertyReadService::getByName)), name);
}
}

View File

@ -1,7 +1,7 @@
package de.ph87.homeautomation.device;
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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -21,6 +21,8 @@ public class DeviceReadService {
private final DeviceRepository deviceRepository;
private final PropertyMapper propertyMapper;
public List<DeviceDto> findAll() {
return deviceRepository.findAll().stream().map(this::toDto).collect(Collectors.toList());
}
@ -28,14 +30,12 @@ public class DeviceReadService {
public DeviceDto toDto(final Device device) {
if (device instanceof DeviceSwitch) {
final DeviceSwitch deviceSwitch = (DeviceSwitch) device;
final Boolean state = mapIfNotNull(deviceSwitch.getGetState(), Property::getBoolean);
return new DeviceSwitchDto(deviceSwitch, mapIfNotNull(deviceSwitch.getGetState(), Property::getName), mapIfNotNull(deviceSwitch.getSetState(), Property::getName), state);
return new DeviceSwitchDto(deviceSwitch, mapIfNotNull(deviceSwitch.getStateProperty(), propertyMapper::toDto));
} else if (device instanceof DeviceShutter) {
final DeviceShutter deviceShutter = (DeviceShutter) device;
final Double percent = mapIfNotNull(deviceShutter.getGetPercent(), Property::getPercent);
return new DeviceShutterDto(deviceShutter, mapIfNotNull(deviceShutter.getGetPercent(), Property::getName), mapIfNotNull(deviceShutter.getSetPercent(), Property::getName), percent);
return new DeviceShutterDto(deviceShutter, mapIfNotNull(deviceShutter.getPositionProperty(), propertyMapper::toDto));
}
throw new RuntimeException("Not implemented: toDto(" + device + ")");
throw new RuntimeException();
}
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.DeviceSwitch;
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.web.BadRequestException;
import lombok.RequiredArgsConstructor;
@ -23,51 +23,26 @@ public class DeviceWriteService {
private final DeviceRepository deviceRepository;
private final PropertyService propertyService;
private final PropertyWriteService propertyWriteService;
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();
deviceSwitch.setTitle(title);
deviceSwitch.setGetState(getStatePropertyName);
deviceSwitch.setSetState(setStatePropertyName);
deviceSwitch.setStateProperty(stateProperty);
deviceRepository.save(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();
deviceShutter.setTitle(title);
deviceShutter.setGetPercent(getPercentPropertyName);
deviceShutter.setSetPercent(setPercentPropertyName);
deviceShutter.setPositionProperty(positionProperty);
deviceRepository.save(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) {
final Device device = deviceReadService.getById(id);
setter.accept(device, value);
@ -99,9 +74,9 @@ public class DeviceWriteService {
public DeviceDto create(final String type) {
switch (type) {
case "DeviceSwitch":
return createDeviceSwitch(generateUnusedTitle(), null, null);
return createDeviceSwitch(generateUnusedTitle(), null);
case "DeviceShutter":
return createDeviceShutter(generateUnusedTitle(), null, null);
return createDeviceShutter(generateUnusedTitle(), null);
}
throw new RuntimeException("Not implemented type: " + type);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -131,14 +131,14 @@ public class KnxThreadService extends AbstractThreadService implements NetworkLi
@Override
public void groupReadResponse(final ProcessEvent processEvent) {
synchronized (databaseAccessLock) {
knxGroupWriteService.updateIfExists(processEvent.getDestination().getRawAddress(), processEvent.getASDU(), processEvent.getSourceAddr());
knxGroupWriteService.setReceivedData(processEvent.getDestination(), processEvent.getASDU(), processEvent.getSourceAddr());
}
}
@Override
public void groupWrite(final ProcessEvent processEvent) {
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;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import de.ph87.homeautomation.channel.Channel;
import de.ph87.homeautomation.channel.IChannelOwner;
import lombok.*;
import tuwien.auto.calimero.GroupAddress;
import javax.persistence.*;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.time.ZonedDateTime;
@Getter
@Setter
@ToString
@Entity
public class KnxGroup {
@Id
@GeneratedValue
@Setter(AccessLevel.NONE)
private Long id;
@NoArgsConstructor
public class KnxGroup extends Channel {
@Setter(AccessLevel.NONE)
@Column(nullable = false, unique = true)
@ -28,45 +25,37 @@ public class KnxGroup {
@Column(nullable = false, unique = true)
private String addressStr;
@Setter(AccessLevel.NONE)
@Column(nullable = false, unique = true)
private String propertyName;
private int dptMain;
@Column(nullable = false)
private String dpt;
private int dptSub;
@Column(nullable = false)
private String title;
private String name;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private PropertyType propertyType;
private String description;
private byte[] value;
private int puid;
private int lastDeviceAddressRaw;
private boolean ets;
private String lastDeviceAddressString;
@Embedded
private KnxTelegram lastTelegram;
private Boolean booleanValue;
private Double numberValue;
private Double value;
private ZonedDateTime valueTimestamp;
private byte[] sendValue;
private int readInterval;
@Column(nullable = false)
private boolean multiGroup;
@Embedded
private KnxGroupLinkInfo read = new KnxGroupLinkInfo();
private byte[] sendValue;
@Embedded
private KnxGroupLinkInfo send = new KnxGroupLinkInfo();
public KnxGroup(final GroupAddress address) {
setAddress(address);
}
public void setAddress(final int rawAddress) {
setAddress(new GroupAddress(rawAddress));
}
@ -74,11 +63,19 @@ public class KnxGroup {
public void setAddress(final GroupAddress groupAddress) {
this.addressRaw = groupAddress.getRawAddress();
this.addressStr = groupAddress.toString();
this.propertyName = "knx.group." + groupAddress.getMainGroup() + "." + groupAddress.getMiddleGroup() + "." + groupAddress.getSubGroup8();
}
public GroupAddress getAddress() {
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 {
try {
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().setErrorMessage(null);
knxGroup.getSend().setNextTimestamp(null);
@ -64,11 +64,7 @@ public class KnxGroupLinkService {
processCommunicator.read(createStateDP(knxGroup));
knxGroup.getRead().setErrorCount(0);
knxGroup.getRead().setErrorMessage(null);
if (knxGroup.getReadInterval() > 0) {
knxGroup.getRead().setNextTimestamp(align(knxGroup.getReadInterval()));
} else {
knxGroup.getRead().setNextTimestamp(null);
}
knxGroup.getRead().setNextTimestamp(null);
return true;
} catch (KNXFormatException e) {
log.error(e.toString());
@ -88,8 +84,7 @@ public class KnxGroupLinkService {
private StateDP createStateDP(final KnxGroup knxGroup) {
final GroupAddress groupAddress = knxGroup.getAddress();
final int mainNumber = Integer.parseInt(knxGroup.getDpt().split("\\.", 2)[0]);
return new StateDP(groupAddress, groupAddress.toString(), mainNumber, knxGroup.getDpt());
return new StateDP(groupAddress, groupAddress.toString(), knxGroup.getDptMain(), knxGroup.getDpt());
}
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 org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tuwien.auto.calimero.GroupAddress;
@Slf4j
@Service
@ -11,4 +12,10 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
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.Optional;
public interface KnxGroupRepository extends CrudRepository<KnxGroup, Long> {
public interface KnxGroupRepository extends CrudRepository<KnxGroup, String> {
Optional<KnxGroup> findByAddressRaw(int rawAddress);
@ -16,14 +16,8 @@ public interface KnxGroupRepository extends CrudRepository<KnxGroup, Long> {
Optional<KnxGroup> findFirstByRead_NextTimestampLessThanEqualOrderByRead_NextTimestampAsc(ZonedDateTime timestamp);
List<KnxGroup> findAllByRead_AbleTrue();
Optional<KnxGroup> findFirstByRead_NextTimestampNotNullOrderByRead_NextTimestampAsc();
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;
import de.ph87.homeautomation.channel.ChannelChangedEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.IndividualAddress;
@ -16,12 +14,11 @@ import tuwien.auto.calimero.dptxlator.*;
import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
@Slf4j
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional
@RequiredArgsConstructor
public class KnxGroupWriteService {
@ -29,107 +26,77 @@ public class KnxGroupWriteService {
private final ApplicationEventPublisher applicationEventPublisher;
private final ApplicationEventPublisher eventPublisher;
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);
public void setSendValue(final KnxGroup knxGroup, final double value) {
findTranslator(knxGroup).ifPresent(translator -> {
try {
final DPTXlator translator = findTranslator(knxGroup);
translator.setData(data);
translate(DPTXlatorBoolean.class, translator, value -> setBooleanValue(knxGroup, value), DPTXlatorBoolean::getValueBoolean);
translate(DPTXlator8BitUnsigned.class, translator, knxGroup::setNumberValue, DPTXlator8BitUnsigned::getNumericValue);
translate(DPTXlator2ByteFloat.class, translator, knxGroup::setNumberValue, DPTXlator2ByteFloat::getNumericValue);
translate(DPTXlator2ByteUnsigned.class, translator, knxGroup::setNumberValue, DPTXlator2ByteUnsigned::getNumericValue);
translate(DPTXlator4ByteFloat.class, translator, knxGroup::setNumberValue, DPTXlator4ByteFloat::getNumericValue);
translate(DPTXlator4ByteSigned.class, translator, knxGroup::setNumberValue, DPTXlator4ByteSigned::getNumericValue);
translate(DPTXlator4ByteUnsigned.class, translator, knxGroup::setNumberValue, DPTXlator4ByteUnsigned::getNumericValue);
translate(DPTXlator8BitSigned.class, translator, knxGroup::setNumberValue, DPTXlator8BitSigned::getNumericValue);
translate(DPTXlator64BitSigned.class, translator, knxGroup::setNumberValue, DPTXlator64BitSigned::getNumericValue);
translate(DPTXlatorSceneNumber.class, translator, knxGroup::setNumberValue, DPTXlatorSceneNumber::getNumericValue);
// TODO implement all DPTXlator...
} catch (NoTranslatorException e) {
log.error(e.getMessage());
if (translator instanceof DPTXlatorBoolean) {
((DPTXlatorBoolean) translator).setValue(value == 1.0);
} else if (translator instanceof DPTXlator8BitUnsigned) {
((DPTXlator8BitUnsigned) translator).setValue((int) value);
} else { // TODO implement all DPTXlator...
translator.setValue("" + value);
}
knxGroup.getSend().setNextTimestamp(ZonedDateTime.now());
knxGroup.setSendValue(translator.getData());
applicationEventPublisher.publishEvent(new KnxThreadWakeUpEvent());
} catch (KNXFormatException e) {
log.error("Failed set value \"{}\" to DptXlator {} for KnxGroup {}", value, translator, knxGroup);
}
log.debug("KnxGroup updated: {}", knxGroup);
});
}
private void setBooleanValue(final KnxGroup knxGroup, final Boolean value) {
knxGroup.setBooleanValue(value);
if (value == null) {
knxGroup.setNumberValue(null);
} else {
knxGroup.setNumberValue(value ? 1.0 : 0.0);
}
}
private <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 {
public void setReceivedData(final GroupAddress groupAddress, final byte[] data, final IndividualAddress knxDeviceAddress) {
final Optional<KnxGroup> knxGroupOptional = knxGroupRepository.findByAddressRaw(groupAddress.getRawAddress());
if (knxGroupOptional.isEmpty()) {
return false;
log.error("No KnxGroup with address={}", groupAddress);
return;
}
final KnxGroup knxGroup = knxGroupOptional.get();
try {
final DPTXlator translator = findTranslator(knxGroup);
if (translator instanceof DPTXlatorBoolean) {
((DPTXlatorBoolean) translator).setValue(value == 1.0);
} else if (translator instanceof DPTXlator8BitUnsigned) {
((DPTXlator8BitUnsigned) translator).setValue((int) value);
} else { // TODO implement all DPTXlator...
translator.setValue("" + value);
knxGroup.setLastTelegram(new KnxTelegram(data, ZonedDateTime.now(), knxDeviceAddress));
translate(knxGroup);
}
private void translate(final KnxGroup knxGroup) {
findTranslator(knxGroup).ifPresent(translator -> {
translator.setData(knxGroup.getLastTelegram().getData());
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) {
throw new KnxGroupFormatException(knxGroup, value, e.getMessage());
});
}
private Optional<DPTXlator> findTranslator(final KnxGroup knxGroup) {
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 {
if (knxGroup.getDpt() == null) {
throw new NoTranslatorException("Missing DPT");
}
final int mainNumber = Integer.parseInt(knxGroup.getDpt().split("\\.")[0]);
try {
return TranslatorTypes.createTranslator(mainNumber, knxGroup.getDpt());
} catch (KNXException e) {
throw new NoTranslatorException(e);
private <Xlator extends DPTXlator, Value> Optional<Value> translate(final Class<Xlator> dptXlatorClass, final DPTXlator translator, final Function<Xlator, Value> getValueFromXlator) {
if (dptXlatorClass.isInstance(translator)) {
final Xlator castXlator = dptXlatorClass.cast(translator);
final Value value = getValueFromXlator.apply(castXlator);
return Optional.of(value);
}
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;
import lombok.Data;
@Data
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;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import de.ph87.homeautomation.channel.Channel;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.*;
import java.time.ZonedDateTime;
import java.util.Objects;
@Getter
@Setter
@ToString
@Entity
@NoArgsConstructor
public final class Property {
@Id
@ -23,7 +18,7 @@ public final class Property {
@Setter(AccessLevel.NONE)
private Long id;
@Column(nullable = false, unique = true)
@Column(nullable = false)
private String name;
@Column(unique = true)
@ -33,20 +28,14 @@ public final class Property {
private Double value;
public Boolean getBoolean() {
final boolean isTrue = Objects.equals(value, 1.0);
final boolean isFalse = Objects.equals(value, 0.0);
if (value == null || (!isTrue && !isFalse)) {
return null;
}
return isTrue;
}
@ManyToOne
private Channel readChannel;
public Double getPercent() {
if (value == null || value < 0.0 || 100.0 < value) {
return null;
}
return value;
@ManyToOne
private Channel writeChannel;
public Property(final String name) {
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 java.io.Serializable;
import java.time.ZonedDateTime;
@Data
public final class PropertyDto {
public final class PropertyDto implements Serializable {
private final long id;
@ -13,16 +14,16 @@ public final class PropertyDto {
private final String title;
private final ZonedDateTime timestamp;
private final Double value;
private final ZonedDateTime valueTimestamp;
public PropertyDto(final Property property) {
this.id = property.getId();
this.name = property.getName();
this.title = property.getTitle();
this.timestamp = property.getTimestamp();
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 java.util.List;
import java.util.Optional;
public interface PropertyRepository extends CrudRepository<Property, Long> {
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;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import lombok.AccessLevel;
import lombok.Getter;
@ -26,11 +27,11 @@ public class Schedule {
@Column(nullable = false, unique = true)
private String title;
private String propertyName;
@ManyToOne
private Property property;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private PropertyType propertyType = PropertyType.ON_OFF;
private SchedulePropertyType propertyType;
@ToString.Exclude
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)

View File

@ -1,10 +1,13 @@
package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.PropertyReadService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static de.ph87.homeautomation.shared.Helpers.mapIfNotNull;
@RestController
@RequestMapping("schedule")
@RequiredArgsConstructor
@ -14,6 +17,8 @@ public class ScheduleController {
private final ScheduleWriteService scheduleWriteService;
private final PropertyReadService propertyReadService;
@GetMapping("findAll")
public List<ScheduleDto> findAll() {
return scheduleReadService.findAllDtos();
@ -39,19 +44,19 @@ public class ScheduleController {
return scheduleWriteService.set(id, Schedule::setEnabled, enabled);
}
@PostMapping("set/{id}/name")
public ScheduleDto setName(@PathVariable final long id, @RequestBody final String name) {
return scheduleWriteService.set(id, Schedule::setTitle, name);
@PostMapping("set/{id}/title")
public ScheduleDto setTitle(@PathVariable final long id, @RequestBody final String title) {
return scheduleWriteService.set(id, Schedule::setTitle, title);
}
@PostMapping("set/{id}/propertyName")
public ScheduleDto setPropertyName(@PathVariable final long id, @RequestBody final String propertyName) {
return scheduleWriteService.set(id, Schedule::setPropertyName, propertyName);
public ScheduleDto setPropertyName(@PathVariable final long id, @RequestBody(required = false) final String propertyName) {
return scheduleWriteService.set(id, (schedule, v) -> schedule.setProperty(mapIfNotNull(v, propertyReadService::getByName)), propertyName);
}
@PostMapping("set/{id}/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;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryDto;
import lombok.Getter;
import java.util.Set;
import java.util.stream.Collectors;
import static de.ph87.homeautomation.shared.Helpers.mapIfNotNull;
@Getter
public class ScheduleDto {
@ -17,7 +20,7 @@ public class ScheduleDto {
public final String propertyName;
public final PropertyType propertyType;
public final SchedulePropertyType propertyType;
public final Set<ScheduleEntryDto> entries;
@ -25,7 +28,7 @@ public class ScheduleDto {
this.id = schedule.getId();
this.enabled = schedule.isEnabled();
this.title = schedule.getTitle();
this.propertyName = schedule.getPropertyName();
this.propertyName = mapIfNotNull(schedule.getProperty(), Property::getName);
this.propertyType = schedule.getPropertyType();
this.entries = schedule.getEntries().stream().map(ScheduleEntryDto::new).collect(Collectors.toSet());
}

View File

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