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 enabled: boolean,
public name: string,
public propertyName: string,
public propertyType: string,
public entries: ScheduleEntry[],
) {
// nothing
@ -17,7 +19,9 @@ export class Schedule {
validateNumberNotNull(json['id']),
validateBooleanNotNull(json['enabled']),
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 {prefix} from "../../../helpers";
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);
}
}
import {validateBooleanNotNull, validateDateAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull} from "../../validators";
import {Timestamp} from "../../Timestamp";
export class ScheduleEntry {
@ -56,7 +22,7 @@ export class ScheduleEntry {
public lastClearTimestamp: Timestamp | null,
public nextClearTimestamp: Timestamp | null,
public nextFuzzyTimestamp: Timestamp | null,
public properties: Map<String, String>,
public value: number,
) {
// nothing
}
@ -81,7 +47,7 @@ export class ScheduleEntry {
Timestamp.fromDateOrNull(validateDateAllowNull(json['lastClearTimestamp'])),
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextClearTimestamp'])),
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextFuzzyTimestamp'])),
validateMap(json['properties'], validateStringNotEmptyNotNull),
validateNumberNotNull(json['value']),
);
}
@ -89,8 +55,13 @@ export class ScheduleEntry {
return item.id;
}
public static compareId(a: ScheduleEntry, b: ScheduleEntry): number {
return a.id - b.id;
public static compare(a: ScheduleEntry, b: ScheduleEntry): number {
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;
}
export function validateBooleanAllowNull(value: any): boolean | null {
if (value === null || value === undefined) {
return null;
}
return validateBooleanNotNull(value);
}
export function validateNumberNotNull(value: any): number {
const number: number = parseFloat(value);
if (isNaN(number)) {
@ -13,6 +20,13 @@ export function validateNumberNotNull(value: any): 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 {
if (!(typeof value === 'string')) {
throw new Error("Not a string: " + value);
@ -23,25 +37,26 @@ export function validateStringNotEmptyNotNull(value: any): string {
return value;
}
export function validateStringNotEmpty_Nullable(value: any): string | null {
export function validateStringNotEmpty_AllowNull(value: any): string | null {
if (value === null || value === undefined || value === '') {
return null;
}
if (!(typeof value === 'string')) {
throw new Error("Not a string: " + value);
return validateStringNotEmptyNotNull(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 {
if (value === null || value === undefined || value === 0 || value === '') {
return null;
}
const number: number = Date.parse(value);
if (isNaN(number)) {
throw new Error("Not a Date: " + value);
}
return new Date(number);
return validateDateNotNull(value);
}
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="item" routerLink="/ScheduleList" routerLinkActive="itemActive">
Zeitplan
Zeitpläne
</div>
<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>Unschärfe</th>
<th colspan="6">Nächste Ausführung</th>
<th>Wert</th>
<th>&nbsp;</th>
</tr>
<tr *ngFor="let entry of schedule.entries" [class.disabled]="entry.nextClearTimestamp === null">
@ -70,7 +71,7 @@
<ng-container *ngIf="entry.type === 'TIME'">
<td class="first">
<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>
</td>
<td class="middle">:</td>
@ -120,6 +121,15 @@
<td class="empty last"></td>
</ng-container>
<td *ngIf="schedule.propertyType === 'ON_OFF'" [class.true]="entry.value" [class.false]="!entry.value" (click)="set(entry, 'value', entry.value > 0 ? 0 : 1)">
{{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)">
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
</td>

View File

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

View File

@ -55,7 +55,6 @@ export class ScheduleComponent implements OnInit {
} else {
this.schedule.entries[index] = entry;
}
this.schedule.entries = this.schedule.entries.sort(ScheduleEntry.compareId)
}
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.KnxGroupRepository;
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.ScheduleRepository;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
@ -16,8 +16,6 @@ import tuwien.auto.calimero.GroupAddress;
import javax.annotation.PostConstruct;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Map;
@SuppressWarnings({"unchecked", "UnusedReturnValue", "SameParameterValue", "UnusedAssignment", "RedundantSuppression"})
@Slf4j
@ -35,109 +33,104 @@ public class DemoDataService {
@PostConstruct
public void postConstruct() {
final KnxGroupDto eg_flur_licht_schalten = createKnxGroupIfNotExists("EG Flur Licht Schalten", 1294, "1.001", false, false);
final KnxGroupDto eg_ambiente_schalten = createKnxGroupIfNotExists("EG Ambiente Schalten", 848, "1.001", false, false);
final KnxGroupDto og_ambiente_schalten = createKnxGroupIfNotExists("OG Ambiente Schalten", 1539, "1.001", false, false);
final KnxGroupDto wohnzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Wohnzimmer Rollladen Position Anfahren", new GroupAddress(0, 4, 24), "5.001", false, false);
final KnxGroupDto schlafzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Schlafzimmer Rollladen Position Anfahren", new GroupAddress(0, 3, 3), "5.001", false, false);
final KnxGroupDto flur_rollladen_position_anfahren = createKnxGroupIfNotExists("Flur Rollladen Position Anfahren", new GroupAddress(0, 5, 13), "5.001", false, false);
final KnxGroupDto bad_licht_mitteschalten = createKnxGroupIfNotExists("Bad Licht Mitte Schalten", 797, "1.001", false, false);
final KnxGroupDto helligkeit = createKnxGroupIfNotExists("Helligkeit", 1286, "9.004", false, true);
final KnxGroupDto eg_flur_licht_schalten = createKnxGroupIfNotExists("EG Flur Licht Schalten", 1294, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto eg_ambiente_schalten = createKnxGroupIfNotExists("EG Ambiente Schalten", 848, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto og_ambiente_schalten = createKnxGroupIfNotExists("OG Ambiente Schalten", 1539, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto wohnzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Wohnzimmer Rollladen Position Anfahren", new GroupAddress(0, 4, 24), "5.001", PropertyType.PERCENT, false, false);
final KnxGroupDto schlafzimmer_rollladen_position_anfahren = createKnxGroupIfNotExists("Schlafzimmer Rollladen Position Anfahren", new GroupAddress(0, 3, 3), "5.001", PropertyType.PERCENT, false, false);
final KnxGroupDto flur_rollladen_position_anfahren = createKnxGroupIfNotExists("Flur Rollladen Position Anfahren", new GroupAddress(0, 5, 13), "5.001", PropertyType.PERCENT, false, false);
final KnxGroupDto bad_licht_mitte_schalten = createKnxGroupIfNotExists("Bad Licht Mitte Schalten", 797, "1.001", PropertyType.ON_OFF, false, false);
final KnxGroupDto helligkeit = createKnxGroupIfNotExists("Helligkeit", 1286, "9.004", PropertyType.LUX, false, true);
if (scheduleRepository.count() == 0) {
final Schedule scheduleEgFlurLicht = new Schedule();
scheduleEgFlurLicht.setEnabled(true);
scheduleEgFlurLicht.setName("EG Flur Licht");
createTime(scheduleEgFlurLicht, 11, 30, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, true));
createTime(scheduleEgFlurLicht, 12, 30, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, false));
createTime(scheduleEgFlurLicht, 16, 30, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, true));
createTime(scheduleEgFlurLicht, 17, 30, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, false));
createTime(scheduleEgFlurLicht, 22, 0, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, true));
createTime(scheduleEgFlurLicht, 23, 0, 0, MIN30, new PropertyEntry(eg_flur_licht_schalten, 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));
final Schedule scheduleEgFlurLicht = createSchedule("EG Flur Licht", eg_flur_licht_schalten);
createTime(scheduleEgFlurLicht, 11, 30, 0, MIN30, true);
createTime(scheduleEgFlurLicht, 12, 30, 0, MIN30, false);
createTime(scheduleEgFlurLicht, 16, 30, 0, MIN30, true);
createTime(scheduleEgFlurLicht, 17, 30, 0, MIN30, false);
createTime(scheduleEgFlurLicht, 22, 0, 0, MIN30, true);
createTime(scheduleEgFlurLicht, 23, 0, 0, MIN30, false);
createTime(scheduleEgFlurLicht, 1, 0, 0, MIN30, true);
createTime(scheduleEgFlurLicht, 2, 0, 0, MIN30, false);
scheduleRepository.save(scheduleEgFlurLicht);
final Schedule scheduleEgAmbiente = new Schedule();
scheduleEgAmbiente.setEnabled(true);
scheduleEgAmbiente.setName("EG Ambiente");
createTime(scheduleEgAmbiente, 7, 15, 0, MIN30, new PropertyEntry(eg_ambiente_schalten, true));
createTime(scheduleEgAmbiente, 9, 30, 0, MIN30, new PropertyEntry(eg_ambiente_schalten, false));
createSunset(scheduleEgAmbiente, Zenith.OFFICIAL, MIN30, new PropertyEntry(eg_ambiente_schalten, true));
createSunset(scheduleEgAmbiente, Zenith.ASTRONOMICAL, MIN30, new PropertyEntry(eg_ambiente_schalten, false));
final Schedule scheduleEgAmbiente = createSchedule("EG Ambiente", eg_ambiente_schalten);
createTime(scheduleEgAmbiente, 7, 15, 0, MIN30, true);
createTime(scheduleEgAmbiente, 9, 30, 0, MIN30, false);
createSunset(scheduleEgAmbiente, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleEgAmbiente, Zenith.ASTRONOMICAL, MIN30, false);
scheduleRepository.save(scheduleEgAmbiente);
final Schedule scheduleOgAmbiente = new Schedule();
scheduleOgAmbiente.setEnabled(true);
scheduleOgAmbiente.setName("OG Ambiente");
createTime(scheduleOgAmbiente, 7, 15, 0, MIN30, new PropertyEntry(og_ambiente_schalten, true));
createTime(scheduleOgAmbiente, 9, 30, 0, MIN30, new PropertyEntry(og_ambiente_schalten, false));
createSunset(scheduleOgAmbiente, Zenith.OFFICIAL, MIN30, new PropertyEntry(og_ambiente_schalten, true));
createSunset(scheduleOgAmbiente, Zenith.ASTRONOMICAL, MIN30, new PropertyEntry(og_ambiente_schalten, false));
final Schedule scheduleOgAmbiente = createSchedule("OG Ambiente", og_ambiente_schalten);
createTime(scheduleOgAmbiente, 7, 15, 0, MIN30, true);
createTime(scheduleOgAmbiente, 9, 30, 0, MIN30, false);
createSunset(scheduleOgAmbiente, Zenith.OFFICIAL, MIN30, true);
createSunset(scheduleOgAmbiente, Zenith.ASTRONOMICAL, MIN30, false);
scheduleRepository.save(scheduleOgAmbiente);
final Schedule scheduleWohnzimmerRollladen = new Schedule();
scheduleWohnzimmerRollladen.setEnabled(true);
scheduleWohnzimmerRollladen.setName("Rollläden Wohnzimmer");
createSunrise(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, new PropertyEntry(wohnzimmer_rollladen_position_anfahren, 0));
createSunset(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, new PropertyEntry(wohnzimmer_rollladen_position_anfahren, 100));
final Schedule scheduleWohnzimmerRollladen = createSchedule("Rollläden Wohnzimmer", wohnzimmer_rollladen_position_anfahren);
createSunrise(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, 0);
createSunset(scheduleWohnzimmerRollladen, Zenith.CIVIL, 0, 100);
scheduleRepository.save(scheduleWohnzimmerRollladen);
final Schedule scheduleSchlafzimmerRollladen = new Schedule();
scheduleSchlafzimmerRollladen.setEnabled(true);
scheduleSchlafzimmerRollladen.setName("Rollläden Schlafzimmer");
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));
final Schedule scheduleSchlafzimmerRollladen = createSchedule("Rollläden Schlafzimmer", schlafzimmer_rollladen_position_anfahren);
createTime(scheduleSchlafzimmerRollladen, 7, 0, 0, 0, 0);
createSunset(scheduleSchlafzimmerRollladen, Zenith.CIVIL, 0, 100);
scheduleRepository.save(scheduleSchlafzimmerRollladen);
final Schedule scheduleFlurRollladen = new Schedule();
scheduleFlurRollladen.setEnabled(true);
scheduleFlurRollladen.setName("Rollläden Flur");
createSunrise(scheduleFlurRollladen, Zenith.CIVIL, 0, new PropertyEntry(flur_rollladen_position_anfahren, 0));
createSunset(scheduleFlurRollladen, Zenith.CIVIL, 0, new PropertyEntry(flur_rollladen_position_anfahren, 100));
final Schedule scheduleFlurRollladen = createSchedule("Rollläden Flur", flur_rollladen_position_anfahren);
createSunrise(scheduleFlurRollladen, Zenith.CIVIL, 0, 0);
createSunset(scheduleFlurRollladen, Zenith.CIVIL, 0, 100);
scheduleRepository.save(scheduleFlurRollladen);
final Schedule scheduleBadLichtMitte = new Schedule();
scheduleBadLichtMitte.setEnabled(true);
scheduleBadLichtMitte.setName("Bad Licht Mitte");
createTime(scheduleBadLichtMitte, 10, 30, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, true));
createTime(scheduleBadLichtMitte, 11, 30, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, false));
createTime(scheduleBadLichtMitte, 15, 30, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, true));
createTime(scheduleBadLichtMitte, 16, 30, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, false));
createTime(scheduleBadLichtMitte, 21, 0, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, true));
createTime(scheduleBadLichtMitte, 22, 0, 0, MIN30, new PropertyEntry(bad_licht_mitteschalten, 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));
final Schedule scheduleBadLichtMitte = createSchedule("Bad Licht Mitte", bad_licht_mitte_schalten);
createTime(scheduleBadLichtMitte, 10, 30, 0, MIN30, true);
createTime(scheduleBadLichtMitte, 11, 30, 0, MIN30, false);
createTime(scheduleBadLichtMitte, 15, 30, 0, MIN30, true);
createTime(scheduleBadLichtMitte, 16, 30, 0, MIN30, false);
createTime(scheduleBadLichtMitte, 21, 0, 0, MIN30, true);
createTime(scheduleBadLichtMitte, 22, 0, 0, MIN30, false);
createTime(scheduleBadLichtMitte, 0, 0, 0, MIN30, true);
createTime(scheduleBadLichtMitte, 1, 0, 0, MIN30, false);
scheduleRepository.save(scheduleBadLichtMitte);
}
}
private KnxGroupDto createKnxGroupIfNotExists(final String name, final int address, final String dpt, final boolean readable, final boolean multiGroup) {
return createKnxGroupIfNotExists(name, new GroupAddress(address), dpt, readable, multiGroup);
private Schedule createSchedule(final String s, final KnxGroupDto knxGroupDto) {
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) {
return knxGroupRepository.findByAddressRaw(address.getRawAddress()).map(KnxGroupDto::new).orElseGet(() -> knxGroupWriteService.create(name, address, dpt, readable, multiGroup));
private KnxGroupDto createKnxGroupIfNotExists(final String name, final int address, final String dpt, final PropertyType type, final boolean readable, final boolean multiGroup) {
return createKnxGroupIfNotExists(name, new GroupAddress(address), dpt, type, readable, multiGroup);
}
private 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);
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) {
return newScheduleEntry(schedule, ScheduleEntryType.TIME, null, hour, minute, second, fuzzySeconds, 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, value);
}
private ScheduleEntry createSunrise(final Schedule schedule, final Zenith zenith, final int fuzzySeconds, final Map.Entry<String, String>... entries) {
return newScheduleEntry(schedule, ScheduleEntryType.SUNRISE, zenith, 0, 0, 0, fuzzySeconds, 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, value);
}
private ScheduleEntry createSunset(final Schedule schedule, final Zenith zenith, final int fuzzySeconds, final Map.Entry<String, String>... entries) {
return newScheduleEntry(schedule, ScheduleEntryType.SUNSET, zenith, 0, 0, 0, fuzzySeconds, 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, 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();
entry.setEnabled(true);
entry.setType(type);
@ -148,7 +141,15 @@ public class DemoDataService {
entry.setMinute(minute);
entry.setSecond(second);
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);
return entry;
}

View File

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

View File

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

View File

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

View File

@ -43,7 +43,7 @@ public class KnxGroupSetService implements IPropertyOwner {
}
@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);
try {
if (knxGroupWriteService.setSendValue(groupAddress, value)) {

View File

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

View File

@ -7,7 +7,7 @@ public interface IPropertyOwner {
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);

View File

@ -16,7 +16,7 @@ public class PropertyService {
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);
getOwnerOrThrow(propertyName).setProperty(propertyName, value);
}

View File

@ -2,8 +2,8 @@ package de.ph87.homeautomation.property;
public class PropertySetException extends Exception {
public PropertySetException(final String propertyName, final String value, final Exception e) {
super(String.format("Failed to set property %s to value %s: %s", propertyName, value, e.getMessage()));
public PropertySetException(final String propertyName, final double value, final Exception e) {
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;
import de.ph87.homeautomation.property.PropertyType;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import lombok.AccessLevel;
import lombok.Getter;
@ -26,6 +27,12 @@ public class Schedule {
@Column(nullable = false, unique = true)
private String name;
@Column(nullable = false)
private String propertyName;
@Column(nullable = false)
private PropertyType propertyType;
@ToString.Exclude
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Set<ScheduleEntry> entries = new HashSet<>();

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.PropertyType;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryDto;
import lombok.Getter;
@ -15,13 +16,19 @@ public class ScheduleDto {
public final String name;
public final String propertyName;
public final PropertyType propertyType;
public final Set<ScheduleEntryDto> entries;
public ScheduleDto(final Schedule schedule) {
id = schedule.getId();
enabled = schedule.isEnabled();
name = schedule.getName();
entries = schedule.getEntries().stream().map(ScheduleEntryDto::new).collect(Collectors.toSet());
this.id = schedule.getId();
this.enabled = schedule.isEnabled();
this.name = schedule.getName();
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()
.filter(entry -> entry.getNextFuzzyTimestamp() != null && !entry.getNextFuzzyTimestamp().isAfter(now))
.max(Comparator.comparing(ScheduleEntry::getNextFuzzyTimestamp))
.ifPresent(entry -> {
entry.setLastClearTimestamp(entry.getNextClearTimestamp());
log.info("Executing Schedule \"{}\" Entry {}", schedule.getName(), entry);
entry.getProperties().forEach(this::applyPropertyMapEntry);
scheduleCalculationService.calculateSchedule(schedule, now);
});
.ifPresent(entry -> executeEntry(schedule, entry, 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 {
propertyService.set(propertyName, value);
propertyService.set(schedule.getPropertyName(), entry.getValue());
} catch (PropertySetException e) {
log.error(e.getMessage());
}
scheduleCalculationService.calculateSchedule(schedule, now);
}
}

View File

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

View File

@ -80,9 +80,19 @@ public class ScheduleEntryController {
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")
public ScheduleEntryDto setFuzzySeconds(@PathVariable final long id, @RequestBody final int 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 java.time.ZonedDateTime;
import java.util.Map;
@Getter
public class ScheduleEntryDto {
@ -44,28 +43,28 @@ public class ScheduleEntryDto {
public final ZonedDateTime nextFuzzyTimestamp;
public final Map<String, String> properties;
public final double value;
public ScheduleEntryDto(final ScheduleEntry scheduleEntry) {
id = scheduleEntry.getId();
enabled = scheduleEntry.isEnabled();
monday = scheduleEntry.isMonday();
tuesday = scheduleEntry.isTuesday();
wednesday = scheduleEntry.isWednesday();
thursday = scheduleEntry.isThursday();
friday = scheduleEntry.isFriday();
saturday = scheduleEntry.isSaturday();
sunday = scheduleEntry.isSunday();
type = scheduleEntry.getType();
zenith = scheduleEntry.getZenith();
hour = scheduleEntry.getHour();
minute = scheduleEntry.getMinute();
second = scheduleEntry.getSecond();
fuzzySeconds = scheduleEntry.getFuzzySeconds();
nextClearTimestamp = scheduleEntry.getNextClearTimestamp();
lastClearTimestamp = scheduleEntry.getLastClearTimestamp();
nextFuzzyTimestamp = scheduleEntry.getNextFuzzyTimestamp();
properties = Map.copyOf(scheduleEntry.getProperties());
public ScheduleEntryDto(final ScheduleEntry entry) {
this.id = entry.getId();
this.enabled = entry.isEnabled();
this.monday = entry.isMonday();
this.tuesday = entry.isTuesday();
this.wednesday = entry.isWednesday();
this.thursday = entry.isThursday();
this.friday = entry.isFriday();
this.saturday = entry.isSaturday();
this.sunday = entry.isSunday();
this.type = entry.getType();
this.zenith = entry.getZenith();
this.hour = entry.getHour();
this.minute = entry.getMinute();
this.second = entry.getSecond();
this.fuzzySeconds = entry.getFuzzySeconds();
this.nextClearTimestamp = entry.getNextClearTimestamp();
this.lastClearTimestamp = entry.getLastClearTimestamp();
this.nextFuzzyTimestamp = entry.getNextFuzzyTimestamp();
this.value = entry.getValue();
}
}

View File

@ -5,7 +5,6 @@ import lombok.Getter;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Optional;
@Getter
@ -15,12 +14,12 @@ public class ScheduleNextExecutionDto {
public final ZonedDateTime nextTimestamp;
public final HashMap<String, String> properties;
public final double numberValue;
private ScheduleNextExecutionDto(final Schedule schedule, final ScheduleEntry entry) {
this.name = schedule.getName();
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) {