ChannelList + Read/Write-Channel in PropertyList
This commit is contained in:
parent
5b769dd8ce
commit
0534cf0bae
@ -1,9 +1,9 @@
|
|||||||
import {KeyValuePair} from "./KeyValuePair";
|
import {SearchResult} from "./SearchResult";
|
||||||
|
|
||||||
export interface ISearchService {
|
export interface ISearchService {
|
||||||
|
|
||||||
get(id: number, next: (results: KeyValuePair) => void, error: (error: any) => void): void;
|
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void;
|
||||||
|
|
||||||
search(term: string, next: (results: KeyValuePair[]) => void, error: (error: any) => void): void;
|
search(term: string, next: (results: SearchResult[]) => void, error: (error: any) => void): void;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
import {validateStringNotEmptyNotNull} from "./validators";
|
|
||||||
|
|
||||||
export class KeyValuePair {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
readonly key: string,
|
|
||||||
readonly value: string,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json: any): KeyValuePair {
|
|
||||||
return new KeyValuePair(
|
|
||||||
validateStringNotEmptyNotNull(json['key']),
|
|
||||||
validateStringNotEmptyNotNull(json['value']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static trackBy(index: number, item: KeyValuePair): string {
|
|
||||||
return item.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static compareKey(a: KeyValuePair, b: KeyValuePair): number {
|
|
||||||
return a.value.localeCompare(b.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
26
src/main/angular/src/app/api/SearchResult.ts
Normal file
26
src/main/angular/src/app/api/SearchResult.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {validateNumberNotNull, validateStringNotEmptyNotNull} from "./validators";
|
||||||
|
|
||||||
|
export class SearchResult {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly id: number,
|
||||||
|
readonly title: string,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json: any): SearchResult {
|
||||||
|
return new SearchResult(
|
||||||
|
validateNumberNotNull(json['id']),
|
||||||
|
validateStringNotEmptyNotNull(json['title']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static trackBy(index: number, item: SearchResult): string {
|
||||||
|
return item.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static compareTitle(a: SearchResult, b: SearchResult): number {
|
||||||
|
return a.title.localeCompare(b.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
85
src/main/angular/src/app/api/channel/Channel.ts
Normal file
85
src/main/angular/src/app/api/channel/Channel.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import {validateBooleanNotNull, validateDateAllowNull, validateNumberAllowNull, validateNumberNotNull, validateStringEmptyToNull, validateStringNotEmptyNotNull} from "../validators";
|
||||||
|
import {prefix} from "../../helpers";
|
||||||
|
|
||||||
|
export abstract class Channel {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly id: number,
|
||||||
|
readonly title: string,
|
||||||
|
readonly type: string,
|
||||||
|
) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJsonAllowNull(json: any): Channel | null {
|
||||||
|
if (!json) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json: any): Channel {
|
||||||
|
const type: string = validateStringNotEmptyNotNull(json['type']);
|
||||||
|
switch (type) {
|
||||||
|
case "KnxGroup":
|
||||||
|
return new KnxGroup(
|
||||||
|
validateNumberNotNull(json['id']),
|
||||||
|
validateStringNotEmptyNotNull(json['title']),
|
||||||
|
type,
|
||||||
|
validateNumberNotNull(json['addressRaw']),
|
||||||
|
validateStringNotEmptyNotNull(json['addressStr']),
|
||||||
|
validateNumberNotNull(json['dptMain']),
|
||||||
|
validateNumberNotNull(json['dptSub']),
|
||||||
|
validateStringEmptyToNull(json['description']),
|
||||||
|
validateNumberNotNull(json['puid']),
|
||||||
|
validateBooleanNotNull(json['ets']),
|
||||||
|
validateNumberAllowNull(json['value']),
|
||||||
|
validateDateAllowNull(json['timestamp']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error("No such type: " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static trackBy(index: number, item: Channel): number {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static compareTypeThenTitle(a: Channel, b: Channel): number {
|
||||||
|
const type: number = -a.type.localeCompare(b.type);
|
||||||
|
if (type !== 0) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
return a.title.localeCompare(b.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KnxGroup extends Channel {
|
||||||
|
|
||||||
|
public addresMain: number;
|
||||||
|
public addresMid: number;
|
||||||
|
public addresSub: number;
|
||||||
|
public dpt: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: number,
|
||||||
|
title: string,
|
||||||
|
type: string,
|
||||||
|
public addressRaw: number,
|
||||||
|
public addressStr: string,
|
||||||
|
public dptMain: number,
|
||||||
|
public dptSub: number,
|
||||||
|
public description: string | null,
|
||||||
|
public puid: number,
|
||||||
|
public ets: boolean,
|
||||||
|
public value: number | null,
|
||||||
|
public timestamp: Date | null,
|
||||||
|
) {
|
||||||
|
super(id, title, type);
|
||||||
|
const addressParts = this.addressStr.split("/");
|
||||||
|
this.addresMain = parseInt(addressParts[0]);
|
||||||
|
this.addresMid = parseInt(addressParts[1]);
|
||||||
|
this.addresSub = parseInt(addressParts[2]);
|
||||||
|
this.dpt = this.dptMain + "." + prefix(this.dptSub, "0", 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
src/main/angular/src/app/api/channel/channel.service.spec.ts
Normal file
16
src/main/angular/src/app/api/channel/channel.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {ChannelService} from './channel.service';
|
||||||
|
|
||||||
|
describe('ChannelService', () => {
|
||||||
|
let service: ChannelService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ChannelService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
39
src/main/angular/src/app/api/channel/channel.service.ts
Normal file
39
src/main/angular/src/app/api/channel/channel.service.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {ApiService, NO_COMPARE, NO_OP} from "../api.service";
|
||||||
|
import {ISearchService} from "../ISearchService";
|
||||||
|
import {SearchResult} from "../SearchResult";
|
||||||
|
import {Update} from "../Update";
|
||||||
|
import {Channel} from "./Channel";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ChannelService implements ISearchService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly api: ApiService,
|
||||||
|
) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll(next: (list: Channel[]) => void, compare: (a: Channel, b: Channel) => number = NO_COMPARE, error: (error: any) => void = NO_OP): void {
|
||||||
|
this.api.getList("channel/findAll", Channel.fromJson, compare, next, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(next: (channel: Update<Channel>) => void): void {
|
||||||
|
this.api.subscribe("ChannelDto", Channel.fromJson, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
|
||||||
|
this.api.getItem("channel/getById/" + id, SearchResult.fromJson, next, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(term: string, next: (results: SearchResult[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
||||||
|
this.api.postReturnList("channel/searchLike", term, SearchResult.fromJson, next, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(channel: Channel, key: string, value: any, next: (item: Channel) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
||||||
|
this.api.postReturnItem("channel/set/" + channel.id + "/" + key, value, Channel.fromJson, next, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import {validateDateAllowNull, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull} from "../validators";
|
import {validateDateAllowNull, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull} from "../validators";
|
||||||
|
import {Channel} from "../channel/Channel";
|
||||||
|
|
||||||
export class Property {
|
export class Property {
|
||||||
|
|
||||||
@ -9,6 +10,8 @@ export class Property {
|
|||||||
public title: string,
|
public title: string,
|
||||||
public value: number | null,
|
public value: number | null,
|
||||||
public timestamp: Date | null,
|
public timestamp: Date | null,
|
||||||
|
public readChannel: Channel | null,
|
||||||
|
public writeChannel: Channel | null,
|
||||||
) {
|
) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
@ -28,11 +31,13 @@ export class Property {
|
|||||||
validateStringNotEmptyNotNull(json['title']),
|
validateStringNotEmptyNotNull(json['title']),
|
||||||
validateNumberAllowNull(json['value']),
|
validateNumberAllowNull(json['value']),
|
||||||
validateDateAllowNull(json['timestamp']),
|
validateDateAllowNull(json['timestamp']),
|
||||||
|
Channel.fromJsonAllowNull(json['readChannel']),
|
||||||
|
Channel.fromJsonAllowNull(json['writeChannel']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static trackBy(index: number, item: Property): string {
|
public static trackBy(index: number, item: Property): number {
|
||||||
return item.name;
|
return item.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static compareTypeThenTitle(a: Property, b: Property): number {
|
public static compareTypeThenTitle(a: Property, b: Property): number {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {ApiService, NO_COMPARE, NO_OP} from "../api.service";
|
import {ApiService, NO_COMPARE, NO_OP} from "../api.service";
|
||||||
import {ISearchService} from "../ISearchService";
|
import {ISearchService} from "../ISearchService";
|
||||||
import {KeyValuePair} from "../KeyValuePair";
|
import {SearchResult} from "../SearchResult";
|
||||||
import {Update} from "../Update";
|
import {Update} from "../Update";
|
||||||
import {Property} from "./Property";
|
import {Property} from "./Property";
|
||||||
|
|
||||||
@ -24,12 +24,12 @@ export class PropertyService implements ISearchService {
|
|||||||
this.api.subscribe("PropertyDto", Property.fromJson, next);
|
this.api.subscribe("PropertyDto", Property.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(id: number, next: (results: KeyValuePair) => void, error: (error: any) => void): void {
|
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
|
||||||
this.api.getItem("property/getById/" + id, KeyValuePair.fromJson, next, error);
|
this.api.getItem("property/getById/" + id, SearchResult.fromJson, next, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
search(term: string, next: (results: KeyValuePair[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
search(term: string, next: (results: SearchResult[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
||||||
this.api.postReturnList("property/searchLike", term, KeyValuePair.fromJson, next, error);
|
this.api.postReturnList("property/searchLike", term, SearchResult.fromJson, next, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(property: Property, key: string, value: any, next: (item: Property) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
set(property: Property, key: string, value: any, next: (item: Property) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {ApiService, NO_COMPARE, NO_OP} from "../api.service";
|
import {ApiService, NO_COMPARE, NO_OP} from "../api.service";
|
||||||
import {ISearchService} from "../ISearchService";
|
import {ISearchService} from "../ISearchService";
|
||||||
import {KeyValuePair} from "../KeyValuePair";
|
import {SearchResult} from "../SearchResult";
|
||||||
import {Update} from "../Update";
|
import {Update} from "../Update";
|
||||||
import {Scene} from "./Scene";
|
import {Scene} from "./Scene";
|
||||||
|
|
||||||
@ -24,12 +24,12 @@ export class SceneService implements ISearchService {
|
|||||||
this.api.subscribe("SceneDto", Scene.fromJson, next);
|
this.api.subscribe("SceneDto", Scene.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(id: number, next: (results: KeyValuePair) => void, error: (error: any) => void): void {
|
get(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
|
||||||
this.api.getItem("scene/getById/" + id, KeyValuePair.fromJson, next, error);
|
this.api.getItem("scene/getById/" + id, SearchResult.fromJson, next, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
search(term: string, next: (results: KeyValuePair[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
search(term: string, next: (results: SearchResult[]) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
||||||
this.api.postReturnList("scene/searchLike", term, KeyValuePair.fromJson, next, error);
|
this.api.postReturnList("scene/searchLike", term, SearchResult.fromJson, next, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(scene: Scene, key: string, value: any, next: (item: Scene) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
set(scene: Scene, key: string, value: any, next: (item: Scene) => void = NO_OP, error: (error: any) => void = NO_OP): void {
|
||||||
|
|||||||
@ -5,10 +5,13 @@ import {ScheduleComponent} from "./pages/schedule/schedule.component";
|
|||||||
import {DeviceListComponent} from "./pages/device-list/device-list.component";
|
import {DeviceListComponent} from "./pages/device-list/device-list.component";
|
||||||
import {DeviceComponent} from "./pages/device/device.component";
|
import {DeviceComponent} from "./pages/device/device.component";
|
||||||
import {PropertyListComponent} from "./pages/property-list/property-list.component";
|
import {PropertyListComponent} from "./pages/property-list/property-list.component";
|
||||||
|
import {ChannelListComponent} from "./pages/channel-list/channel-list.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: 'Device', component: DeviceComponent},
|
{path: 'Device', component: DeviceComponent},
|
||||||
{path: 'DeviceList', component: DeviceListComponent},
|
{path: 'DeviceList', component: DeviceListComponent},
|
||||||
|
// {path: 'Channel', component: ChannelComponent},
|
||||||
|
{path: 'ChannelList', component: ChannelListComponent},
|
||||||
// {path: 'Property', component: PropertyComponent},
|
// {path: 'Property', component: PropertyComponent},
|
||||||
{path: 'PropertyList', component: PropertyListComponent},
|
{path: 'PropertyList', component: PropertyListComponent},
|
||||||
{path: 'Schedule', component: ScheduleComponent},
|
{path: 'Schedule', component: ScheduleComponent},
|
||||||
|
|||||||
@ -3,18 +3,17 @@
|
|||||||
<div class="item" routerLink="/ScheduleList" routerLinkActive="itemActive">
|
<div class="item" routerLink="/ScheduleList" routerLinkActive="itemActive">
|
||||||
Zeitpläne
|
Zeitpläne
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item" routerLink="/PropertyList" routerLinkActive="itemActive">
|
|
||||||
Eigenschaften
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item" routerLink="/DeviceList" routerLinkActive="itemActive">
|
<div class="item" routerLink="/DeviceList" routerLinkActive="itemActive">
|
||||||
Geräte
|
Geräte
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="item breadcrumb" [routerLink]="['/Schedule', {id: dataService.schedule.id}]" routerLinkActive="itemActive" *ngIf="dataService.schedule">-->
|
<div class="item itemSecondary" routerLink="/PropertyList" routerLinkActive="itemActive">
|
||||||
<!-- {{dataService.schedule.name}}-->
|
Eigenschaften
|
||||||
<!-- </div>-->
|
</div>
|
||||||
|
|
||||||
|
<div class="item itemSecondary" routerLink="/ChannelList" routerLinkActive="itemActive">
|
||||||
|
Kanäle
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,12 @@
|
|||||||
border-right: 1px solid black;
|
border-right: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.itemSecondary {
|
||||||
|
float: right;
|
||||||
|
border-left: 1px solid black;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
.item:hover {
|
.item:hover {
|
||||||
background-color: lightskyblue;
|
background-color: lightskyblue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {SearchComponent} from './shared/search/search.component';
|
|||||||
import {DeviceListComponent} from './pages/device-list/device-list.component';
|
import {DeviceListComponent} from './pages/device-list/device-list.component';
|
||||||
import {DeviceComponent} from './pages/device/device.component';
|
import {DeviceComponent} from './pages/device/device.component';
|
||||||
import {PropertyListComponent} from './pages/property-list/property-list.component';
|
import {PropertyListComponent} from './pages/property-list/property-list.component';
|
||||||
|
import {ChannelListComponent} from './pages/channel-list/channel-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -26,6 +27,7 @@ import {PropertyListComponent} from './pages/property-list/property-list.compone
|
|||||||
DeviceListComponent,
|
DeviceListComponent,
|
||||||
DeviceComponent,
|
DeviceComponent,
|
||||||
PropertyListComponent,
|
PropertyListComponent,
|
||||||
|
ChannelListComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Titel</th>
|
||||||
|
<th>Typ</th>
|
||||||
|
<th colspan="3">Adresse</th>
|
||||||
|
<th>DPT</th>
|
||||||
|
<th colspan="2">Wert</th>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let channel of channels">
|
||||||
|
<td>{{channel.title}}</td>
|
||||||
|
<td>{{channel.type}}</td>
|
||||||
|
<ng-container *ngIf="channel.type === 'KnxGroup'">
|
||||||
|
<td class="first number">{{asKnxGroup(channel).addresMain}} / </td>
|
||||||
|
<td class="middle number">{{asKnxGroup(channel).addresMid}} / </td>
|
||||||
|
<td class="last number">{{asKnxGroup(channel).addresSub}}</td>
|
||||||
|
<td class="number">{{asKnxGroup(channel).dpt}}</td>
|
||||||
|
<td class="number">{{asKnxGroup(channel).value}}</td>
|
||||||
|
<td>{{asKnxGroup(channel).timestamp | date:'yyyy-MM-dd HH:mm:ss'}}</td>
|
||||||
|
</ng-container>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {ChannelListComponent} from './channel-list.component';
|
||||||
|
|
||||||
|
describe('ChannelListComponent', () => {
|
||||||
|
let component: ChannelListComponent;
|
||||||
|
let fixture: ComponentFixture<ChannelListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ChannelListComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ChannelListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {ChannelService} from "../../api/channel/channel.service";
|
||||||
|
import {Channel, KnxGroup} from "../../api/channel/Channel";
|
||||||
|
import {Update} from "../../api/Update";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-channel-list',
|
||||||
|
templateUrl: './channel-list.component.html',
|
||||||
|
styleUrls: ['./channel-list.component.less']
|
||||||
|
})
|
||||||
|
export class ChannelListComponent implements OnInit {
|
||||||
|
|
||||||
|
channels: Channel[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly channelService: ChannelService,
|
||||||
|
) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.channelService.subscribe(update => this.updateChannel(update))
|
||||||
|
this.channelService.findAll(channels => this.channels = channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
asKnxGroup(channel: Channel): KnxGroup {
|
||||||
|
return channel as KnxGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateChannel(update: Update<Channel>): void {
|
||||||
|
const index: number = this.channels.findIndex(c => c.id === update.payload.id);
|
||||||
|
if (index >= 0) {
|
||||||
|
if (update.existing) {
|
||||||
|
this.channels[index] = update.payload;
|
||||||
|
} else {
|
||||||
|
this.channels.slice(index, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.channels.push(update.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -29,14 +29,6 @@
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
border-radius: 25%;
|
border-radius: 25%;
|
||||||
|
|
||||||
.center {
|
|
||||||
position: absolute;
|
|
||||||
margin: 0;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
<ng-template #empty>
|
||||||
|
<td class="empty">-</td>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Bezeichnung</th>
|
<th>Bezeichnung</th>
|
||||||
@ -5,8 +9,11 @@
|
|||||||
<th>Typ</th>
|
<th>Typ</th>
|
||||||
<th>Wert</th>
|
<th>Wert</th>
|
||||||
<th>Zeitstempel</th>
|
<th>Zeitstempel</th>
|
||||||
|
<th>Lesekanal</th>
|
||||||
|
<th>Schreibkanal</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngFor="let property of properties.sort(Property.compareTypeThenTitle)">
|
<ng-container *ngFor="let property of properties.sort(Property.compareTypeThenTitle)">
|
||||||
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<app-edit-field [initial]="property.title" (valueChange)="edit(property, 'title', $event)"></app-edit-field>
|
<app-edit-field [initial]="property.title" (valueChange)="edit(property, 'title', $event)"></app-edit-field>
|
||||||
</td>
|
</td>
|
||||||
@ -23,7 +30,7 @@
|
|||||||
<option value="SCENE">Szene</option>
|
<option value="SCENE">Szene</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<ng-container *ngIf="property.value !== null">
|
<ng-container *ngIf="property.value !== null else empty">
|
||||||
<td *ngIf="property.type === 'BOOLEAN'" class="boolean" [class.true]="property.value" [class.false]="!property.value" (click)="edit(property, 'value', property.value > 0 ? 0 : 1)">
|
<td *ngIf="property.type === 'BOOLEAN'" class="boolean" [class.true]="property.value" [class.false]="!property.value" (click)="edit(property, 'value', property.value > 0 ? 0 : 1)">
|
||||||
{{property.value ? "An" : "Aus"}}
|
{{property.value ? "An" : "Aus"}}
|
||||||
</td>
|
</td>
|
||||||
@ -43,11 +50,15 @@
|
|||||||
{{findScene(property)?.title || "Unbekannt: " + property.value}}
|
{{findScene(property)?.title || "Unbekannt: " + property.value}}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="property.value === null">
|
<td *ngIf="property.timestamp !== null else empty">
|
||||||
<td class="empty">
|
{{property.timestamp | date:'yyyy-MM-dd HH:mm:ss'}}
|
||||||
-LEER-
|
</td>
|
||||||
|
<td class="full">
|
||||||
|
<app-search [searchService]="channelService" [initial]="property.readChannel?.id" (valueChange)="set(property, 'readChannel', $event)"></app-search>
|
||||||
|
</td>
|
||||||
|
<td class="full">
|
||||||
|
<app-search [searchService]="channelService" [initial]="property.writeChannel?.id" (valueChange)="set(property, 'writeChannel', $event)"></app-search>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
|
||||||
<td>{{property.timestamp | date:'yyyy-MM-dd HH:mm:ss'}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</ng-container>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {Property} from "../../api/property/Property";
|
|||||||
import {PropertyService} from "../../api/property/property.service";
|
import {PropertyService} from "../../api/property/property.service";
|
||||||
import {Scene} from "../../api/scene/Scene";
|
import {Scene} from "../../api/scene/Scene";
|
||||||
import {SceneService} from "../../api/scene/scene.service";
|
import {SceneService} from "../../api/scene/scene.service";
|
||||||
|
import {ChannelService} from "../../api/channel/channel.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-property-list',
|
selector: 'app-property-list',
|
||||||
@ -20,6 +21,7 @@ export class PropertyListComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
readonly propertyService: PropertyService,
|
readonly propertyService: PropertyService,
|
||||||
readonly sceneService: SceneService,
|
readonly sceneService: SceneService,
|
||||||
|
readonly channelService: ChannelService,
|
||||||
) {
|
) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
@ -70,4 +72,8 @@ export class PropertyListComponent implements OnInit {
|
|||||||
return this.scenes.find(s => s.id === property.value);
|
return this.scenes.find(s => s.id === property.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set(property: Property, key: string, value: any): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<input #input *ngIf="editing" type="text" [(ngModel)]="value" (keypress)="keypress($event)" (blur)="finish()">
|
<input #input *ngIf="editing" type="text" [(ngModel)]="value" (keypress)="keypress($event)" (blur)="finish()">
|
||||||
<div *ngIf="!editing" [class.empty]="initial == ''" (click)="start()">
|
<div *ngIf="!editing" [class.empty]="initial == ''" (click)="start()">
|
||||||
<ng-container *ngIf="initial != ''">{{initial}}</ng-container>
|
<ng-container *ngIf="initial != ''">{{initial}}</ng-container>
|
||||||
<ng-container *ngIf="initial == ''">- LEER -</ng-container>
|
<ng-container *ngIf="initial == ''">-</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
<div *ngIf="!searching" (click)="start()" [class.empty]="!selected">
|
<div class="all">
|
||||||
|
|
||||||
|
<div class="initial" *ngIf="!searching" (click)="start()" [class.empty]="!selected">
|
||||||
<ng-container *ngIf="selected">
|
<ng-container *ngIf="selected">
|
||||||
{{selected.value}}
|
{{selected.title}}
|
||||||
<ng-container *ngIf="showKey">
|
<ng-container *ngIf="showKey">
|
||||||
[{{selected.key}}]
|
[{{selected.id}}]
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!selected">
|
<ng-container *ngIf="!selected">-</ng-container>
|
||||||
-LEER-
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input #input type="text" *ngIf="searching" [(ngModel)]="term" (ngModelChange)="changed()" (keydown)="inputKeyPress($event)" (focus)="cancelOnBlur=true" (blur)="blur()">
|
<input #input type="text" *ngIf="searching" [(ngModel)]="term" (ngModelChange)="changed()" (keydown)="inputKeyPress($event)" (focus)="cancelOnBlur=true" (blur)="blur()">
|
||||||
@ -17,15 +17,17 @@
|
|||||||
-
|
-
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="selected" class="result selected" (mousedown)="dontCancelOnBlur()" (click)="select(selected)">
|
<div *ngIf="selected" class="result selected" (mousedown)="dontCancelOnBlur()" (click)="select(selected)">
|
||||||
{{selected.value}}
|
{{selected.title}}
|
||||||
<ng-container *ngIf="showKey">
|
<ng-container *ngIf="showKey">
|
||||||
[{{selected.key}}]
|
[{{selected.id}}]
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div *ngFor="let result of results" class="result" (mousedown)="dontCancelOnBlur()" (click)="select(result)">
|
<div *ngFor="let result of results" class="result" (mousedown)="dontCancelOnBlur()" (click)="select(result)">
|
||||||
{{result.value}}
|
{{result.title}}
|
||||||
<ng-container *ngIf="showKey">
|
<ng-container *ngIf="showKey">
|
||||||
[{{result.key}}]
|
[{{result.id}}]
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,9 @@
|
|||||||
|
.all {
|
||||||
|
.initial {
|
||||||
|
padding: 5px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-bottom: 1px solid black;
|
border-bottom: 1px solid black;
|
||||||
@ -17,3 +23,5 @@
|
|||||||
background-color: lightyellow;
|
background-color: lightyellow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
|
import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
|
||||||
import {KeyValuePair} from "../../api/KeyValuePair";
|
import {SearchResult} from "../../api/SearchResult";
|
||||||
import {ISearchService} from "../../api/ISearchService";
|
import {ISearchService} from "../../api/ISearchService";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -33,13 +33,13 @@ export class SearchComponent<T> implements OnInit {
|
|||||||
allowEmpty: boolean = true;
|
allowEmpty: boolean = true;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
valueChange: EventEmitter<string | null> = new EventEmitter<string | null>();
|
valueChange: EventEmitter<number | null> = new EventEmitter<number | null>();
|
||||||
|
|
||||||
term: string = "";
|
term: string = "";
|
||||||
|
|
||||||
results: KeyValuePair[] = [];
|
results: SearchResult[] = [];
|
||||||
|
|
||||||
selected?: KeyValuePair;
|
selected?: SearchResult;
|
||||||
|
|
||||||
searching: boolean = false;
|
searching: boolean = false;
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ export class SearchComponent<T> implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
this.term = this.selected?.value || "";
|
this.term = this.selected?.title || "";
|
||||||
if (this.resultList && this.input) {
|
if (this.resultList && this.input) {
|
||||||
this.resultList.style.left = this.input.style.left;
|
this.resultList.style.left = this.input.style.left;
|
||||||
}
|
}
|
||||||
@ -110,10 +110,10 @@ export class SearchComponent<T> implements OnInit {
|
|||||||
this.cancelOnBlur = false;
|
this.cancelOnBlur = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
select(result: KeyValuePair | undefined): void {
|
select(result: SearchResult | undefined): void {
|
||||||
this.searching = false;
|
this.searching = false;
|
||||||
this.selected = result;
|
this.selected = result;
|
||||||
this.valueChange.emit(this.selected?.key);
|
this.valueChange.emit(this.selected?.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,12 @@ img {
|
|||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
td, th {
|
td, th {
|
||||||
|
height: 0; // (=> auto growth) enables use of height percent for children
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
|
|
||||||
@ -36,6 +41,14 @@ table.vertical {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: gray;
|
color: gray;
|
||||||
|
|||||||
@ -18,6 +18,8 @@ public abstract class Channel {
|
|||||||
@Setter(AccessLevel.NONE)
|
@Setter(AccessLevel.NONE)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
public abstract Class<? extends IChannelOwner> getChannelOwnerClass();
|
public abstract Class<? extends IChannelOwner> getChannelOwnerClass();
|
||||||
|
|
||||||
public abstract Double getValue();
|
public abstract Double getValue();
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
package de.ph87.homeautomation.channel;
|
||||||
|
|
||||||
|
import de.ph87.homeautomation.shared.ISearchController;
|
||||||
|
import de.ph87.homeautomation.shared.SearchResult;
|
||||||
|
import de.ph87.homeautomation.web.NotFoundException;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("channel")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChannelController implements ISearchController {
|
||||||
|
|
||||||
|
private final ChannelService channelService;
|
||||||
|
|
||||||
|
@GetMapping("findAll")
|
||||||
|
public List<ChannelDto> findAll() {
|
||||||
|
return channelService.findAllDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GetMapping("getById/{id}")
|
||||||
|
public SearchResult getById(@PathVariable final long id) {
|
||||||
|
return channelService.findDtoById(id).map(this::toSearchResult).orElseThrow(() -> new NotFoundException("Channel.id=" + id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PostMapping("searchLike")
|
||||||
|
public List<SearchResult> searchLike(@RequestBody final String term) {
|
||||||
|
return channelService.findAllDtoLike(term).stream().map(this::toSearchResult).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchResult toSearchResult(final ChannelDto dto) {
|
||||||
|
return new SearchResult(dto.getId(), dto.getTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
24
src/main/java/de/ph87/homeautomation/channel/ChannelDto.java
Normal file
24
src/main/java/de/ph87/homeautomation/channel/ChannelDto.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package de.ph87.homeautomation.channel;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public abstract class ChannelDto implements Serializable {
|
||||||
|
|
||||||
|
private final long id;
|
||||||
|
|
||||||
|
private final String title;
|
||||||
|
|
||||||
|
private final String type;
|
||||||
|
|
||||||
|
protected ChannelDto(final Channel channel) {
|
||||||
|
this.id = channel.getId();
|
||||||
|
this.title = channel.getName();
|
||||||
|
this.type = channel.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package de.ph87.homeautomation.channel;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
|
||||||
|
public interface ChannelRepository extends CrudRepository<Channel, Long> {
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,14 +1,13 @@
|
|||||||
package de.ph87.homeautomation.channel;
|
package de.ph87.homeautomation.channel;
|
||||||
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import de.ph87.homeautomation.property.PropertyReadService;
|
import de.ph87.homeautomation.shared.Helpers;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
|
||||||
import org.springframework.context.event.EventListener;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -20,19 +19,7 @@ public class ChannelService {
|
|||||||
|
|
||||||
private final List<IChannelOwner> channelOwners;
|
private final List<IChannelOwner> channelOwners;
|
||||||
|
|
||||||
private final PropertyReadService propertyReadService;
|
private final ChannelRepository channelRepository;
|
||||||
|
|
||||||
@EventListener(ApplicationStartedEvent.class)
|
|
||||||
public void readAllPropertyChannels() {
|
|
||||||
propertyReadService.findAllByReadChannelNotNull().forEach(property -> {
|
|
||||||
final Optional<IChannelOwner> ownerOptional = findByChannel(property.getReadChannel());
|
|
||||||
if (ownerOptional.isPresent()) {
|
|
||||||
ownerOptional.get().read(property.getReadChannel());
|
|
||||||
} else {
|
|
||||||
log.error("No Owner for Property: {}", property);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<IChannelOwner> findByChannel(final Channel channel) {
|
public Optional<IChannelOwner> findByChannel(final Channel channel) {
|
||||||
return channelOwners.stream().filter(owner -> channel.getChannelOwnerClass().isInstance(owner)).findFirst();
|
return channelOwners.stream().filter(owner -> channel.getChannelOwnerClass().isInstance(owner)).findFirst();
|
||||||
@ -50,4 +37,27 @@ public class ChannelService {
|
|||||||
getByChannel(channel).write(property.getWriteChannel(), value);
|
getByChannel(channel).write(property.getWriteChannel(), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChannelDto toDtoAllowNull(final Channel channel) {
|
||||||
|
if (channel == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return toDto(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChannelDto toDto(final Channel channel) {
|
||||||
|
return getByChannel(channel).toDto(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChannelDto> findAllDto() {
|
||||||
|
return channelOwners.stream().map(IChannelOwner::findAllDto).reduce(new ArrayList<>(), Helpers::merge);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChannelDto> findAllDtoLike(final String term) {
|
||||||
|
return channelOwners.stream().map(owner -> owner.findAllDtoLike(term)).reduce(new ArrayList<>(), Helpers::merge);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ChannelDto> findDtoById(final long id) {
|
||||||
|
return channelRepository.findById(id).map(this::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,17 @@
|
|||||||
package de.ph87.homeautomation.channel;
|
package de.ph87.homeautomation.channel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface IChannelOwner {
|
public interface IChannelOwner {
|
||||||
|
|
||||||
void read(final Channel channel);
|
void read(final Channel channel);
|
||||||
|
|
||||||
void write(final Channel channel, final double value);
|
void write(final Channel channel, final double value);
|
||||||
|
|
||||||
|
ChannelDto toDto(final Channel channel);
|
||||||
|
|
||||||
|
List<ChannelDto> findAllDto();
|
||||||
|
|
||||||
|
List<ChannelDto> findAllDtoLike(final String like);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
package de.ph87.homeautomation.knx.group;
|
package de.ph87.homeautomation.knx.group;
|
||||||
|
|
||||||
import de.ph87.homeautomation.channel.Channel;
|
import de.ph87.homeautomation.channel.Channel;
|
||||||
|
import de.ph87.homeautomation.channel.ChannelDto;
|
||||||
import de.ph87.homeautomation.channel.IChannelOwner;
|
import de.ph87.homeautomation.channel.IChannelOwner;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -15,22 +19,31 @@ public class KnxGroupChannelOwnerService implements IChannelOwner {
|
|||||||
|
|
||||||
private final KnxGroupWriteService knxGroupWriteService;
|
private final KnxGroupWriteService knxGroupWriteService;
|
||||||
|
|
||||||
|
private final KnxGroupReadService knxGroupReadService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(final Channel channel) {
|
public void read(final Channel channel) {
|
||||||
if (!(channel instanceof KnxGroup)) {
|
knxGroupWriteService.requestRead((KnxGroup) channel);
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
final KnxGroup knxGroup = (KnxGroup) channel;
|
|
||||||
knxGroupWriteService.requestRead(knxGroup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(final Channel channel, final double value) {
|
public void write(final Channel channel, final double value) {
|
||||||
if (!(channel instanceof KnxGroup)) {
|
knxGroupWriteService.requestWrite((KnxGroup) channel, value);
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
}
|
||||||
final KnxGroup knxGroup = (KnxGroup) channel;
|
|
||||||
knxGroupWriteService.requestWrite(knxGroup, value);
|
@Override
|
||||||
|
public ChannelDto toDto(final Channel channel) {
|
||||||
|
return new KnxGroupDto((KnxGroup) channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ChannelDto> findAllDto() {
|
||||||
|
return knxGroupReadService.findAll().stream().map(this::toDto).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ChannelDto> findAllDtoLike(final String like) {
|
||||||
|
return knxGroupReadService.findAllLike(like).stream().map(this::toDto).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
package de.ph87.homeautomation.knx.group;
|
package de.ph87.homeautomation.knx.group;
|
||||||
|
|
||||||
import lombok.Data;
|
import de.ph87.homeautomation.channel.ChannelDto;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
@Data
|
@Getter
|
||||||
public class KnxGroupDto implements Serializable {
|
@ToString(callSuper = true)
|
||||||
|
public class KnxGroupDto extends ChannelDto {
|
||||||
|
|
||||||
public final int addressRaw;
|
public final int addressRaw;
|
||||||
|
|
||||||
@ -16,8 +18,6 @@ public class KnxGroupDto implements Serializable {
|
|||||||
|
|
||||||
public final int dptSub;
|
public final int dptSub;
|
||||||
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
public final String description;
|
public final String description;
|
||||||
|
|
||||||
public final int puid;
|
public final int puid;
|
||||||
@ -37,11 +37,11 @@ public class KnxGroupDto implements Serializable {
|
|||||||
public final KnxGroupLinkInfo send;
|
public final KnxGroupLinkInfo send;
|
||||||
|
|
||||||
public KnxGroupDto(final KnxGroup knxGroup) {
|
public KnxGroupDto(final KnxGroup knxGroup) {
|
||||||
|
super(knxGroup);
|
||||||
this.addressRaw = knxGroup.getAddressRaw();
|
this.addressRaw = knxGroup.getAddressRaw();
|
||||||
this.addressStr = knxGroup.getAddressStr();
|
this.addressStr = knxGroup.getAddressStr();
|
||||||
this.dptMain = knxGroup.getDptMain();
|
this.dptMain = knxGroup.getDptMain();
|
||||||
this.dptSub = knxGroup.getDptSub();
|
this.dptSub = knxGroup.getDptSub();
|
||||||
this.name = knxGroup.getName();
|
|
||||||
this.description = knxGroup.getDescription();
|
this.description = knxGroup.getDescription();
|
||||||
this.puid = knxGroup.getPuid();
|
this.puid = knxGroup.getPuid();
|
||||||
this.ets = knxGroup.isEts();
|
this.ets = knxGroup.isEts();
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import tuwien.auto.calimero.GroupAddress;
|
import tuwien.auto.calimero.GroupAddress;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -18,4 +20,12 @@ public class KnxGroupReadService {
|
|||||||
return knxGroupRepository.findByAddressRaw(new GroupAddress(main, mid, sub).getRawAddress()).orElseThrow(RuntimeException::new);
|
return knxGroupRepository.findByAddressRaw(new GroupAddress(main, mid, sub).getRawAddress()).orElseThrow(RuntimeException::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<KnxGroup> findAll() {
|
||||||
|
return knxGroupRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<KnxGroup> findAllLike(final String term) {
|
||||||
|
return knxGroupRepository.findAllByNameContainsIgnoreCaseOrAddressStrContainsIgnoreCase(term, term);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import java.time.ZonedDateTime;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface KnxGroupRepository extends CrudRepository<KnxGroup, String> {
|
public interface KnxGroupRepository extends CrudRepository<KnxGroup, Long> {
|
||||||
|
|
||||||
Optional<KnxGroup> findByAddressRaw(int rawAddress);
|
Optional<KnxGroup> findByAddressRaw(int rawAddress);
|
||||||
|
|
||||||
@ -18,6 +18,6 @@ public interface KnxGroupRepository extends CrudRepository<KnxGroup, String> {
|
|||||||
|
|
||||||
Optional<KnxGroup> findFirstByRead_NextTimestampNotNullOrderByRead_NextTimestampAsc();
|
Optional<KnxGroup> findFirstByRead_NextTimestampNotNullOrderByRead_NextTimestampAsc();
|
||||||
|
|
||||||
boolean existsByAddressRaw(int rawAddress);
|
List<KnxGroup> findAllByNameContainsIgnoreCaseOrAddressStrContainsIgnoreCase(String name, String addressStr);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package de.ph87.homeautomation.knx.group;
|
package de.ph87.homeautomation.knx.group;
|
||||||
|
|
||||||
import de.ph87.homeautomation.channel.ChannelChangedEvent;
|
import de.ph87.homeautomation.channel.ChannelChangedEvent;
|
||||||
|
import de.ph87.homeautomation.channel.ChannelDto;
|
||||||
import de.ph87.homeautomation.web.WebSocketService;
|
import de.ph87.homeautomation.web.WebSocketService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -120,7 +121,7 @@ public class KnxGroupWriteService {
|
|||||||
|
|
||||||
private KnxGroupDto publish(final KnxGroup knxGroup) {
|
private KnxGroupDto publish(final KnxGroup knxGroup) {
|
||||||
final KnxGroupDto dto = knxGroupMapper.toDto(knxGroup);
|
final KnxGroupDto dto = knxGroupMapper.toDto(knxGroup);
|
||||||
webSocketService.send(dto, true);
|
webSocketService.send(ChannelDto.class.getSimpleName(), dto, true);
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
package de.ph87.homeautomation.property;
|
||||||
|
|
||||||
|
import de.ph87.homeautomation.channel.ChannelService;
|
||||||
|
import de.ph87.homeautomation.channel.IChannelOwner;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PropertyChannelService {
|
||||||
|
|
||||||
|
private final ChannelService channelService;
|
||||||
|
|
||||||
|
private final PropertyReadService propertyReadService;
|
||||||
|
|
||||||
|
@EventListener(ApplicationStartedEvent.class)
|
||||||
|
public void readAllPropertyChannels() {
|
||||||
|
propertyReadService.findAllByReadChannelNotNull().forEach(property -> {
|
||||||
|
final Optional<IChannelOwner> ownerOptional = channelService.findByChannel(property.getReadChannel());
|
||||||
|
if (ownerOptional.isPresent()) {
|
||||||
|
ownerOptional.get().read(property.getReadChannel());
|
||||||
|
} else {
|
||||||
|
log.error("No Owner for Property: {}", property);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package de.ph87.homeautomation.property;
|
package de.ph87.homeautomation.property;
|
||||||
|
|
||||||
import de.ph87.homeautomation.shared.ISearchController;
|
import de.ph87.homeautomation.shared.ISearchController;
|
||||||
import de.ph87.homeautomation.shared.KeyValuePair;
|
import de.ph87.homeautomation.shared.SearchResult;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@ -44,19 +44,18 @@ public class PropertyController implements ISearchController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@GetMapping("getById/{id}")
|
@GetMapping("getById/{id}")
|
||||||
public KeyValuePair getById(@PathVariable final long id) {
|
public SearchResult getById(@PathVariable final long id) {
|
||||||
final PropertyDto propertyDto = propertyReadService.getDtoById(id);
|
return toSearchResult(propertyReadService.getDtoById(id));
|
||||||
return toKeyValuePair(propertyDto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PostMapping("searchLike")
|
@PostMapping("searchLike")
|
||||||
public List<KeyValuePair> searchLike(@RequestBody final String term) {
|
public List<SearchResult> searchLike(@RequestBody final String term) {
|
||||||
return propertyReadService.findAllDtoLike("%" + term + "%").stream().map(this::toKeyValuePair).collect(Collectors.toList());
|
return propertyReadService.findAllDtoLike("%" + term + "%").stream().map(this::toSearchResult).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyValuePair toKeyValuePair(final PropertyDto propertyDto) {
|
private SearchResult toSearchResult(final PropertyDto propertyDto) {
|
||||||
return new KeyValuePair(propertyDto.getName(), propertyDto.getTitle());
|
return new SearchResult(propertyDto.getId(), propertyDto.getTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package de.ph87.homeautomation.property;
|
package de.ph87.homeautomation.property;
|
||||||
|
|
||||||
|
import de.ph87.homeautomation.channel.ChannelDto;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@ -20,13 +21,19 @@ public final class PropertyDto implements Serializable {
|
|||||||
|
|
||||||
private final ZonedDateTime timestamp;
|
private final ZonedDateTime timestamp;
|
||||||
|
|
||||||
public PropertyDto(final Property property) {
|
private final ChannelDto readChannel;
|
||||||
|
|
||||||
|
private final ChannelDto writeChannel;
|
||||||
|
|
||||||
|
public PropertyDto(final Property property, final ChannelDto readChannel, final ChannelDto writeChannel) {
|
||||||
this.id = property.getId();
|
this.id = property.getId();
|
||||||
this.type = property.getType();
|
this.type = property.getType();
|
||||||
this.name = property.getName();
|
this.name = property.getName();
|
||||||
this.title = property.getTitle();
|
this.title = property.getTitle();
|
||||||
this.value = property.getValue();
|
this.value = property.getValue();
|
||||||
this.timestamp = property.getTimestamp();
|
this.timestamp = property.getTimestamp();
|
||||||
|
this.readChannel = readChannel;
|
||||||
|
this.writeChannel = writeChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package de.ph87.homeautomation.property;
|
package de.ph87.homeautomation.property;
|
||||||
|
|
||||||
|
import de.ph87.homeautomation.channel.ChannelService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -11,8 +12,10 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PropertyMapper {
|
public class PropertyMapper {
|
||||||
|
|
||||||
|
private final ChannelService channelService;
|
||||||
|
|
||||||
public PropertyDto toDto(final Property property) {
|
public PropertyDto toDto(final Property property) {
|
||||||
return new PropertyDto(property);
|
return new PropertyDto(property, channelService.toDtoAllowNull(property.getReadChannel()), channelService.toDtoAllowNull(property.getWriteChannel()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import java.util.List;
|
|||||||
|
|
||||||
public interface ISearchController {
|
public interface ISearchController {
|
||||||
|
|
||||||
KeyValuePair getById(final long id);
|
SearchResult getById(final long id);
|
||||||
|
|
||||||
List<KeyValuePair> searchLike(final String term);
|
List<SearchResult> searchLike(final String term);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
package de.ph87.homeautomation.shared;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class KeyValuePair {
|
|
||||||
|
|
||||||
public final String key;
|
|
||||||
|
|
||||||
public final String value;
|
|
||||||
|
|
||||||
public KeyValuePair(final String key, final String value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package de.ph87.homeautomation.shared;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class SearchResult {
|
||||||
|
|
||||||
|
public final long id;
|
||||||
|
|
||||||
|
public final String title;
|
||||||
|
|
||||||
|
public SearchResult(final long id, final String title) {
|
||||||
|
this.id = id;
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user