REFACTOR: Replaced ScheduleEntry properties by single value

+ UI ScheduleEntry.value
This commit is contained in:
Patrick Haßel 2021-10-29 11:24:58 +02:00
parent 87acae3b2e
commit da31a892e1
25 changed files with 264 additions and 194 deletions

View File

@ -0,0 +1,35 @@
import {prefix} from "../helpers";
export class Timestamp {
public readonly WEEKDAY: string[] = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
public readonly dayName;
public readonly timeString;
public constructor(
readonly date: Date,
) {
const now = new Date();
const minutes: string = prefix(this.date.getMinutes(), '0', 2);
if (date.getDate() === now.getDate()) {
this.dayName = "Heute";
this.timeString = date.getHours() + ":" + minutes;
} else if (date.getDate() === now.getDate() + 1) {
this.dayName = "Morgen";
this.timeString = date.getHours() + ":" + minutes;
} else {
this.dayName = this.WEEKDAY[date.getDay()];
this.timeString = date.getHours() + ":" + minutes;
}
}
public static fromDateOrNull(date: Date | null): Timestamp | null {
if (date === null) {
return null;
}
return new Timestamp(date);
}
}

View File

@ -7,6 +7,8 @@ export class Schedule {
public id: number, public id: number,
public enabled: boolean, public enabled: boolean,
public name: string, public name: string,
public propertyName: string,
public propertyType: string,
public entries: ScheduleEntry[], public entries: ScheduleEntry[],
) { ) {
// nothing // nothing
@ -17,7 +19,9 @@ export class Schedule {
validateNumberNotNull(json['id']), validateNumberNotNull(json['id']),
validateBooleanNotNull(json['enabled']), validateBooleanNotNull(json['enabled']),
validateStringNotEmptyNotNull(json['name']), validateStringNotEmptyNotNull(json['name']),
validateListOrEmpty(json['entries'], ScheduleEntry.fromJson, ScheduleEntry.compareId), validateStringNotEmptyNotNull(json['propertyName']),
validateStringNotEmptyNotNull(json['propertyType']),
validateListOrEmpty(json['entries'], ScheduleEntry.fromJson, ScheduleEntry.compare),
); );
} }

View File

@ -1,39 +1,5 @@
import {validateBooleanNotNull, validateDateAllowNull, validateMap, validateNumberNotNull, validateStringNotEmptyNotNull} from "../../validators"; import {validateBooleanNotNull, validateDateAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull} from "../../validators";
import {prefix} from "../../../helpers"; import {Timestamp} from "../../Timestamp";
class Timestamp {
public readonly WEEKDAY: string[] = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
public readonly dayName;
public readonly timeString;
public constructor(
readonly date: Date,
) {
const now = new Date();
const minutes: string = prefix(this.date.getMinutes(), '0', 2);
if (date.getDate() === now.getDate()) {
this.dayName = "Heute";
this.timeString = date.getHours() + ":" + minutes;
} else if (date.getDate() === now.getDate() + 1) {
this.dayName = "Morgen";
this.timeString = date.getHours() + ":" + minutes;
} else {
this.dayName = this.WEEKDAY[date.getDay()];
this.timeString = date.getHours() + ":" + minutes;
}
}
public static fromDateOrNull(date: Date | null): Timestamp | null {
if (date === null) {
return null;
}
return new Timestamp(date);
}
}
export class ScheduleEntry { export class ScheduleEntry {
@ -56,7 +22,7 @@ export class ScheduleEntry {
public lastClearTimestamp: Timestamp | null, public lastClearTimestamp: Timestamp | null,
public nextClearTimestamp: Timestamp | null, public nextClearTimestamp: Timestamp | null,
public nextFuzzyTimestamp: Timestamp | null, public nextFuzzyTimestamp: Timestamp | null,
public properties: Map<String, String>, public value: number,
) { ) {
// nothing // nothing
} }
@ -81,7 +47,7 @@ export class ScheduleEntry {
Timestamp.fromDateOrNull(validateDateAllowNull(json['lastClearTimestamp'])), Timestamp.fromDateOrNull(validateDateAllowNull(json['lastClearTimestamp'])),
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextClearTimestamp'])), Timestamp.fromDateOrNull(validateDateAllowNull(json['nextClearTimestamp'])),
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextFuzzyTimestamp'])), Timestamp.fromDateOrNull(validateDateAllowNull(json['nextFuzzyTimestamp'])),
validateMap(json['properties'], validateStringNotEmptyNotNull), validateNumberNotNull(json['value']),
); );
} }
@ -89,8 +55,13 @@ export class ScheduleEntry {
return item.id; return item.id;
} }
public static compareId(a: ScheduleEntry, b: ScheduleEntry): number { public static compare(a: ScheduleEntry, b: ScheduleEntry): number {
return a.id - b.id; if (a.nextFuzzyTimestamp === null) {
return +1;
} else if (b.nextFuzzyTimestamp === null) {
return -1;
}
return a.nextFuzzyTimestamp.date.getTime() - b.nextFuzzyTimestamp.date.getTime();
} }
} }

View File

@ -5,6 +5,13 @@ export function validateBooleanNotNull(value: any): boolean {
return value; return value;
} }
export function validateBooleanAllowNull(value: any): boolean | null {
if (value === null || value === undefined) {
return null;
}
return validateBooleanNotNull(value);
}
export function validateNumberNotNull(value: any): number { export function validateNumberNotNull(value: any): number {
const number: number = parseFloat(value); const number: number = parseFloat(value);
if (isNaN(number)) { if (isNaN(number)) {
@ -13,6 +20,13 @@ export function validateNumberNotNull(value: any): number {
return number; return number;
} }
export function validateNumberAllowNull(value: any): number | null {
if (value === null || value === undefined) {
return null;
}
return validateNumberNotNull(value);
}
export function validateStringNotEmptyNotNull(value: any): string { export function validateStringNotEmptyNotNull(value: any): string {
if (!(typeof value === 'string')) { if (!(typeof value === 'string')) {
throw new Error("Not a string: " + value); throw new Error("Not a string: " + value);
@ -23,25 +37,26 @@ export function validateStringNotEmptyNotNull(value: any): string {
return value; return value;
} }
export function validateStringNotEmpty_Nullable(value: any): string | null { export function validateStringNotEmpty_AllowNull(value: any): string | null {
if (value === null || value === undefined || value === '') { if (value === null || value === undefined || value === '') {
return null; return null;
} }
if (!(typeof value === 'string')) { return validateStringNotEmptyNotNull(value);
throw new Error("Not a string: " + value); }
function validateDateNotNull(value: any): Date {
const number: number = Date.parse(value);
if (isNaN(number)) {
throw new Error("Not a Date: " + value);
} }
return value; return new Date(number);
} }
export function validateDateAllowNull(value: any): Date | null { export function validateDateAllowNull(value: any): Date | null {
if (value === null || value === undefined || value === 0 || value === '') { if (value === null || value === undefined || value === 0 || value === '') {
return null; return null;
} }
const number: number = Date.parse(value); return validateDateNotNull(value);
if (isNaN(number)) {
throw new Error("Not a Date: " + value);
}
return new Date(number);
} }
export function validateListOrEmpty<T>(json: any, fromJson: (json: any) => T, compare: (a: T, b: T) => number): T[] { export function validateListOrEmpty<T>(json: any, fromJson: (json: any) => T, compare: (a: T, b: T) => number): T[] {

View File

@ -1,7 +1,7 @@
<div class="menubar"> <div class="menubar">
<div class="item" routerLink="/ScheduleList" routerLinkActive="itemActive"> <div class="item" routerLink="/ScheduleList" routerLinkActive="itemActive">
Zeitplan Zeitpläne
</div> </div>
<div class="item breadcrumb" [routerLink]="['/Schedule', {id: dataService.schedule.id}]" routerLinkActive="itemActive" *ngIf="dataService.schedule"> <div class="item breadcrumb" [routerLink]="['/Schedule', {id: dataService.schedule.id}]" routerLinkActive="itemActive" *ngIf="dataService.schedule">

View File

@ -28,6 +28,7 @@
<th colspan="3">Uhrzeit</th> <th colspan="3">Uhrzeit</th>
<th>Unschärfe</th> <th>Unschärfe</th>
<th colspan="6">Nächste Ausführung</th> <th colspan="6">Nächste Ausführung</th>
<th>Wert</th>
<th>&nbsp;</th> <th>&nbsp;</th>
</tr> </tr>
<tr *ngFor="let entry of schedule.entries" [class.disabled]="entry.nextClearTimestamp === null"> <tr *ngFor="let entry of schedule.entries" [class.disabled]="entry.nextClearTimestamp === null">
@ -70,7 +71,7 @@
<ng-container *ngIf="entry.type === 'TIME'"> <ng-container *ngIf="entry.type === 'TIME'">
<td class="first"> <td class="first">
<select class="number" [(ngModel)]="entry.hour" (ngModelChange)="set(entry, 'hour', entry.hour)"> <select class="number" [(ngModel)]="entry.hour" (ngModelChange)="set(entry, 'hour', entry.hour)">
<option *ngFor="let _ of [].constructor(60); let value = index" [ngValue]="value">{{value}}</option> <option *ngFor="let _ of [].constructor(24); let value = index" [ngValue]="value">{{value}}</option>
</select> </select>
</td> </td>
<td class="middle">:</td> <td class="middle">:</td>
@ -120,6 +121,15 @@
<td class="empty last"></td> <td class="empty last"></td>
</ng-container> </ng-container>
<td *ngIf="schedule.propertyType === 'ON_OFF'" [class.true]="entry.value" [class.false]="!entry.value" (click)="set(entry, 'value', entry.value > 0 ? 0 : 1)">
{{entry.value ? "An" : "Aus"}}
</td>
<td *ngIf="schedule.propertyType === 'PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select class="number" [(ngModel)]="entry.value" (ngModelChange)="set(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td class="delete" (click)="delete(entry)"> <td class="delete" (click)="delete(entry)">
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon> <fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
</td> </td>

View File

@ -32,6 +32,10 @@ tr.header {
background-color: palegreen; background-color: palegreen;
} }
.tristate {
background-color: yellow;
}
.false { .false {
background-color: indianred; background-color: indianred;
} }

View File

@ -55,7 +55,6 @@ export class ScheduleComponent implements OnInit {
} else { } else {
this.schedule.entries[index] = entry; this.schedule.entries[index] = entry;
} }
this.schedule.entries = this.schedule.entries.sort(ScheduleEntry.compareId)
} }
private remove(entry: ScheduleEntry): void { private remove(entry: ScheduleEntry): void {

View File

@ -4,7 +4,7 @@ import com.luckycatlabs.sunrisesunset.Zenith;
import de.ph87.homeautomation.knx.group.KnxGroupDto; import de.ph87.homeautomation.knx.group.KnxGroupDto;
import de.ph87.homeautomation.knx.group.KnxGroupRepository; import de.ph87.homeautomation.knx.group.KnxGroupRepository;
import de.ph87.homeautomation.knx.group.KnxGroupWriteService; import de.ph87.homeautomation.knx.group.KnxGroupWriteService;
import de.ph87.homeautomation.schedule.PropertyEntry; import de.ph87.homeautomation.property.PropertyType;
import de.ph87.homeautomation.schedule.Schedule; import de.ph87.homeautomation.schedule.Schedule;
import de.ph87.homeautomation.schedule.ScheduleRepository; import de.ph87.homeautomation.schedule.ScheduleRepository;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry; import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
@ -16,8 +16,6 @@ import tuwien.auto.calimero.GroupAddress;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Map;
@SuppressWarnings({"unchecked", "UnusedReturnValue", "SameParameterValue", "UnusedAssignment", "RedundantSuppression"}) @SuppressWarnings({"unchecked", "UnusedReturnValue", "SameParameterValue", "UnusedAssignment", "RedundantSuppression"})
@Slf4j @Slf4j
@ -35,109 +33,104 @@ public class DemoDataService {
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
final KnxGroupDto eg_flur_licht_schalten = createKnxGroupIfNotExists("EG Flur Licht Schalten", 1294, "1.001", false, false); 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", 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", 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", 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", 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", 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_mitteschalten = createKnxGroupIfNotExists("Bad Licht Mitte Schalten", 797, "1.001", 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", false, true); final KnxGroupDto helligkeit = createKnxGroupIfNotExists("Helligkeit", 1286, "9.004", PropertyType.LUX, false, true);
if (scheduleRepository.count() == 0) { if (scheduleRepository.count() == 0) {
final Schedule scheduleEgFlurLicht = new Schedule(); final Schedule scheduleEgFlurLicht = createSchedule("EG Flur Licht", eg_flur_licht_schalten);
scheduleEgFlurLicht.setEnabled(true); createTime(scheduleEgFlurLicht, 11, 30, 0, MIN30, true);
scheduleEgFlurLicht.setName("EG Flur Licht"); createTime(scheduleEgFlurLicht, 12, 30, 0, MIN30, false);
createTime(scheduleEgFlurLicht, 11, 30, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, true)); createTime(scheduleEgFlurLicht, 16, 30, 0, MIN30, true);
createTime(scheduleEgFlurLicht, 12, 30, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, false)); createTime(scheduleEgFlurLicht, 17, 30, 0, MIN30, false);
createTime(scheduleEgFlurLicht, 16, 30, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, true)); createTime(scheduleEgFlurLicht, 22, 0, 0, MIN30, true);
createTime(scheduleEgFlurLicht, 17, 30, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, false)); createTime(scheduleEgFlurLicht, 23, 0, 0, MIN30, false);
createTime(scheduleEgFlurLicht, 22, 0, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, true)); createTime(scheduleEgFlurLicht, 1, 0, 0, MIN30, true);
createTime(scheduleEgFlurLicht, 23, 0, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, false)); createTime(scheduleEgFlurLicht, 2, 0, 0, MIN30, false);
createTime(scheduleEgFlurLicht, 1, 0, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, true));
createTime(scheduleEgFlurLicht, 2, 0, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, false));
scheduleRepository.save(scheduleEgFlurLicht); scheduleRepository.save(scheduleEgFlurLicht);
final Schedule scheduleEgAmbiente = new Schedule(); final Schedule scheduleEgAmbiente = createSchedule("EG Ambiente", eg_ambiente_schalten);
scheduleEgAmbiente.setEnabled(true); createTime(scheduleEgAmbiente, 7, 15, 0, MIN30, true);
scheduleEgAmbiente.setName("EG Ambiente"); createTime(scheduleEgAmbiente, 9, 30, 0, MIN30, false);
createTime(scheduleEgAmbiente, 7, 15, 0, MIN30, new PropertyEntry(eg_ambiente_schalten, true)); createSunset(scheduleEgAmbiente, Zenith.OFFICIAL, MIN30, true);
createTime(scheduleEgAmbiente, 9, 30, 0, MIN30, new PropertyEntry(eg_ambiente_schalten, false)); createSunset(scheduleEgAmbiente, Zenith.ASTRONOMICAL, MIN30, false);
createSunset(scheduleEgAmbiente, Zenith.OFFICIAL, MIN30, new PropertyEntry(eg_ambiente_schalten, true));
createSunset(scheduleEgAmbiente, Zenith.ASTRONOMICAL, MIN30, new PropertyEntry(eg_ambiente_schalten, false));
scheduleRepository.save(scheduleEgAmbiente); scheduleRepository.save(scheduleEgAmbiente);
final Schedule scheduleOgAmbiente = new Schedule(); final Schedule scheduleOgAmbiente = createSchedule("OG Ambiente", og_ambiente_schalten);
scheduleOgAmbiente.setEnabled(true); createTime(scheduleOgAmbiente, 7, 15, 0, MIN30, true);
scheduleOgAmbiente.setName("OG Ambiente"); createTime(scheduleOgAmbiente, 9, 30, 0, MIN30, false);
createTime(scheduleOgAmbiente, 7, 15, 0, MIN30, new PropertyEntry(og_ambiente_schalten, true)); createSunset(scheduleOgAmbiente, Zenith.OFFICIAL, MIN30, true);
createTime(scheduleOgAmbiente, 9, 30, 0, MIN30, new PropertyEntry(og_ambiente_schalten, false)); createSunset(scheduleOgAmbiente, Zenith.ASTRONOMICAL, MIN30, false);
createSunset(scheduleOgAmbiente, Zenith.OFFICIAL, MIN30, new PropertyEntry(og_ambiente_schalten, true));
createSunset(scheduleOgAmbiente, Zenith.ASTRONOMICAL, MIN30, new PropertyEntry(og_ambiente_schalten, false));
scheduleRepository.save(scheduleOgAmbiente); scheduleRepository.save(scheduleOgAmbiente);
final Schedule scheduleWohnzimmerRollladen = new Schedule(); final Schedule scheduleWohnzimmerRollladen = createSchedule("Rollläden Wohnzimmer", wohnzimmer_rollladen_position_anfahren);
scheduleWohnzimmerRollladen.setEnabled(true); createSunrise(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, 0);
scheduleWohnzimmerRollladen.setName("Rollläden Wohnzimmer"); createSunset(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, 100);
createSunrise(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, new PropertyEntry(wohnzimmer_rollladen_position_anfahren, 0));
createSunset(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, new PropertyEntry(wohnzimmer_rollladen_position_anfahren, 100));
scheduleRepository.save(scheduleWohnzimmerRollladen); scheduleRepository.save(scheduleWohnzimmerRollladen);
final Schedule scheduleSchlafzimmerRollladen = new Schedule(); final Schedule scheduleSchlafzimmerRollladen = createSchedule("Rollläden Schlafzimmer", schlafzimmer_rollladen_position_anfahren);
scheduleSchlafzimmerRollladen.setEnabled(true); createTime(scheduleSchlafzimmerRollladen, 7, 0, 0, 0, 0);
scheduleSchlafzimmerRollladen.setName("Rollläden Schlafzimmer"); createSunset(scheduleSchlafzimmerRollladen, Zenith.CIVIL, 0, 100);
createTime(scheduleSchlafzimmerRollladen, 7, 0, 0, 0, new PropertyEntry(schlafzimmer_rollladen_position_anfahren, 0));
createSunset(scheduleSchlafzimmerRollladen, Zenith.CIVIL, 0, new PropertyEntry(schlafzimmer_rollladen_position_anfahren, 100));
scheduleRepository.save(scheduleSchlafzimmerRollladen); scheduleRepository.save(scheduleSchlafzimmerRollladen);
final Schedule scheduleFlurRollladen = new Schedule(); final Schedule scheduleFlurRollladen = createSchedule("Rollläden Flur", flur_rollladen_position_anfahren);
scheduleFlurRollladen.setEnabled(true); createSunrise(scheduleFlurRollladen, Zenith.CIVIL, 0, 0);
scheduleFlurRollladen.setName("Rollläden Flur"); createSunset(scheduleFlurRollladen, Zenith.CIVIL, 0, 100);
createSunrise(scheduleFlurRollladen, Zenith.CIVIL, 0, new PropertyEntry(flur_rollladen_position_anfahren, 0));
createSunset(scheduleFlurRollladen, Zenith.CIVIL, 0, new PropertyEntry(flur_rollladen_position_anfahren, 100));
scheduleRepository.save(scheduleFlurRollladen); scheduleRepository.save(scheduleFlurRollladen);
final Schedule scheduleBadLichtMitte = new Schedule(); final Schedule scheduleBadLichtMitte = createSchedule("Bad Licht Mitte", bad_licht_mitte_schalten);
scheduleBadLichtMitte.setEnabled(true); createTime(scheduleBadLichtMitte, 10, 30, 0, MIN30, true);
scheduleBadLichtMitte.setName("Bad Licht Mitte"); createTime(scheduleBadLichtMitte, 11, 30, 0, MIN30, false);
createTime(scheduleBadLichtMitte, 10, 30, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, true)); createTime(scheduleBadLichtMitte, 15, 30, 0, MIN30, true);
createTime(scheduleBadLichtMitte, 11, 30, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, false)); createTime(scheduleBadLichtMitte, 16, 30, 0, MIN30, false);
createTime(scheduleBadLichtMitte, 15, 30, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, true)); createTime(scheduleBadLichtMitte, 21, 0, 0, MIN30, true);
createTime(scheduleBadLichtMitte, 16, 30, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, false)); createTime(scheduleBadLichtMitte, 22, 0, 0, MIN30, false);
createTime(scheduleBadLichtMitte, 21, 0, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, true)); createTime(scheduleBadLichtMitte, 0, 0, 0, MIN30, true);
createTime(scheduleBadLichtMitte, 22, 0, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, false)); createTime(scheduleBadLichtMitte, 1, 0, 0, MIN30, false);
createTime(scheduleBadLichtMitte, 0, 0, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, true));
createTime(scheduleBadLichtMitte, 1, 0, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, false));
scheduleRepository.save(scheduleBadLichtMitte); scheduleRepository.save(scheduleBadLichtMitte);
} }
} }
private KnxGroupDto createKnxGroupIfNotExists(final String name, final int address, final String dpt, final boolean readable, final boolean multiGroup) { private Schedule createSchedule(final String s, final KnxGroupDto knxGroupDto) {
return createKnxGroupIfNotExists(name, new GroupAddress(address), dpt, readable, multiGroup); final Schedule schedule = new Schedule();
schedule.setEnabled(true);
schedule.setName(s);
schedule.setPropertyName(knxGroupDto.propertyName);
schedule.setPropertyType(knxGroupDto.getPropertyType());
return schedule;
} }
private KnxGroupDto createKnxGroupIfNotExists(final String name, final GroupAddress address, final String dpt, final boolean readable, final boolean multiGroup) { private KnxGroupDto createKnxGroupIfNotExists(final String name, final int address, final String dpt, final PropertyType type, final boolean readable, final boolean multiGroup) {
return knxGroupRepository.findByAddressRaw(address.getRawAddress()).map(KnxGroupDto::new).orElseGet(() -> knxGroupWriteService.create(name, address, dpt, readable, multiGroup)); return createKnxGroupIfNotExists(name, new GroupAddress(address), dpt, type, readable, multiGroup);
} }
private ScheduleEntry createRelative(final Schedule schedule, final int inSeconds, final int fuzzySeconds, final Map.Entry<String, String>... entries) { private KnxGroupDto createKnxGroupIfNotExists(final String name, final GroupAddress address, final String dpt, final PropertyType type, final boolean readable, final boolean multiGroup) {
return knxGroupRepository.findByAddressRaw(address.getRawAddress()).map(KnxGroupDto::new).orElseGet(() -> knxGroupWriteService.create(name, address, dpt, type, readable, multiGroup));
}
private ScheduleEntry createRelative(final Schedule schedule, final int inSeconds, final int fuzzySeconds, final Object value) {
final ZonedDateTime now = ZonedDateTime.now().plusSeconds(inSeconds).withNano(0); final ZonedDateTime now = ZonedDateTime.now().plusSeconds(inSeconds).withNano(0);
return newScheduleEntry(schedule, ScheduleEntryType.TIME, null, now.getHour(), now.getMinute(), now.getSecond(), fuzzySeconds, entries); return newScheduleEntry(schedule, ScheduleEntryType.TIME, null, now.getHour(), now.getMinute(), now.getSecond(), fuzzySeconds, value);
} }
private ScheduleEntry createTime(final Schedule schedule, final int hour, final int minute, final int second, final int fuzzySeconds, final Map.Entry<String, String>... entries) { private ScheduleEntry createTime(final Schedule schedule, final int hour, final int minute, final int second, final int fuzzySeconds, final Object value) {
return newScheduleEntry(schedule, ScheduleEntryType.TIME, null, hour, minute, second, fuzzySeconds, entries); return newScheduleEntry(schedule, ScheduleEntryType.TIME, null, hour, minute, second, fuzzySeconds, value);
} }
private ScheduleEntry createSunrise(final Schedule schedule, final Zenith zenith, final int fuzzySeconds, final Map.Entry<String, String>... entries) { private ScheduleEntry createSunrise(final Schedule schedule, final Zenith zenith, final int fuzzySeconds, final Object value) {
return newScheduleEntry(schedule, ScheduleEntryType.SUNRISE, zenith, 0, 0, 0, fuzzySeconds, entries); return newScheduleEntry(schedule, ScheduleEntryType.SUNRISE, zenith, 0, 0, 0, fuzzySeconds, value);
} }
private ScheduleEntry createSunset(final Schedule schedule, final Zenith zenith, final int fuzzySeconds, final Map.Entry<String, String>... entries) { private ScheduleEntry createSunset(final Schedule schedule, final Zenith zenith, final int fuzzySeconds, final Object value) {
return newScheduleEntry(schedule, ScheduleEntryType.SUNSET, zenith, 0, 0, 0, fuzzySeconds, entries); return newScheduleEntry(schedule, ScheduleEntryType.SUNSET, zenith, 0, 0, 0, fuzzySeconds, value);
} }
private ScheduleEntry newScheduleEntry(final Schedule schedule, final ScheduleEntryType type, final Zenith zenith, final int hour, final int minute, final int second, final int fuzzySeconds, final Map.Entry<String, String>... entries) { private ScheduleEntry newScheduleEntry(final Schedule schedule, final ScheduleEntryType type, final Zenith zenith, final int hour, final int minute, final int second, final int fuzzySeconds, final Object value) {
final ScheduleEntry entry = new ScheduleEntry(); final ScheduleEntry entry = new ScheduleEntry();
entry.setEnabled(true); entry.setEnabled(true);
entry.setType(type); entry.setType(type);
@ -148,7 +141,15 @@ public class DemoDataService {
entry.setMinute(minute); entry.setMinute(minute);
entry.setSecond(second); entry.setSecond(second);
entry.setFuzzySeconds(fuzzySeconds); entry.setFuzzySeconds(fuzzySeconds);
Arrays.stream(entries).forEach(p -> entry.getProperties().put(p.getKey(), p.getValue())); if (value instanceof Boolean) {
entry.setValue((boolean) value ? 1.0 : 0.0);
} else if (value instanceof Double) {
entry.setValue((Double) value);
} else if (value instanceof Integer) {
entry.setValue((Integer) value);
} else {
throw new RuntimeException();
}
schedule.getEntries().add(entry); schedule.getEntries().add(entry);
return entry; return entry;
} }

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.knx.group; package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.property.PropertyType;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -20,16 +21,21 @@ public class KnxGroup {
@Setter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
private Long id; private Long id;
@Column(unique = true) @Column(nullable = false, unique = true)
private int addressRaw; private int addressRaw;
@Column(unique = true) @Column(nullable = false, unique = true)
private String addressStr; private String addressStr;
@Column(nullable = false)
private String dpt; private String dpt;
@Column(nullable = false)
private String name; private String name;
@Column(nullable = false)
private PropertyType propertyType;
private byte[] value; private byte[] value;
private int lastDeviceAddressRaw; private int lastDeviceAddressRaw;
@ -46,6 +52,7 @@ public class KnxGroup {
private int readInterval; private int readInterval;
@Column(nullable = false)
private boolean multiGroup; private boolean multiGroup;
@Embedded @Embedded

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.knx.group; package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.property.PropertyType;
import lombok.Getter; import lombok.Getter;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@ -19,6 +20,8 @@ public class KnxGroupDto {
public final String name; public final String name;
public final PropertyType propertyType;
public final Boolean booleanValue; public final Boolean booleanValue;
public final Double numberValue; public final Double numberValue;
@ -32,6 +35,8 @@ public class KnxGroupDto {
propertyName = knxGroup.getPropertyName(); propertyName = knxGroup.getPropertyName();
dpt = knxGroup.getDpt(); dpt = knxGroup.getDpt();
name = knxGroup.getName(); name = knxGroup.getName();
propertyType = knxGroup.getPropertyType();
booleanValue = knxGroup.getBooleanValue(); booleanValue = knxGroup.getBooleanValue();
numberValue = knxGroup.getNumberValue(); numberValue = knxGroup.getNumberValue();
valueTimestamp = knxGroup.getValueTimestamp(); valueTimestamp = knxGroup.getValueTimestamp();

View File

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

View File

@ -43,7 +43,7 @@ public class KnxGroupSetService implements IPropertyOwner {
} }
@Override @Override
public void setProperty(final String propertyName, final String value) throws PropertySetException { public void setProperty(final String propertyName, final double value) throws PropertySetException {
final GroupAddress groupAddress = parseGroupAddress(propertyName); final GroupAddress groupAddress = parseGroupAddress(propertyName);
try { try {
if (knxGroupWriteService.setSendValue(groupAddress, value)) { if (knxGroupWriteService.setSendValue(groupAddress, value)) {

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.knx.group; package de.ph87.homeautomation.knx.group;
import de.ph87.homeautomation.property.PropertyType;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -16,7 +17,6 @@ import java.io.IOException;
import java.net.*; import java.net.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -108,17 +108,18 @@ public class KnxGroupWriteService {
knxGroupRepository.findAllByRead_AbleTrue().forEach(knxGroup -> knxGroup.getRead().setNextTimestamp(ZonedDateTime.now())); knxGroupRepository.findAllByRead_AbleTrue().forEach(knxGroup -> knxGroup.getRead().setNextTimestamp(ZonedDateTime.now()));
} }
public KnxGroupDto create(final String name, final GroupAddress address, final String dpt, final boolean readable, final boolean multiGroup) { public KnxGroupDto create(final String name, final GroupAddress address, final String dpt, final PropertyType type, final boolean readable, final boolean multiGroup) {
final KnxGroup trans = new KnxGroup(); final KnxGroup trans = new KnxGroup();
trans.setAddress(address); trans.setAddress(address);
trans.setDpt(dpt); trans.setDpt(dpt);
trans.setMultiGroup(multiGroup); trans.setMultiGroup(multiGroup);
trans.setName(name); trans.setName(name);
trans.setPropertyType(type);
trans.getRead().setAble(readable); trans.getRead().setAble(readable);
return new KnxGroupDto(knxGroupRepository.save(trans)); return new KnxGroupDto(knxGroupRepository.save(trans));
} }
public boolean setSendValue(final GroupAddress groupAddress, final String value) throws KnxGroupFormatException { public boolean setSendValue(final GroupAddress groupAddress, final double value) throws KnxGroupFormatException {
final Optional<KnxGroup> knxGroupOptional = knxGroupRepository.findByAddressRaw(groupAddress.getRawAddress()); final Optional<KnxGroup> knxGroupOptional = knxGroupRepository.findByAddressRaw(groupAddress.getRawAddress());
if (knxGroupOptional.isEmpty()) { if (knxGroupOptional.isEmpty()) {
return false; return false;
@ -128,16 +129,11 @@ public class KnxGroupWriteService {
try { try {
final DPTXlator translator = findTranslator(knxGroup); final DPTXlator translator = findTranslator(knxGroup);
if (translator instanceof DPTXlatorBoolean) { if (translator instanceof DPTXlatorBoolean) {
final boolean isTrue = Objects.equals(value, "true"); ((DPTXlatorBoolean) translator).setValue(value == 1.0);
final boolean isFalse = Objects.equals(value, "false");
if (!isTrue && !isFalse) {
throw new KnxGroupFormatException(knxGroup, value, "Must be \"true\" or \"false\".");
}
((DPTXlatorBoolean) translator).setValue(isTrue);
} else if (translator instanceof DPTXlator8BitUnsigned) { } else if (translator instanceof DPTXlator8BitUnsigned) {
((DPTXlator8BitUnsigned) translator).setValue(Integer.parseInt(value)); ((DPTXlator8BitUnsigned) translator).setValue((int) value);
} else { // TODO implement all DPTXlator... } else { // TODO implement all DPTXlator...
translator.setValue(value); translator.setValue("" + value);
} }
knxGroup.setSendValue(translator.getData()); knxGroup.setSendValue(translator.getData());
knxGroup.getSend().setNextTimestamp(ZonedDateTime.now()); knxGroup.getSend().setNextTimestamp(ZonedDateTime.now());

View File

@ -7,7 +7,7 @@ public interface IPropertyOwner {
Pattern getPropertyNamePattern(); Pattern getPropertyNamePattern();
void setProperty(final String propertyName, final String value) throws PropertySetException; void setProperty(final String propertyName, final double value) throws PropertySetException;
Boolean readBoolean(String propertyName); Boolean readBoolean(String propertyName);

View File

@ -16,7 +16,7 @@ public class PropertyService {
private final Set<IPropertyOwner> propertyOwners; private final Set<IPropertyOwner> propertyOwners;
public void set(final String propertyName, final String value) throws PropertySetException { public void set(final String propertyName, final double value) throws PropertySetException {
log.debug("Setting property \"{}\" => {}", propertyName, value); log.debug("Setting property \"{}\" => {}", propertyName, value);
getOwnerOrThrow(propertyName).setProperty(propertyName, value); getOwnerOrThrow(propertyName).setProperty(propertyName, value);
} }

View File

@ -2,8 +2,8 @@ package de.ph87.homeautomation.property;
public class PropertySetException extends Exception { public class PropertySetException extends Exception {
public PropertySetException(final String propertyName, final String value, final Exception e) { public PropertySetException(final String propertyName, final double value, final Exception e) {
super(String.format("Failed to set property %s to value %s: %s", propertyName, value, e.getMessage())); super(String.format("Failed to set property %s to value %f: %s", propertyName, value, e.getMessage()));
} }
} }

View File

@ -0,0 +1,5 @@
package de.ph87.homeautomation.property;
public enum PropertyType {
ON_OFF, PERCENT, LUX
}

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.schedule; package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.PropertyType;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry; import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
@ -26,6 +27,12 @@ public class Schedule {
@Column(nullable = false, unique = true) @Column(nullable = false, unique = true)
private String name; private String name;
@Column(nullable = false)
private String propertyName;
@Column(nullable = false)
private PropertyType propertyType;
@ToString.Exclude @ToString.Exclude
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Set<ScheduleEntry> entries = new HashSet<>(); private Set<ScheduleEntry> entries = new HashSet<>();

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.schedule; package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.PropertyType;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryDto; import de.ph87.homeautomation.schedule.entry.ScheduleEntryDto;
import lombok.Getter; import lombok.Getter;
@ -15,13 +16,19 @@ public class ScheduleDto {
public final String name; public final String name;
public final String propertyName;
public final PropertyType propertyType;
public final Set<ScheduleEntryDto> entries; public final Set<ScheduleEntryDto> entries;
public ScheduleDto(final Schedule schedule) { public ScheduleDto(final Schedule schedule) {
id = schedule.getId(); this.id = schedule.getId();
enabled = schedule.isEnabled(); this.enabled = schedule.isEnabled();
name = schedule.getName(); this.name = schedule.getName();
entries = schedule.getEntries().stream().map(ScheduleEntryDto::new).collect(Collectors.toSet()); this.propertyName = schedule.getPropertyName();
this.propertyType = schedule.getPropertyType();
this.entries = schedule.getEntries().stream().map(ScheduleEntryDto::new).collect(Collectors.toSet());
} }
} }

View File

@ -32,20 +32,18 @@ public class ScheduleExecutionService {
schedule.getEntries().stream() schedule.getEntries().stream()
.filter(entry -> entry.getNextFuzzyTimestamp() != null && !entry.getNextFuzzyTimestamp().isAfter(now)) .filter(entry -> entry.getNextFuzzyTimestamp() != null && !entry.getNextFuzzyTimestamp().isAfter(now))
.max(Comparator.comparing(ScheduleEntry::getNextFuzzyTimestamp)) .max(Comparator.comparing(ScheduleEntry::getNextFuzzyTimestamp))
.ifPresent(entry -> { .ifPresent(entry -> executeEntry(schedule, entry, now));
entry.setLastClearTimestamp(entry.getNextClearTimestamp());
log.info("Executing Schedule \"{}\" Entry {}", schedule.getName(), entry);
entry.getProperties().forEach(this::applyPropertyMapEntry);
scheduleCalculationService.calculateSchedule(schedule, now);
});
} }
private void applyPropertyMapEntry(final String propertyName, final String value) { private void executeEntry(final Schedule schedule, final ScheduleEntry entry, final ZonedDateTime now) {
entry.setLastClearTimestamp(entry.getNextClearTimestamp());
log.info("Executing Schedule \"{}\" Entry {}", schedule.getName(), entry);
try { try {
propertyService.set(propertyName, value); propertyService.set(schedule.getPropertyName(), entry.getValue());
} catch (PropertySetException e) { } catch (PropertySetException e) {
log.error(e.getMessage()); log.error(e.getMessage());
} }
scheduleCalculationService.calculateSchedule(schedule, now);
} }
} }

View File

@ -5,10 +5,11 @@ import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import javax.persistence.*; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Random; import java.util.Random;
@Getter @Getter
@ -52,6 +53,8 @@ public class ScheduleEntry {
private int fuzzySeconds = 0; private int fuzzySeconds = 0;
private double value = 0;
private ZonedDateTime nextClearTimestamp; private ZonedDateTime nextClearTimestamp;
private ZonedDateTime lastClearTimestamp; private ZonedDateTime lastClearTimestamp;
@ -59,9 +62,6 @@ public class ScheduleEntry {
@Setter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
private ZonedDateTime nextFuzzyTimestamp; private ZonedDateTime nextFuzzyTimestamp;
@ElementCollection(fetch = FetchType.EAGER)
private Map<String, String> properties = new HashMap<>();
public void setNextClearTimestamp(final ZonedDateTime next) { public void setNextClearTimestamp(final ZonedDateTime next) {
nextClearTimestamp = next; nextClearTimestamp = next;
if (nextClearTimestamp == null) { if (nextClearTimestamp == null) {

View File

@ -80,9 +80,19 @@ public class ScheduleEntryController {
return scheduleEntryWriteService.set(id, ScheduleEntry::setMinute, value); return scheduleEntryWriteService.set(id, ScheduleEntry::setMinute, value);
} }
@PostMapping("set/{id}/second")
public ScheduleEntryDto setSecond(@PathVariable final long id, @RequestBody final int value) {
return scheduleEntryWriteService.set(id, ScheduleEntry::setSecond, value);
}
@PostMapping("set/{id}/fuzzySeconds") @PostMapping("set/{id}/fuzzySeconds")
public ScheduleEntryDto setFuzzySeconds(@PathVariable final long id, @RequestBody final int value) { public ScheduleEntryDto setFuzzySeconds(@PathVariable final long id, @RequestBody final int value) {
return scheduleEntryWriteService.set(id, ScheduleEntry::setFuzzySeconds, value); return scheduleEntryWriteService.set(id, ScheduleEntry::setFuzzySeconds, value);
} }
@PostMapping("set/{id}/value")
public ScheduleEntryDto setValue(@PathVariable final long id, @RequestBody final double value) {
return scheduleEntryWriteService.set(id, ScheduleEntry::setValue, value);
}
} }

View File

@ -3,7 +3,6 @@ package de.ph87.homeautomation.schedule.entry;
import lombok.Getter; import lombok.Getter;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Map;
@Getter @Getter
public class ScheduleEntryDto { public class ScheduleEntryDto {
@ -44,28 +43,28 @@ public class ScheduleEntryDto {
public final ZonedDateTime nextFuzzyTimestamp; public final ZonedDateTime nextFuzzyTimestamp;
public final Map<String, String> properties; public final double value;
public ScheduleEntryDto(final ScheduleEntry scheduleEntry) { public ScheduleEntryDto(final ScheduleEntry entry) {
id = scheduleEntry.getId(); this.id = entry.getId();
enabled = scheduleEntry.isEnabled(); this.enabled = entry.isEnabled();
monday = scheduleEntry.isMonday(); this.monday = entry.isMonday();
tuesday = scheduleEntry.isTuesday(); this.tuesday = entry.isTuesday();
wednesday = scheduleEntry.isWednesday(); this.wednesday = entry.isWednesday();
thursday = scheduleEntry.isThursday(); this.thursday = entry.isThursday();
friday = scheduleEntry.isFriday(); this.friday = entry.isFriday();
saturday = scheduleEntry.isSaturday(); this.saturday = entry.isSaturday();
sunday = scheduleEntry.isSunday(); this.sunday = entry.isSunday();
type = scheduleEntry.getType(); this.type = entry.getType();
zenith = scheduleEntry.getZenith(); this.zenith = entry.getZenith();
hour = scheduleEntry.getHour(); this.hour = entry.getHour();
minute = scheduleEntry.getMinute(); this.minute = entry.getMinute();
second = scheduleEntry.getSecond(); this.second = entry.getSecond();
fuzzySeconds = scheduleEntry.getFuzzySeconds(); this.fuzzySeconds = entry.getFuzzySeconds();
nextClearTimestamp = scheduleEntry.getNextClearTimestamp(); this.nextClearTimestamp = entry.getNextClearTimestamp();
lastClearTimestamp = scheduleEntry.getLastClearTimestamp(); this.lastClearTimestamp = entry.getLastClearTimestamp();
nextFuzzyTimestamp = scheduleEntry.getNextFuzzyTimestamp(); this.nextFuzzyTimestamp = entry.getNextFuzzyTimestamp();
properties = Map.copyOf(scheduleEntry.getProperties()); this.value = entry.getValue();
} }
} }

View File

@ -5,7 +5,6 @@ import lombok.Getter;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.Optional; import java.util.Optional;
@Getter @Getter
@ -15,12 +14,12 @@ public class ScheduleNextExecutionDto {
public final ZonedDateTime nextTimestamp; public final ZonedDateTime nextTimestamp;
public final HashMap<String, String> properties; public final double numberValue;
private ScheduleNextExecutionDto(final Schedule schedule, final ScheduleEntry entry) { private ScheduleNextExecutionDto(final Schedule schedule, final ScheduleEntry entry) {
this.name = schedule.getName(); this.name = schedule.getName();
this.nextTimestamp = entry.getNextFuzzyTimestamp(); this.nextTimestamp = entry.getNextFuzzyTimestamp();
this.properties = new HashMap<>(entry.getProperties()); this.numberValue = entry.getValue();
} }
public static Optional<ScheduleNextExecutionDto> create(final Schedule schedule, final ZonedDateTime now) { public static Optional<ScheduleNextExecutionDto> create(final Schedule schedule, final ZonedDateTime now) {