DeviceSwitch, DeviceShutter incl. UI

This commit is contained in:
Patrick Haßel 2021-11-02 11:47:20 +01:00
parent 08f1c6f93f
commit 4939d9398f
42 changed files with 586 additions and 142 deletions

View File

@ -6,7 +6,7 @@ import {environment} from "../../environments/environment";
export function NO_OP() {
}
export function NO_SORT<T>(a: T, b: T): number {
export function NO_COMPARE<T>(a: T, b: T): number {
return 0;
}
@ -32,7 +32,7 @@ export class ApiService {
this.http.get<any>(environment.apiBasePath + path).pipe(map(fromJson)).subscribe(next, errorInterceptor(error));
}
getList<T>(path: string, fromJson: (json: any) => T, compare: (a: T, b: T) => number = NO_SORT, next: (list: T[]) => void = NO_OP, error: (error: any) => void = NO_OP) {
getList<T>(path: string, fromJson: (json: any) => T, compare: (a: T, b: T) => number = NO_COMPARE, next: (list: T[]) => void = NO_OP, error: (error: any) => void = NO_OP) {
this.http.get<any[]>(environment.apiBasePath + path).pipe(map(list => list.map(fromJson).sort(compare))).subscribe(next, errorInterceptor(error));
}

View File

@ -0,0 +1,73 @@
import {validateNumberNotNull, validateStringNotEmptyNotNull} from "../validators";
import {Property} from "../property/property.service";
export abstract class Device {
constructor(
readonly id: number,
readonly title: string,
readonly type: string,
) {
// nothing
}
static fromJson(json: any): Device {
const type: string = validateStringNotEmptyNotNull(json['type']);
switch (type) {
case "DeviceSwitch":
return new DeviceSwitch(
validateNumberNotNull(json['id']),
validateStringNotEmptyNotNull(json['title']),
type,
Property.fromJsonAllowNull(json['getState']),
Property.fromJsonAllowNull(json['setState']),
);
case "DeviceShutter":
return new DeviceShutter(
validateNumberNotNull(json['id']),
validateStringNotEmptyNotNull(json['title']),
type,
Property.fromJsonAllowNull(json['getPercent']),
Property.fromJsonAllowNull(json['setPercent']),
);
}
throw new Error("No such type: " + type);
}
public static trackBy(index: number, item: Device): string {
return item.title;
}
public static compareTitle(a: Device, b: Device): number {
return a.title.localeCompare(b.title);
}
}
export class DeviceSwitch extends Device {
constructor(
id: number,
title: string,
type: string,
readonly getState: Property | null,
readonly setState: Property | null,
) {
super(id, title, type);
}
}
export class DeviceShutter extends Device {
constructor(
id: number,
title: string,
type: string,
readonly getPercent: Property | null,
readonly setPercent: Property | null,
) {
super(id, title, type);
}
}

View File

@ -0,0 +1,16 @@
import {TestBed} from '@angular/core/testing';
import {DeviceService} from './device.service';
describe('DeviceService', () => {
let service: DeviceService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(DeviceService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
import {Injectable} from '@angular/core';
import {ApiService, NO_COMPARE, NO_OP} from "../api.service";
import {Device} from "./Device";
@Injectable({
providedIn: 'root'
})
export class DeviceService {
constructor(
readonly api: ApiService,
) {
// nothing
}
findAll(next: (list: Device[]) => void, compare: (a: Device, b: Device) => number = NO_COMPARE, error: (error: any) => void = NO_OP): void {
this.api.getList("device/findAll", Device.fromJson, compare, next, error);
}
}

View File

@ -1,6 +1,6 @@
import {Injectable} from '@angular/core';
import {ApiService, NO_OP} from "../api.service";
import {validateNumberNotNull, validateStringNotEmptyNotNull} from "../validators";
import {validateBooleanAllowNull, validateDateAllowNull, validateNumberAllowNull, validateStringNotEmptyNotNull} from "../validators";
import {ISearchService} from "../ISearchService";
import {KeyValuePair} from "../KeyValuePair";
@ -8,17 +8,30 @@ export class Property {
constructor(
public name: string,
public type: string,
public value: number,
public title: string,
public propertyType: string,
public booleanValue: boolean | null,
public numberValue: number | null,
public timestamp: Date | null,
) {
// nothing
}
static fromJsonAllowNull(json: any): Property | null {
if (json === undefined || json === null) {
return null;
}
return this.fromJson(json);
}
static fromJson(json: any): Property {
return new Property(
validateStringNotEmptyNotNull(json['name']),
validateStringNotEmptyNotNull(json['type']),
validateNumberNotNull(json['value']),
validateStringNotEmptyNotNull(json['title']),
validateStringNotEmptyNotNull(json['propertyType']),
validateBooleanAllowNull(json['booleanValue']),
validateNumberAllowNull(json['numberValue']),
validateDateAllowNull(json['timestamp']),
);
}
@ -51,4 +64,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)
}
}

View File

@ -2,8 +2,10 @@ import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {ScheduleListComponent} from "./pages/schedule-list/schedule-list.component";
import {ScheduleComponent} from "./pages/schedule/schedule.component";
import {DeviceListComponent} from "./pages/device-list/device-list.component";
const routes: Routes = [
{path: 'DeviceList', component: DeviceListComponent},
{path: 'Schedule', component: ScheduleComponent},
{path: 'ScheduleList', component: ScheduleListComponent},
{path: '**', redirectTo: '/ScheduleList'},

View File

@ -4,6 +4,10 @@
Zeitpläne
</div>
<div class="item" routerLink="/DeviceList" routerLinkActive="itemActive">
Geräte
</div>
<!-- <div class="item breadcrumb" [routerLink]="['/Schedule', {id: dataService.schedule.id}]" routerLinkActive="itemActive" *ngIf="dataService.schedule">-->
<!-- {{dataService.schedule.name}}-->
<!-- </div>-->

View File

@ -7,6 +7,10 @@
border-right: 1px solid black;
}
.item:hover {
background-color: lightskyblue;
}
.breadcrumb {
background-color: lightgray;
}

View File

@ -11,6 +11,7 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {NumberComponent} from './shared/number/number.component';
import {ScheduleComponent} from "./pages/schedule/schedule.component";
import {SearchComponent} from './shared/search/search.component';
import {DeviceListComponent} from './pages/device-list/device-list.component';
@NgModule({
declarations: [
@ -20,6 +21,7 @@ import {SearchComponent} from './shared/search/search.component';
ScheduleListComponent,
NumberComponent,
SearchComponent,
DeviceListComponent,
],
imports: [
BrowserModule,

View File

@ -0,0 +1,35 @@
<ng-container *ngFor="let device of devices">
<ng-container [ngSwitch]="device.type">
<div class="device" *ngSwitchCase="'DeviceSwitch'" [class.switchOn]="isSwitchStateOn(device)" [class.switchOff]="isSwitchStateOff(device)" [class.switchUnknown]="isSwitchStateUnknown(device)">
<div class="title">
{{device.title}}
</div>
<div class="controls">
<img class="control" src="assets/switch-on.svg" (click)="setSwitchState(device, true)"/>
<img class="control" src="assets/switch-off.svg" (click)="setSwitchState(device, false)"/>
</div>
</div>
<div class="device" *ngSwitchCase="'DeviceShutter'" [class.blindOpen]="isShutterStateOpen(device)" [class.shutterBetween]="isShutterStateBetween(device)" [class.shutterClosed]="isShutterStateClosed(device)" [class.shutterUnknown]="isShutterStateUnknown(device)">
<div class="title">
{{device.title}}
</div>
<div class="controls">
<div class="control button" (click)="setShutterPercent(device, 0)">
<span class="center">Auf</span>
</div>
<div class="control button" (click)="setShutterPercent(device, 40)">
<span class="center">50%</span>
</div>
<div class="control button" (click)="setShutterPercent(device, 75)">
<span class="center">90%</span>
</div>
<div class="control button" (click)="setShutterPercent(device, 85)">
<span class="center">Schlitze</span>
</div>
<div class="control button" (click)="setShutterPercent(device, 100)">
<span class="center">Zu</span>
</div>
</div>
</div>
</ng-container>
</ng-container>

View File

@ -0,0 +1,66 @@
.device {
padding: 5px;
margin-bottom: 5px;
border-radius: 10px;
.title {
font-weight: bold;
}
.controls {
.control {
position: relative;
float: left;
width: 60px;
height: 60px;
padding: 5px;
margin: 5px;
border-radius: 25%;
.center {
position: absolute;
margin: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.button {
background-color: lightblue;
}
.control:hover {
background-color: lightskyblue;
}
}
}
.switchOn {
background-color: palegreen;
}
.switchOff {
background-color: indianred;
}
.switchUnknown {
background-color: gray;
}
.blindOpen {
background-color: palegreen;
}
.shutterBetween {
background-color: yellow;
}
.shutterClosed {
background-color: indianred;
}
.shutterUnknown {
background-color: gray;
}

View File

@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {DeviceListComponent} from './device-list.component';
describe('DeviceListComponent', () => {
let component: DeviceListComponent;
let fixture: ComponentFixture<DeviceListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DeviceListComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DeviceListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,81 @@
import {Component, OnInit} from '@angular/core';
import {DeviceService} from "../../api/device/device.service";
import {PropertyService} from "../../api/property/property.service";
import {Device, DeviceShutter, DeviceSwitch} from "../../api/device/Device";
@Component({
selector: 'app-device-list',
templateUrl: './device-list.component.html',
styleUrls: ['./device-list.component.less']
})
export class DeviceListComponent implements OnInit {
devices: Device[] = [];
constructor(
readonly deviceService: DeviceService,
readonly propertyService: PropertyService,
) {
// nothing
}
ngOnInit(): void {
this.deviceService.findAll(devices => this.devices = devices);
}
asDeviceSwitch(device: Device): DeviceSwitch {
return device as DeviceSwitch;
}
asDeviceShutter(device: Device): DeviceShutter {
return device as DeviceShutter;
}
setSwitchState(d: Device, value: boolean): void {
const device: DeviceSwitch = d as DeviceSwitch;
if (!device.setState) {
throw new Error("Property 'setState' not set for: " + device);
}
this.propertyService.set(device.setState.name, value ? 1 : 0);
}
setShutterPercent(d: Device, value: number): void {
const device: DeviceShutter = d as DeviceShutter;
if (!device.setPercent) {
throw new Error("Property 'setPercent' not set for: " + device);
}
this.propertyService.set(device.setPercent.name, value);
}
isSwitchStateOn(device: Device): boolean {
return (device as DeviceSwitch).getState?.booleanValue === true;
}
isSwitchStateOff(device: Device): boolean {
return (device as DeviceSwitch).getState?.booleanValue === false;
}
isSwitchStateUnknown(device: Device): boolean {
const value: boolean | null | undefined = (device as DeviceSwitch).getState?.booleanValue;
return value === null || value === undefined;
}
isShutterStateOpen(device: Device): boolean {
return (device as DeviceShutter).getPercent?.numberValue === 0;
}
isShutterStateBetween(device: Device): boolean {
const value: number | null | undefined = (device as DeviceShutter).getPercent?.numberValue;
return value !== null && value !== undefined && value > 0 && value < 100;
}
isShutterStateClosed(device: Device): boolean {
return (device as DeviceShutter).getPercent?.numberValue === 100;
}
isShutterStateUnknown(device: Device): boolean {
const value: number | null | undefined = (device as DeviceShutter).getPercent?.numberValue;
return value === null || value === undefined;
}
}

View File

@ -91,7 +91,6 @@ export class SearchComponent<T> implements OnInit {
}
select(result: KeyValuePair): void {
console.log(result);
this.searching = false;
this.selected = result;
this.valueChange.emit(this.selected?.key);

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 496.158 496.158" xml:space="preserve">
<path style="fill:#EB9783;" d="M496.158,248.085c0-137.021-111.07-248.082-248.076-248.082C111.07,0.003,0,111.063,0,248.085 c0,137.002,111.07,248.07,248.082,248.07C385.088,496.155,496.158,385.087,496.158,248.085z"/>
<g>
<path style="fill:#D63232;" d="M373.299,154.891c-19.558-26.212-47.401-46.023-78.401-55.787c-0.759-0.238-1.588-0.103-2.229,0.369 c-0.643,0.471-1.021,1.22-1.021,2.016l0.16,40.256c0,1.074,0.514,2.06,1.332,2.562c31.732,19.456,66.504,47,66.504,103.237 c0,61.515-50.047,111.56-111.562,111.56c-61.517,0-111.566-50.045-111.566-111.56c0-58.737,35.199-84.661,67.615-103.917 c0.836-0.496,1.363-1.492,1.363-2.58l0.154-39.909c0-0.793-0.375-1.539-1.013-2.01c-0.638-0.472-1.46-0.611-2.219-0.381 c-31.283,9.586-59.41,29.357-79.202,55.672c-20.467,27.215-31.285,59.603-31.285,93.662c0,86.099,70.049,156.146,156.152,156.146 c86.1,0,156.147-70.047,156.147-156.146C404.228,214.235,393.533,182.01,373.299,154.891z"/>
<path style="fill:#D63232;" d="M251.851,67.009h-7.549c-11.788,0-21.378,9.59-21.378,21.377v181.189 c0,11.787,9.59,21.377,21.378,21.377h7.549c11.788,0,21.378-9.59,21.378-21.377V88.386 C273.229,76.599,263.64,67.009,251.851,67.009z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 496.158 496.158" xml:space="preserve">
<path style="fill:#97EB83;" d="M496.158,248.085c0-137.021-111.07-248.082-248.076-248.082C111.07,0.003,0,111.063,0,248.085 c0,137.002,111.07,248.07,248.082,248.07C385.088,496.155,496.158,385.087,496.158,248.085z"/>
<g>
<path style="fill:#32D632;" d="M373.299,154.891c-19.558-26.212-47.401-46.023-78.401-55.787c-0.759-0.238-1.588-0.103-2.229,0.369 c-0.643,0.471-1.021,1.22-1.021,2.016l0.16,40.256c0,1.074,0.514,2.06,1.332,2.562c31.732,19.456,66.504,47,66.504,103.237 c0,61.515-50.047,111.56-111.562,111.56c-61.517,0-111.566-50.045-111.566-111.56c0-58.737,35.199-84.661,67.615-103.917 c0.836-0.496,1.363-1.492,1.363-2.58l0.154-39.909c0-0.793-0.375-1.539-1.013-2.01c-0.638-0.472-1.46-0.611-2.219-0.381 c-31.283,9.586-59.41,29.357-79.202,55.672c-20.467,27.215-31.285,59.603-31.285,93.662c0,86.099,70.049,156.146,156.152,156.146 c86.1,0,156.147-70.047,156.147-156.146C404.228,214.235,393.533,182.01,373.299,154.891z"/>
<path style="fill:#32D632;" d="M251.851,67.009h-7.549c-11.788,0-21.378,9.59-21.378,21.377v181.189 c0,11.787,9.59,21.377,21.378,21.377h7.549c11.788,0,21.378-9.59,21.378-21.377V88.386 C273.229,76.599,263.64,67.009,251.851,67.009z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,9 +1,11 @@
package de.ph87.homeautomation;
import com.luckycatlabs.sunrisesunset.Zenith;
import de.ph87.homeautomation.device.DeviceWriteService;
import de.ph87.homeautomation.knx.group.KnxGroupDto;
import de.ph87.homeautomation.knx.group.KnxGroupRepository;
import de.ph87.homeautomation.knx.group.KnxGroupWriteService;
import de.ph87.homeautomation.property.PropertyDto;
import de.ph87.homeautomation.property.PropertyType;
import de.ph87.homeautomation.schedule.Schedule;
import de.ph87.homeautomation.schedule.ScheduleRepository;
@ -23,7 +25,9 @@ import java.time.ZonedDateTime;
@RequiredArgsConstructor
public class DemoDataService {
public static final int MIN30 = 30 * 60;
private static final int MIN30 = 30 * 60;
private static final Zenith BETWEEN_OFFICIAL_AND_CIVIL = new Zenith(93.0);
private final KnxGroupWriteService knxGroupWriteService;
@ -31,19 +35,25 @@ public class DemoDataService {
private final KnxGroupRepository knxGroupRepository;
private final DeviceWriteService deviceWriteService;
@PostConstruct
public void postConstruct() {
final KnxGroupDto eg_flur_licht_schalten = createKnxGroupIfNotExists("EG Flur Licht Schalten", 1294, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto eg_ambiente_schalten = createKnxGroupIfNotExists("EG Ambiente Schalten", 848, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto og_ambiente_schalten = createKnxGroupIfNotExists("OG Ambiente Schalten", 1539, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto wohnzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Wohnzimmer Rollladen Position Anfahren", new GroupAddress(0, 4, 24), "5.001", PropertyType.PERCENT, false, false);
final KnxGroupDto schlafzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Schlafzimmer Rollladen Position Anfahren", new GroupAddress(0, 3, 3), "5.001", PropertyType.PERCENT, false, false);
final KnxGroupDto flur_rollladen_position_anfahren = createKnxGroupIfNotExists("Flur Rollladen Position Anfahren", new GroupAddress(0, 5, 13), "5.001", PropertyType.PERCENT, false, false);
final KnxGroupDto bad_licht_mitte_schalten = createKnxGroupIfNotExists("Bad Licht Mitte Schalten", 797, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto helligkeit = createKnxGroupIfNotExists("Helligkeit", 1286, "9.004", PropertyType.LUX, false, true);
final KnxGroupDto eg_flur_licht_schalten = createKnxGroupIfNotExists("EG Flur Licht Schalten", 0, 5, 14, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto eg_ambiente_schalten = createKnxGroupIfNotExists("EG Ambiente Schalten", 0, 3, 80, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto og_ambiente_schalten = createKnxGroupIfNotExists("OG Ambiente Schalten", 0, 6, 3, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto wohnzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Wohnzimmer Rollladen Position Anfahren", 0, 4, 24, "5.001", PropertyType.PERCENT, false, false);
final KnxGroupDto schlafzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Schlafzimmer Rollladen Position Anfahren", 0, 3, 3, "5.001", PropertyType.PERCENT, false, false);
final KnxGroupDto flur_rollladen_position_anfahren = createKnxGroupIfNotExists("Flur Rollladen Position Anfahren", 0, 5, 13, "5.001", PropertyType.PERCENT, false, false);
final KnxGroupDto bad_licht_mitte_schalten = createKnxGroupIfNotExists("Bad Licht Mitte Schalten", 0, 3, 29, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto bad_licht_mitte_status = createKnxGroupIfNotExists("Bad Licht Mitte Status", 0, 3, 30, "1.001", PropertyType.ON_OFF, true, false);
final KnxGroupDto helligkeit = createKnxGroupIfNotExists("Helligkeit", 0, 5, 6, "9.004", PropertyType.LUX, false, true);
deviceWriteService.createDeviceSwitch("Bad Licht Mitte", bad_licht_mitte_status, bad_licht_mitte_schalten);
deviceWriteService.createDeviceShutter("Flur OG Rollladen", null, flur_rollladen_position_anfahren);
if (scheduleRepository.count() == 0) {
final Schedule scheduleEgFlurLicht = createSchedule("EG Flur Licht", eg_flur_licht_schalten, PropertyType.ON_OFF);
final Schedule scheduleEgFlurLicht = createSchedule("EG Flur Licht", eg_flur_licht_schalten);
createTime(scheduleEgFlurLicht, 11, 30, 0, MIN30, true);
createTime(scheduleEgFlurLicht, 12, 30, 0, MIN30, false);
createTime(scheduleEgFlurLicht, 16, 30, 0, MIN30, true);
@ -54,36 +64,36 @@ public class DemoDataService {
createTime(scheduleEgFlurLicht, 2, 0, 0, MIN30, false);
scheduleRepository.save(scheduleEgFlurLicht);
final Schedule scheduleEgAmbiente = createSchedule("EG Ambiente", eg_ambiente_schalten, PropertyType.ON_OFF);
final Schedule scheduleEgAmbiente = createSchedule("EG Ambiente", eg_ambiente_schalten);
createTime(scheduleEgAmbiente, 7, 15, 0, MIN30, true);
createTime(scheduleEgAmbiente, 9, 30, 0, MIN30, false);
createSunset(scheduleEgAmbiente, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleEgAmbiente, Zenith.ASTRONOMICAL, MIN30, false);
scheduleRepository.save(scheduleEgAmbiente);
final Schedule scheduleOgAmbiente = createSchedule("OG Ambiente", og_ambiente_schalten, PropertyType.ON_OFF);
final Schedule scheduleOgAmbiente = createSchedule("OG Ambiente", og_ambiente_schalten);
createTime(scheduleOgAmbiente, 7, 15, 0, MIN30, true);
createTime(scheduleOgAmbiente, 9, 30, 0, MIN30, false);
createSunset(scheduleOgAmbiente, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleOgAmbiente, Zenith.ASTRONOMICAL, MIN30, false);
scheduleRepository.save(scheduleOgAmbiente);
final Schedule scheduleWohnzimmerRollladen = createSchedule("Rollläden Wohnzimmer", wohnzimmer_rollladen_position_anfahren, PropertyType.SHUTTER);
createSunrise(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, 0);
createSunset(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, 100);
final Schedule scheduleWohnzimmerRollladen = createSchedule("Rollläden Wohnzimmer", wohnzimmer_rollladen_position_anfahren);
createSunrise(scheduleWohnzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0);
createSunset(scheduleWohnzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
scheduleRepository.save(scheduleWohnzimmerRollladen);
final Schedule scheduleSchlafzimmerRollladen = createSchedule("Rollläden Schlafzimmer", schlafzimmer_rollladen_position_anfahren, PropertyType.SHUTTER);
final Schedule scheduleSchlafzimmerRollladen = createSchedule("Rollläden Schlafzimmer", schlafzimmer_rollladen_position_anfahren);
createTime(scheduleSchlafzimmerRollladen, 7, 0, 0, 0, 0);
createSunset(scheduleSchlafzimmerRollladen, Zenith.CIVIL, 0, 100);
createSunset(scheduleSchlafzimmerRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
scheduleRepository.save(scheduleSchlafzimmerRollladen);
final Schedule scheduleFlurRollladen = createSchedule("Rollladen Flur", flur_rollladen_position_anfahren, PropertyType.SHUTTER);
createSunrise(scheduleFlurRollladen, Zenith.CIVIL, 0, 0);
createSunset(scheduleFlurRollladen, Zenith.CIVIL, 0, 100);
final Schedule scheduleFlurRollladen = createSchedule("Rollladen Flur", flur_rollladen_position_anfahren);
createSunrise(scheduleFlurRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 0);
createSunset(scheduleFlurRollladen, BETWEEN_OFFICIAL_AND_CIVIL, 0, 100);
scheduleRepository.save(scheduleFlurRollladen);
final Schedule scheduleBadLichtMitte = createSchedule("Bad Licht Mitte", bad_licht_mitte_schalten, PropertyType.ON_OFF);
final Schedule scheduleBadLichtMitte = createSchedule("Bad Licht Mitte", bad_licht_mitte_schalten);
createTime(scheduleBadLichtMitte, 10, 30, 0, MIN30, true);
createTime(scheduleBadLichtMitte, 11, 30, 0, MIN30, false);
createTime(scheduleBadLichtMitte, 15, 30, 0, MIN30, true);
@ -96,20 +106,17 @@ public class DemoDataService {
}
}
private Schedule createSchedule(final String s, final KnxGroupDto knxGroupDto, final PropertyType propertyType) {
private Schedule createSchedule(final String s, final PropertyDto propertyDto) {
final Schedule schedule = new Schedule();
schedule.setEnabled(true);
schedule.setName(s);
schedule.setPropertyName(knxGroupDto.propertyName);
schedule.setPropertyType(propertyType);
schedule.setPropertyName(propertyDto.getName());
schedule.setPropertyType(propertyDto.getPropertyType());
return schedule;
}
private KnxGroupDto createKnxGroupIfNotExists(final String name, final int address, final String dpt, final PropertyType type, final boolean readable, final boolean multiGroup) {
return createKnxGroupIfNotExists(name, new GroupAddress(address), dpt, type, readable, multiGroup);
}
private KnxGroupDto createKnxGroupIfNotExists(final String name, final GroupAddress address, final String dpt, final PropertyType type, final boolean readable, final boolean multiGroup) {
private KnxGroupDto createKnxGroupIfNotExists(final String name, final int main, final int mid, final int sub, final String dpt, final PropertyType type, final boolean readable, final boolean multiGroup) {
final GroupAddress address = new GroupAddress(main, mid, sub);
return knxGroupRepository.findByAddressRaw(address.getRawAddress()).map(KnxGroupDto::new).orElseGet(() -> knxGroupWriteService.create(name, address, dpt, type, readable, multiGroup));
}

View File

@ -1,9 +1,9 @@
package de.ph87.homeautomation.device;
import de.ph87.homeautomation.device.devices.DeviceDto;
import de.ph87.homeautomation.property.PropertySetException;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -14,9 +14,16 @@ public class DeviceController {
private final DeviceReadService deviceReadService;
private final DeviceWriteService deviceWriteService;
@GetMapping("findAll")
public List<DeviceDto> findAll() {
return deviceReadService.findAll();
}
@PostMapping("set")
public void set(@RequestBody final DeviceSetDto dto) throws PropertySetException {
deviceWriteService.set(dto);
}
}

View File

@ -1,6 +1,7 @@
package de.ph87.homeautomation.device;
import de.ph87.homeautomation.device.devices.*;
import de.ph87.homeautomation.property.PropertyDto;
import de.ph87.homeautomation.property.PropertyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -10,6 +11,8 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
import static de.ph87.homeautomation.shared.Helpers.mapIfNotNull;
@Slf4j
@Service
@Transactional
@ -27,12 +30,16 @@ public class DeviceReadService {
private DeviceDto toDto(final Device device) {
if (device instanceof DeviceSwitch) {
final DeviceSwitch deviceSwitch = (DeviceSwitch) device;
return new DeviceSwitchDto(deviceSwitch, propertyService.readBoolean(deviceSwitch.getStatePropertyName()));
} else if (device instanceof DeviceNumber) {
final DeviceNumber deviceNumber = (DeviceNumber) device;
return new DeviceNumberDto(deviceNumber, propertyService.readNumber(deviceNumber.getValuePropertyName()));
final PropertyDto getState = mapIfNotNull(deviceSwitch.getGetState(), propertyService::getById);
final PropertyDto setState = mapIfNotNull(deviceSwitch.getSetState(), propertyService::getById);
return new DeviceSwitchDto(deviceSwitch, getState, setState);
} else if (device instanceof DeviceShutter) {
final DeviceShutter deviceShutter = (DeviceShutter) device;
final PropertyDto getPercent = mapIfNotNull(deviceShutter.getGetPercent(), propertyService::getById);
final PropertyDto setPercent = mapIfNotNull(deviceShutter.getSetPercent(), propertyService::getById);
return new DeviceShutterDto(deviceShutter, getPercent, setPercent);
}
throw new RuntimeException("Not imeplemented: toDto(" + device + ")");
throw new RuntimeException("Not implemented: toDto(" + device + ")");
}
}

View File

@ -0,0 +1,14 @@
package de.ph87.homeautomation.device;
import lombok.Data;
@Data
public class DeviceSetDto {
private long id;
private String property;
private double value;
}

View File

@ -1,13 +1,18 @@
package de.ph87.homeautomation.device;
import de.ph87.homeautomation.device.devices.DeviceNumber;
import de.ph87.homeautomation.device.devices.Device;
import de.ph87.homeautomation.device.devices.DeviceShutter;
import de.ph87.homeautomation.device.devices.DeviceSwitch;
import de.ph87.homeautomation.property.PropertyDto;
import de.ph87.homeautomation.property.PropertyService;
import de.ph87.homeautomation.property.PropertySetException;
import de.ph87.office.web.NotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import static de.ph87.homeautomation.shared.Helpers.mapIfNotNull;
@Slf4j
@Service
@ -17,17 +22,49 @@ public class DeviceWriteService {
private final DeviceRepository deviceRepository;
@PostConstruct
public void postConstruct() {
private final PropertyService propertyService;
public void createDeviceSwitch(final String title, final PropertyDto getState, final PropertyDto setState) {
final DeviceSwitch deviceSwitch = new DeviceSwitch();
deviceSwitch.setName("TEST");
deviceSwitch.setStatePropertyName("knx.group.0.3.6");
deviceSwitch.setTitle(title);
deviceSwitch.setGetState(mapIfNotNull(getState, PropertyDto::getName));
deviceSwitch.setSetState(mapIfNotNull(setState, PropertyDto::getName));
deviceRepository.save(deviceSwitch);
}
final DeviceNumber deviceNumber = new DeviceNumber();
deviceNumber.setName("Helligkeit");
deviceNumber.setValuePropertyName("knx.group.0.5.6");
deviceRepository.save(deviceNumber);
public void createDeviceShutter(final String title, final PropertyDto getPercent, final PropertyDto setPercent) {
final DeviceShutter deviceShutter = new DeviceShutter();
deviceShutter.setTitle(title);
deviceShutter.setGetPercent(mapIfNotNull(getPercent, PropertyDto::getName));
deviceShutter.setSetPercent(mapIfNotNull(setPercent, PropertyDto::getName));
deviceRepository.save(deviceShutter);
}
public void set(final DeviceSetDto dto) throws PropertySetException {
final Device device = 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) throws PropertySetException {
switch (property) {
case "switch":
propertyService.set(device.getSetState(), value);
}
}
private void setShutter(final DeviceShutter device, final String property, final double value) throws PropertySetException {
switch (property) {
case "percent":
propertyService.set(device.getSetPercent(), value);
}
}
private Device getById(final long id) {
return deviceRepository.findById(id).orElseThrow(() -> new NotFoundException("Device.id=%d", id));
}
}

View File

@ -19,6 +19,6 @@ public abstract class Device {
@Setter(AccessLevel.NONE)
private Long id;
private String name;
private String title;
}

View File

@ -1,6 +1,5 @@
package de.ph87.homeautomation.device;
package de.ph87.homeautomation.device.devices;
import de.ph87.homeautomation.device.devices.Device;
import lombok.Getter;
@Getter
@ -8,13 +7,13 @@ public abstract class DeviceDto {
public final long id;
public final String name;
public final String title;
public final String type;
public DeviceDto(final Device device) {
this.id = device.getId();
this.name = device.getName();
this.title = device.getTitle();
this.type = device.getClass().getSimpleName();
}

View File

@ -1,16 +0,0 @@
package de.ph87.homeautomation.device.devices;
import de.ph87.homeautomation.device.DeviceDto;
import lombok.Getter;
@Getter
public class DeviceNumberDto extends DeviceDto {
public final Number value;
public DeviceNumberDto(final DeviceNumber device, final Number value) {
super(device);
this.value = value;
}
}

View File

@ -10,8 +10,10 @@ import javax.persistence.Entity;
@Setter
@ToString
@Entity
public class DeviceNumber extends Device {
public class DeviceShutter extends Device {
private String valuePropertyName;
private String getPercent;
private String setPercent;
}

View File

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

View File

@ -12,8 +12,8 @@ import javax.persistence.Entity;
@Entity
public class DeviceSwitch extends Device {
private String switchPropertyName;
private String setState;
private String statePropertyName;
private String getState;
}

View File

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

View File

@ -7,7 +7,6 @@ import de.ph87.homeautomation.shared.AbstractThreadService;
import de.ph87.network.router.Router;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;
import tuwien.auto.calimero.CloseEvent;
@ -153,9 +152,8 @@ public class KnxThreadService extends AbstractThreadService implements NetworkLi
// ignore
}
@EventListener(KnxThreadWakeUpEvent.class)
@TransactionalEventListener
public void wakeUp() {
public void wakeUp(final KnxThreadWakeUpEvent event) {
_wakeUp();
}

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.property.IProperty;
import de.ph87.homeautomation.property.PropertyType;
import lombok.AccessLevel;
import lombok.Getter;
@ -14,7 +15,7 @@ import java.time.ZonedDateTime;
@Setter
@ToString
@Entity
public class KnxGroup {
public class KnxGroup implements IProperty {
@Id
@GeneratedValue

View File

@ -1,12 +1,10 @@
package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.property.PropertyType;
import de.ph87.homeautomation.property.PropertyDto;
import lombok.Getter;
import java.time.ZonedDateTime;
@Getter
public class KnxGroupDto {
public class KnxGroupDto extends PropertyDto {
public final long id;
@ -14,32 +12,14 @@ public class KnxGroupDto {
public final String addressStr;
public final String propertyName;
public final String dpt;
public final String name;
public final PropertyType propertyType;
public final Boolean booleanValue;
public final Double numberValue;
public final ZonedDateTime valueTimestamp;
public KnxGroupDto(final KnxGroup knxGroup) {
super(knxGroup.getPropertyName(), knxGroup.getTitle(), knxGroup.getPropertyType(), knxGroup.getBooleanValue(), knxGroup.getNumberValue(), knxGroup.getValueTimestamp());
id = knxGroup.getId();
addressRaw = knxGroup.getAddressRaw();
addressStr = knxGroup.getAddressStr();
propertyName = knxGroup.getPropertyName();
dpt = knxGroup.getDpt();
name = knxGroup.getTitle();
propertyType = knxGroup.getPropertyType();
booleanValue = knxGroup.getBooleanValue();
numberValue = knxGroup.getNumberValue();
valueTimestamp = knxGroup.getValueTimestamp();
}
}

View File

@ -1,6 +1,5 @@
package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.knx.KnxThreadService;
import de.ph87.homeautomation.property.IPropertyOwner;
import de.ph87.homeautomation.property.PropertyDto;
import de.ph87.homeautomation.property.PropertySetException;
@ -11,6 +10,7 @@ import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tuwien.auto.calimero.GroupAddress;
import java.util.List;
@ -23,14 +23,13 @@ import static de.ph87.homeautomation.shared.Helpers.quoteOrNull;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class KnxGroupSetService implements IPropertyOwner {
@Getter
private final Pattern propertyNamePattern = Pattern.compile("^knx\\.group\\.(\\d+)\\.(\\d+)\\.(\\d+)$");
private final KnxThreadService knxThreadService;
private final KnxGroupWriteService knxGroupWriteService;
private final KnxGroupRepository knxGroupRepository;
@ -68,28 +67,18 @@ public class KnxGroupSetService implements IPropertyOwner {
}
@Override
public Optional<PropertyDto> findPropertyByName(final String propertyName) {
return knxGroupRepository.findByPropertyName(propertyName).map(this::toPropertyDto);
public Optional<KnxGroupDto> findPropertyByName(final String propertyName) {
return knxGroupRepository.findByPropertyName(propertyName).map(KnxGroupDto::new);
}
@Override
public List<PropertyDto> findAllProperties() {
return knxGroupRepository.findAll().stream().map(this::toPropertyDto).collect(Collectors.toList());
return knxGroupRepository.findAll().stream().map(KnxGroupDto::new).collect(Collectors.toList());
}
@Override
public List<PropertyDto> findAllPropertiesLike(final String like) {
return knxGroupRepository.findAllByPropertyNameLikeIgnoreCaseOrTitleLikeIgnoreCase(like, like).stream().map(this::toPropertyDto).collect(Collectors.toList());
}
private PropertyDto toPropertyDto(final KnxGroup knxGroup) {
return new PropertyDto(
knxGroup.getPropertyName(),
knxGroup.getTitle(),
knxGroup.getBooleanValue(),
knxGroup.getNumberValue(),
knxGroup.getValueTimestamp()
);
return knxGroupRepository.findAllByPropertyNameLikeIgnoreCaseOrTitleLikeIgnoreCase(like, like).stream().map(KnxGroupDto::new).collect(Collectors.toList());
}
private GroupAddress parseGroupAddress(final String propertyName) {

View File

@ -125,7 +125,6 @@ public class KnxGroupWriteService {
return false;
}
final KnxGroup knxGroup = knxGroupOptional.get();
try {
final DPTXlator translator = findTranslator(knxGroup);
if (translator instanceof DPTXlatorBoolean) {

View File

@ -0,0 +1,7 @@
package de.ph87.homeautomation.property;
public interface IProperty {
String getPropertyName();
}

View File

@ -18,6 +18,6 @@ public interface IPropertyOwner {
List<PropertyDto> findAllPropertiesLike(final String like);
Optional<PropertyDto> findPropertyByName(final String propertyName);
Optional<? extends PropertyDto> findPropertyByName(final String propertyName);
}

View File

@ -31,4 +31,9 @@ public class PropertyController implements ISearchController {
return propertyService.findAllLike("%" + term + "%").stream().map(propertyDto -> new KeyValuePair(propertyDto.name, propertyDto.title)).collect(Collectors.toList());
}
@PostMapping("set")
public void set(@RequestBody final PropertySetDto dto) throws PropertySetException {
propertyService.set(dto.getName(), dto.getValue());
}
}

View File

@ -11,15 +11,18 @@ public class PropertyDto {
public final String title;
public final PropertyType propertyType;
public final Boolean booleanValue;
public final Number numberValue;
public final ZonedDateTime timestamp;
public PropertyDto(final String name, final String title, final Boolean booleanValue, final Number numberValue, final ZonedDateTime timestamp) {
public PropertyDto(final String name, final String title, final PropertyType propertyType, final Boolean booleanValue, final Number numberValue, final ZonedDateTime timestamp) {
this.name = name;
this.title = title;
this.propertyType = propertyType;
this.booleanValue = booleanValue;
this.numberValue = numberValue;
this.timestamp = timestamp;

View File

@ -1,12 +1,15 @@
package de.ph87.homeautomation.property;
import de.ph87.homeautomation.shared.Helpers;
import de.ph87.office.web.NotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
@Slf4j
@Service
@ -34,17 +37,17 @@ public class PropertyService {
}
public List<PropertyDto> findAll() {
return propertyOwners.stream().map(IPropertyOwner::findAllProperties).reduce(new ArrayList<>(), Helpers::merge);
return mergeDtoLists(propertyOwners.stream().map(IPropertyOwner::findAllProperties));
}
public List<PropertyDto> findAllLike(final String like) {
return propertyOwners.stream().map(iProperyOwner -> iProperyOwner.findAllPropertiesLike(like)).reduce(PropertyService::merge).orElse(Collections.emptyList());
return mergeDtoLists(propertyOwners.stream().map(iProperyOwner -> iProperyOwner.findAllPropertiesLike(like)));
}
private static <T> List<T> merge(final List<T> a, final List<T> b) {
final ArrayList<T> c = new ArrayList<>(a);
c.addAll(b);
return c;
private List<PropertyDto> mergeDtoLists(final Stream<? extends List<? extends PropertyDto>> stream) {
final List<PropertyDto> result = new ArrayList<>();
stream.forEach(result::addAll);
return result;
}
public PropertyDto getById(final String propertyName) {

View File

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

View File

@ -2,9 +2,17 @@ package de.ph87.homeautomation.shared;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class Helpers {
public static <T, U> U mapIfNotNull(final T value, final Function<T, U> map) {
if (value == null) {
return null;
}
return map.apply(value);
}
public static String quoteOrNull(final String value) {
if (value == null) {
return null;
@ -33,7 +41,7 @@ public class Helpers {
return result;
}
public static <T> List<T> merge(final List<T> a, final List<T> b) {
public static <T> List<T> merge(final List<? extends T> a, final List<? extends T> b) {
final List<T> c = new ArrayList<>(a);
c.addAll(b);
return c;