Provide detailed Sunrise/-set times per Schedule from Server
This commit is contained in:
parent
0b146b5972
commit
fd5ceced45
25
src/main/angular/src/app/api/astro/Astro.ts
Normal file
25
src/main/angular/src/app/api/astro/Astro.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {validateDateAllowNull, validateNumberNotNull, validateStringNullToEmpty} from "../validators";
|
||||
|
||||
export class Astro {
|
||||
|
||||
constructor(
|
||||
readonly zenith: number,
|
||||
readonly sunrise: Date,
|
||||
readonly sunset: Date,
|
||||
readonly sunriseName: string,
|
||||
readonly sunsetName: string,
|
||||
) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
static fromJson(json: any): Astro {
|
||||
return new Astro(
|
||||
validateNumberNotNull(json['zenith']),
|
||||
validateDateAllowNull(json['sunrise']),
|
||||
validateDateAllowNull(json['sunset']),
|
||||
validateStringNullToEmpty(json['sunriseName']),
|
||||
validateStringNullToEmpty(json['sunsetName']),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,17 +1,22 @@
|
||||
import {validateBooleanNotNull, validateListOrEmpty, validateNumberNotNull, validateStringNotEmptyNotNull} from "../validators";
|
||||
import {ScheduleEntry} from "./entry/ScheduleEntry";
|
||||
import {Astro} from "../astro/Astro";
|
||||
|
||||
export class Schedule {
|
||||
|
||||
readonly next?: ScheduleEntry;
|
||||
|
||||
readonly last?: ScheduleEntry;
|
||||
|
||||
constructor(
|
||||
readonly id: number,
|
||||
readonly enabled: boolean,
|
||||
readonly title: string,
|
||||
readonly entries: ScheduleEntry[],
|
||||
readonly astros: Astro[],
|
||||
) {
|
||||
this.next = entries.filter(e => e.nextFuzzyTimestamp).sort((a, b) => a.nextFuzzyTimestamp.date.getTime() - b.nextFuzzyTimestamp.date.getTime())[0];
|
||||
this.last = entries.filter(e => e.lastFuzzyTimestamp).sort((a, b) => b.nextFuzzyTimestamp.date.getTime() - a.nextFuzzyTimestamp.date.getTime())[0];
|
||||
}
|
||||
|
||||
static fromJson(json: any): Schedule {
|
||||
@ -20,6 +25,7 @@ export class Schedule {
|
||||
validateBooleanNotNull(json['enabled']),
|
||||
validateStringNotEmptyNotNull(json['title']),
|
||||
validateListOrEmpty(json['entries'], ScheduleEntry.fromJson, ScheduleEntry.comparePosition),
|
||||
validateListOrEmpty(json['astros'], Astro.fromJson),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ export class ScheduleEntry {
|
||||
readonly lastClearTimestamp: Timestamp | null,
|
||||
readonly nextClearTimestamp: Timestamp | null,
|
||||
readonly nextFuzzyTimestamp: Timestamp | null,
|
||||
readonly lastFuzzyTimestamp: Timestamp | null,
|
||||
readonly property: Property | null,
|
||||
readonly value: number,
|
||||
readonly bulk: Bulk | null,
|
||||
@ -57,6 +58,7 @@ export class ScheduleEntry {
|
||||
Timestamp.fromDateOrNull(validateDateAllowNull(json['lastClearTimestamp'])),
|
||||
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextClearTimestamp'])),
|
||||
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextFuzzyTimestamp'])),
|
||||
Timestamp.fromDateOrNull(validateDateAllowNull(json['lastFuzzyTimestamp'])),
|
||||
Property.fromJsonAllowNull(json['property']),
|
||||
validateNumberNotNull(json['value']),
|
||||
Bulk.fromJsonOrNull(json['bulk']),
|
||||
|
||||
@ -17,6 +17,8 @@ import {PropertyListComponent} from './pages/property/list/property-list.compone
|
||||
import {ChannelListComponent} from './pages/channel/list/channel-list.component';
|
||||
import {BulkListComponent} from './pages/bulk/list/bulk-list.component';
|
||||
import {BulkEditorComponent} from './pages/bulk/editor/bulk-editor.component';
|
||||
import {LeftPadDirective} from './pipes/left-pad.directive';
|
||||
import {EntryValueComponent} from './shared/entry-value/entry-value.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -33,6 +35,8 @@ import {BulkEditorComponent} from './pages/bulk/editor/bulk-editor.component';
|
||||
DeviceListComponent,
|
||||
BulkListComponent,
|
||||
BulkEditorComponent,
|
||||
LeftPadDirective,
|
||||
EntryValueComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
@ -55,23 +55,11 @@
|
||||
<ng-container *ngIf="entry.type === 'SUNRISE' || entry.type === 'SUNSET'">
|
||||
<td>
|
||||
<select [ngModel]="entry.zenith" (ngModelChange)="set(entry, 'zenith', $event)">
|
||||
<option value="87">
|
||||
[ 87°]
|
||||
<ng-container *ngIf="entry.type === 'SUNRISE'">Nach Sonnenaufgang</ng-container>
|
||||
<ng-container *ngIf="entry.type === 'SUNSET'">Vor Sonnenuntergang</ng-container>
|
||||
<option *ngFor="let event of schedule.astros; let index = index" [value]="event.zenith">
|
||||
[{{event.zenith | number:'0.1-1' | leftPad:5}}°, {{(entry.type === 'SUNRISE' ? event.sunrise : event.sunset) | date:'HH:mm'}}] {{entry.type === 'SUNRISE' ? event.sunriseName : event.sunsetName}}
|
||||
</option>
|
||||
<option value="90.8333">
|
||||
[ 90°]
|
||||
<ng-container *ngIf="entry.type === 'SUNRISE'">Sonnenaufgang</ng-container>
|
||||
<ng-container *ngIf="entry.type === 'SUNSET'">Sonnenuntergang</ng-container>
|
||||
</option>
|
||||
<option value="93">[ 93°]</option>
|
||||
<option value="96">[ 96°] Bürgerliche Dämmerung</option>
|
||||
<option value="99">[ 99°]</option>
|
||||
<option value="102">[102°] Nautische Dämmerung</option>
|
||||
<option value="105">[105°]</option>
|
||||
<option value="108">[108°] Astronomische Dämmerung</option>
|
||||
</select>
|
||||
|
||||
</td>
|
||||
</ng-container>
|
||||
<td *ngIf="entry.type !== 'SUNRISE' && entry.type !== 'SUNSET'" class="empty"></td>
|
||||
@ -85,7 +73,7 @@
|
||||
<td class="middle">:</td>
|
||||
<td class="last">
|
||||
<select [ngModel]="entry.minute" (ngModelChange)="set(entry, 'minute', $event)">
|
||||
<option *ngFor="let _ of [].constructor(60); let value = index" [ngValue]="value">{{value | number:'2.0'}}</option>
|
||||
<option *ngFor="let _ of [].constructor(12); let value = index" [ngValue]="value * 5">{{value * 5 | number:'2.0'}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</ng-container>
|
||||
@ -132,44 +120,10 @@
|
||||
<td>
|
||||
<app-search [searchService]="propertyService" [initial]="entry.property?.id" (valueChange)="set(entry, 'property', $event)"></app-search>
|
||||
</td>
|
||||
<ng-container [ngSwitch]="entry.property?.type">
|
||||
<td *ngSwitchCase="'BOOLEAN'" [class.true]="entry.value" [class.false]="!entry.value" (click)="set(entry, 'value', entry.value > 0 ? 0 : 1)">
|
||||
{{entry.value ? "An" : "Aus"}}
|
||||
</td>
|
||||
<td *ngSwitchCase="'SHUTTER'" [class.true]="entry.value === 0" [class.false]="entry.value === 100" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', $event)">
|
||||
<option [ngValue]="0">100% Offen</option>
|
||||
<option [ngValue]="35"> 50%</option>
|
||||
<option [ngValue]="55"> 75%</option>
|
||||
<option [ngValue]="75"> 90% Sonnenschutz</option>
|
||||
<option [ngValue]="85">100% Schlitze</option>
|
||||
<option [ngValue]="100">100% Geschlossen</option>
|
||||
</select>
|
||||
</td>
|
||||
<td *ngSwitchCase="'BRIGHTNESS_PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', $event)">
|
||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
||||
</select>
|
||||
</td>
|
||||
<td *ngSwitchCase="'COLOR_TEMPERATURE'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', $event)">
|
||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
||||
</select>
|
||||
</td>
|
||||
<td *ngSwitchCase="'LUX'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', $event)">
|
||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
||||
</select>
|
||||
</td>
|
||||
<td *ngSwitchCase="'SCENE'">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', $event)">
|
||||
<option *ngFor="let scene of scenes" [ngValue]="scene.number">#{{scene.number | number:'2.0-0'}} {{scene.title}}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td *ngSwitchDefault class="empty">
|
||||
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<td>
|
||||
<app-entry-value [entry]="entry" [allowChange]="true" (onSet)="set(entry, $event.key, $event.value)"></app-entry-value>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<app-search [searchService]="bulkService" [initial]="entry.bulk?.id" (valueChange)="set(entry, 'bulk', $event)"></app-search>
|
||||
|
||||
@ -6,8 +6,6 @@ import {ScheduleEntryService} from "../../../api/schedule/entry/schedule-entry.s
|
||||
import {faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCircle, faTimesCircle} from '@fortawesome/free-regular-svg-icons';
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {PropertyService} from "../../../api/property/property.service";
|
||||
import {Scene} from "../../../api/scene/Scene";
|
||||
import {SceneService} from "../../../api/scene/scene.service";
|
||||
import {BulkService} from "../../../api/bulk/BulkService";
|
||||
import {Update} from "../../../api/Update";
|
||||
import {NO_OP} from "../../../api/api.service";
|
||||
@ -35,8 +33,6 @@ export class ScheduleEditorComponent implements OnInit {
|
||||
|
||||
schedule!: Schedule;
|
||||
|
||||
scenes: Scene[] = [];
|
||||
|
||||
constructor(
|
||||
readonly router: Router,
|
||||
readonly activatedRoute: ActivatedRoute,
|
||||
@ -44,14 +40,12 @@ export class ScheduleEditorComponent implements OnInit {
|
||||
readonly scheduleEntryService: ScheduleEntryService,
|
||||
readonly propertyService: PropertyService,
|
||||
readonly bulkService: BulkService,
|
||||
readonly sceneService: SceneService,
|
||||
) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.scheduleService.subscribe(update => this.update(update));
|
||||
this.sceneService.findAll(scenes => this.scenes = scenes);
|
||||
this.activatedRoute.params.subscribe(params => this.scheduleService.getById(params['id'], schedule => this.schedule = schedule));
|
||||
}
|
||||
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Bezeichnung</th>
|
||||
<th colspan="3">Zeitpunkt</th>
|
||||
<th colspan="3">Nächste</th>
|
||||
<th colspan="3">Eigenschaft</th>
|
||||
<th>Massenausführung</th>
|
||||
<th colspan="3">Letzte</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr *ngFor="let schedule of schedules; trackBy: Schedule.trackBy">
|
||||
@ -24,12 +25,18 @@
|
||||
|
||||
<td class="number first" [class.empty]="!schedule.next?.property">{{schedule.next?.property?.title}}</td>
|
||||
<td class="number middle" [class.empty]="!schedule.next?.property"> = </td>
|
||||
<td class="number last" [class.empty]="!schedule.next?.property">{{schedule.next?.value}}</td>
|
||||
<td class="number last" [class.empty]="!schedule.next?.property">
|
||||
<app-entry-value *ngIf="schedule.next" [entry]="schedule.next" [allowChange]="false"></app-entry-value>
|
||||
</td>
|
||||
|
||||
<td [class.empty]="!schedule.next?.bulk">
|
||||
{{schedule.next?.bulk?.name}}
|
||||
</td>
|
||||
|
||||
<td class="number first" [class.empty]="!schedule.last?.lastFuzzyTimestamp">{{schedule.last?.lastFuzzyTimestamp.dayName}}</td>
|
||||
<td class="number middle" [class.empty]="!schedule.last?.lastFuzzyTimestamp">: </td>
|
||||
<td class="number last" [class.empty]="!schedule.last?.lastFuzzyTimestamp">{{schedule.last?.lastFuzzyTimestamp.timeString}}</td>
|
||||
|
||||
<td class="delete" (click)="delete(schedule)">
|
||||
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
|
||||
</td>
|
||||
|
||||
20
src/main/angular/src/app/pipes/left-pad.directive.ts
Normal file
20
src/main/angular/src/app/pipes/left-pad.directive.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'leftPad',
|
||||
})
|
||||
export class LeftPadDirective implements PipeTransform {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
transform(value: any, count: number): any {
|
||||
let result = "" + value;
|
||||
const rest: number = count - result.length;
|
||||
if (rest > 0) {
|
||||
result = " ".repeat(rest) + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
<ng-container [ngSwitch]="entry.property?.type">
|
||||
|
||||
<div *ngSwitchCase="'BOOLEAN'" [class.true]="entry.value" [class.false]="!entry.value" (click)="set('value', entry.value > 0 ? 0 : 1)">
|
||||
{{entry.value ? "An" : "Aus"}}
|
||||
</div>
|
||||
|
||||
<div *ngSwitchCase="'SHUTTER'" [class.true]="entry.value === 0" [class.false]="entry.value === 100" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
||||
<option [ngValue]="0">100% Offen</option>
|
||||
<option [ngValue]="35"> 50%</option>
|
||||
<option [ngValue]="55"> 75%</option>
|
||||
<option [ngValue]="75"> 90% Sonnenschutz</option>
|
||||
<option [ngValue]="85">100% Schlitze</option>
|
||||
<option [ngValue]="100">100% Geschlossen</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div *ngSwitchCase="'BRIGHTNESS_PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div *ngSwitchCase="'COLOR_TEMPERATURE'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div *ngSwitchCase="'LUX'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div *ngSwitchCase="'SCENE'">
|
||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
||||
<option *ngFor="let scene of scenes" [ngValue]="scene.number">#{{scene.number | number:'2.0-0'}} {{scene.title}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div *ngSwi
|
||||
tchDefault class="empty">
|
||||
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
@ -0,0 +1,51 @@
|
||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||
import {ScheduleEntry} from "../../api/schedule/entry/ScheduleEntry";
|
||||
import {Scene} from "../../api/scene/Scene";
|
||||
import {SceneService} from "../../api/scene/scene.service";
|
||||
|
||||
export class OnSet {
|
||||
|
||||
constructor(
|
||||
readonly key: string,
|
||||
readonly value: any,
|
||||
) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-entry-value',
|
||||
templateUrl: './entry-value.component.html',
|
||||
styleUrls: ['./entry-value.component.less']
|
||||
})
|
||||
export class EntryValueComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
entry!: ScheduleEntry;
|
||||
|
||||
@Input()
|
||||
allowChange: boolean = false;
|
||||
|
||||
@Output()
|
||||
onSet: EventEmitter<OnSet> = new EventEmitter<OnSet>();
|
||||
|
||||
scenes: Scene[] = [];
|
||||
|
||||
constructor(
|
||||
readonly sceneService: SceneService,) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.sceneService.findAll(scenes => this.scenes = scenes);
|
||||
}
|
||||
|
||||
set(key: string, value: any): void {
|
||||
if (!this.allowChange) {
|
||||
return;
|
||||
}
|
||||
this.onSet.emit(new OnSet(key, value));
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,10 +4,12 @@
|
||||
|
||||
import {getBaseUrl} from "./UrlHelper";
|
||||
|
||||
const PROD: boolean = false;
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
restBase: getBaseUrl('http', 8080),
|
||||
websocketBase: getBaseUrl('ws', 8080),
|
||||
restBase: PROD ? 'http://10.0.0.50:8082' : getBaseUrl('http', 8080),
|
||||
websocketBase: PROD ? 'ws://10.0.0.50:8082' : getBaseUrl('ws', 8080),
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
package de.ph87.homeautomation.schedule;
|
||||
|
||||
import com.luckycatlabs.sunrisesunset.Zenith;
|
||||
import com.luckycatlabs.sunrisesunset.calculator.SolarEventCalculator;
|
||||
import com.luckycatlabs.sunrisesunset.dto.Location;
|
||||
import de.ph87.homeautomation.Config;
|
||||
import de.ph87.homeautomation.schedule.astro.AstroCalculator;
|
||||
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
|
||||
import de.ph87.homeautomation.schedule.entry.ScheduleEntryType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -14,11 +11,8 @@ import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@ -27,14 +21,14 @@ import java.util.Optional;
|
||||
@RequiredArgsConstructor
|
||||
public class ScheduleCalculator {
|
||||
|
||||
private final Config config;
|
||||
|
||||
private final ScheduleReader scheduleReader;
|
||||
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private final ScheduleMapper scheduleMapper;
|
||||
|
||||
private final AstroCalculator astroCalculator;
|
||||
|
||||
@EventListener(ApplicationStartedEvent.class)
|
||||
public void calculateAllNext() {
|
||||
final ZonedDateTime now = ZonedDateTime.now();
|
||||
@ -82,7 +76,8 @@ public class ScheduleCalculator {
|
||||
return midnight.withHour(entry.getHour()).withMinute(entry.getMinute()).withSecond(entry.getSecond());
|
||||
case SUNRISE:
|
||||
case SUNSET:
|
||||
return astroNext(entry, midnight);
|
||||
final boolean sunrise = entry.getType() == ScheduleEntryType.SUNRISE;
|
||||
return astroCalculator.forDay(midnight, sunrise, entry.getZenith());
|
||||
default:
|
||||
log.error("AstroEvent not implemented: {}", entry.getType());
|
||||
break;
|
||||
@ -90,49 +85,20 @@ public class ScheduleCalculator {
|
||||
return null;
|
||||
}
|
||||
|
||||
private ZonedDateTime astroNext(final ScheduleEntry entry, ZonedDateTime midnight) {
|
||||
final Location location = new Location(config.getLatitude(), config.getLongitude());
|
||||
final SolarEventCalculator calculator = new SolarEventCalculator(location, config.getTimezone());
|
||||
final Calendar calendar = GregorianCalendar.from(midnight);
|
||||
final Calendar nextCalendar = astroNext(calculator, entry.getType(), new Zenith(entry.getZenith()), calendar);
|
||||
if (nextCalendar == null) {
|
||||
return null;
|
||||
}
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(nextCalendar.getTimeInMillis()), midnight.getZone());
|
||||
}
|
||||
|
||||
private Calendar astroNext(final SolarEventCalculator calculator, final ScheduleEntryType type, final Zenith solarZenith, final Calendar calendar) {
|
||||
switch (type) {
|
||||
case SUNRISE:
|
||||
return calculator.computeSunriseCalendar(solarZenith, calendar);
|
||||
case SUNSET:
|
||||
return calculator.computeSunsetCalendar(solarZenith, calendar);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isAnyWeekdayEnabled(final ScheduleEntry entry) {
|
||||
return entry.isMonday() || entry.isTuesday() || entry.isWednesday() || entry.isThursday() || entry.isFriday() || entry.isSaturday() || entry.isSunday();
|
||||
}
|
||||
|
||||
private boolean isWeekdayEnabled(final ScheduleEntry entry, final ZonedDateTime value) {
|
||||
switch (value.getDayOfWeek()) {
|
||||
case MONDAY:
|
||||
return entry.isMonday();
|
||||
case TUESDAY:
|
||||
return entry.isTuesday();
|
||||
case WEDNESDAY:
|
||||
return entry.isWednesday();
|
||||
case THURSDAY:
|
||||
return entry.isThursday();
|
||||
case FRIDAY:
|
||||
return entry.isFriday();
|
||||
case SATURDAY:
|
||||
return entry.isSaturday();
|
||||
case SUNDAY:
|
||||
return entry.isSunday();
|
||||
}
|
||||
return false;
|
||||
return switch (value.getDayOfWeek()) {
|
||||
case MONDAY -> entry.isMonday();
|
||||
case TUESDAY -> entry.isTuesday();
|
||||
case WEDNESDAY -> entry.isWednesday();
|
||||
case THURSDAY -> entry.isThursday();
|
||||
case FRIDAY -> entry.isFriday();
|
||||
case SATURDAY -> entry.isSaturday();
|
||||
case SUNDAY -> entry.isSunday();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.ph87.homeautomation.schedule;
|
||||
|
||||
import de.ph87.homeautomation.property.PropertyReader;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -15,8 +14,6 @@ public class ScheduleController {
|
||||
|
||||
private final ScheduleWriter scheduleWriter;
|
||||
|
||||
private final PropertyReader propertyReader;
|
||||
|
||||
@GetMapping("findAll")
|
||||
public List<ScheduleDto> findAll() {
|
||||
return scheduleReader.findAllDtos();
|
||||
|
||||
@ -1,27 +1,32 @@
|
||||
package de.ph87.homeautomation.schedule;
|
||||
|
||||
import de.ph87.homeautomation.schedule.astro.AstroDto;
|
||||
import de.ph87.homeautomation.schedule.entry.ScheduleEntryDto;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
public class ScheduleDto implements Serializable {
|
||||
|
||||
public final long id;
|
||||
private final long id;
|
||||
|
||||
public final boolean enabled;
|
||||
private final boolean enabled;
|
||||
|
||||
public final String title;
|
||||
private final String title;
|
||||
|
||||
public final Set<ScheduleEntryDto> entries;
|
||||
private final Set<ScheduleEntryDto> entries;
|
||||
|
||||
public ScheduleDto(final Schedule schedule, final Set<ScheduleEntryDto> entries) {
|
||||
private final List<AstroDto> astros;
|
||||
|
||||
public ScheduleDto(final Schedule schedule, final Set<ScheduleEntryDto> entries, final List<AstroDto> astros) {
|
||||
this.id = schedule.getId();
|
||||
this.enabled = schedule.isEnabled();
|
||||
this.title = schedule.getTitle();
|
||||
this.entries = entries;
|
||||
this.astros = astros;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ public class ScheduleExecutor {
|
||||
bulkExecutor.execute(entry.getBulk());
|
||||
}
|
||||
entry.setLastClearTimestamp(entry.getNextClearTimestamp());
|
||||
entry.setLastFuzzyTimestamp(entry.getNextFuzzyTimestamp());
|
||||
scheduleWriter.notifyChanged(schedule);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package de.ph87.homeautomation.schedule;
|
||||
|
||||
import de.ph87.homeautomation.schedule.astro.AstroDto;
|
||||
import de.ph87.homeautomation.schedule.astro.AstroService;
|
||||
import de.ph87.homeautomation.schedule.entry.ScheduleEntryDto;
|
||||
import de.ph87.homeautomation.schedule.entry.ScheduleEntryMapper;
|
||||
import de.ph87.homeautomation.web.WebSocketService;
|
||||
@ -8,6 +10,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -21,15 +24,17 @@ public class ScheduleMapper {
|
||||
|
||||
private final WebSocketService webSocketService;
|
||||
|
||||
private final AstroService astroService;
|
||||
|
||||
public ScheduleDto toDto(final Schedule schedule) {
|
||||
final Set<ScheduleEntryDto> entries = schedule.getEntries().stream().map(scheduleEntryMapper::toDto).collect(Collectors.toSet());
|
||||
return new ScheduleDto(schedule, entries);
|
||||
final List<AstroDto> astros = astroService.findAllNext();
|
||||
return new ScheduleDto(schedule, entries, astros);
|
||||
}
|
||||
|
||||
public ScheduleDto publish(final Schedule schedule, final boolean existing) {
|
||||
public void publish(final Schedule schedule, final boolean existing) {
|
||||
final ScheduleDto dto = toDto(schedule);
|
||||
webSocketService.send(ScheduleDto.class, dto, existing);
|
||||
return dto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
package de.ph87.homeautomation.schedule.astro;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class Astro {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private long id;
|
||||
|
||||
@Version
|
||||
private long version;
|
||||
|
||||
@Setter
|
||||
private boolean enabled = true;
|
||||
|
||||
@Setter
|
||||
private String error;
|
||||
|
||||
@Column(unique = true)
|
||||
private double zenith;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
private String differentNameForSunset;
|
||||
|
||||
public Astro(final double zenith, @NonNull final String name, @Nullable final String differentNameForSunset) {
|
||||
this.zenith = zenith;
|
||||
this.name = name;
|
||||
this.differentNameForSunset = differentNameForSunset;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package de.ph87.homeautomation.schedule.astro;
|
||||
|
||||
import com.luckycatlabs.sunrisesunset.Zenith;
|
||||
import com.luckycatlabs.sunrisesunset.calculator.SolarEventCalculator;
|
||||
import com.luckycatlabs.sunrisesunset.dto.Location;
|
||||
import de.ph87.homeautomation.Config;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AstroCalculator {
|
||||
|
||||
private final Config config;
|
||||
|
||||
public ZonedDateTime next(final ZonedDateTime now, final boolean sunrise, final double zenith) {
|
||||
ZonedDateTime day = now.truncatedTo(ChronoUnit.DAYS);
|
||||
ZonedDateTime next;
|
||||
do {
|
||||
next = forDay(day, sunrise, zenith);
|
||||
day = day.plusDays(1);
|
||||
} while (next != null && !next.isAfter(now));
|
||||
return next;
|
||||
}
|
||||
|
||||
public ZonedDateTime forDay(final ZonedDateTime midnight, final boolean sunrise, final double zenith) {
|
||||
final Location location = new Location(config.getLatitude(), config.getLongitude());
|
||||
final SolarEventCalculator calculator = new SolarEventCalculator(location, config.getTimezone());
|
||||
final Calendar calendar = GregorianCalendar.from(midnight);
|
||||
final Calendar nextCalendar = forDay(calculator, sunrise, new Zenith(zenith), calendar);
|
||||
if (nextCalendar == null) {
|
||||
return null;
|
||||
}
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(nextCalendar.getTimeInMillis()), midnight.getZone());
|
||||
}
|
||||
|
||||
private Calendar forDay(final SolarEventCalculator calculator, final boolean sunrise, final Zenith solarZenith, final Calendar calendar) {
|
||||
if (sunrise) {
|
||||
return calculator.computeSunriseCalendar(solarZenith, calendar);
|
||||
} else {
|
||||
return calculator.computeSunsetCalendar(solarZenith, calendar);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package de.ph87.homeautomation.schedule.astro;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class AstroDto {
|
||||
|
||||
@NonNull
|
||||
private final double zenith;
|
||||
|
||||
@NonNull
|
||||
private final ZonedDateTime sunrise;
|
||||
|
||||
@NonNull
|
||||
private final ZonedDateTime sunset;
|
||||
|
||||
@NonNull
|
||||
private final String sunriseName;
|
||||
|
||||
@Nullable
|
||||
private final String sunsetName;
|
||||
|
||||
public AstroDto(@NonNull final Astro astro, @NonNull final ZonedDateTime sunrise, @NonNull final ZonedDateTime sunset) {
|
||||
this.zenith = astro.getZenith();
|
||||
this.sunrise = sunrise;
|
||||
this.sunset = sunset;
|
||||
this.sunriseName = astro.getName();
|
||||
this.sunsetName = astro.getDifferentNameForSunset() == null ? astro.getName() : astro.getDifferentNameForSunset();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package de.ph87.homeautomation.schedule.astro;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AstroRepository extends JpaRepository<Astro, Long> {
|
||||
|
||||
List<Astro> findAllByEnabledTrue();
|
||||
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package de.ph87.homeautomation.schedule.astro;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@Transactional
|
||||
@RequestMapping("Astro")
|
||||
@RequiredArgsConstructor
|
||||
public class AstroService {
|
||||
|
||||
private final AstroRepository astroRepository;
|
||||
|
||||
private final AstroCalculator astroCalculator;
|
||||
|
||||
@EventListener(ApplicationStartedEvent.class)
|
||||
public void startup() {
|
||||
if (astroRepository.count() == 0) {
|
||||
astroRepository.save(new Astro(71.0000, "Aufgang +++++", "Untergang +++++"));
|
||||
astroRepository.save(new Astro(75.0000, "Aufgang ++++", "Untergang ++++"));
|
||||
astroRepository.save(new Astro(80.0000, "Aufgang +++", "Untergang +++"));
|
||||
astroRepository.save(new Astro(85.0000, "Aufgang ++", "Untergang ++"));
|
||||
astroRepository.save(new Astro(90.0000, "Aufgang +", "Untergang +"));
|
||||
astroRepository.save(new Astro(90.8333, "Aufgang", "Untergang"));
|
||||
astroRepository.save(new Astro(92.0000, "Aufgang -", null));
|
||||
astroRepository.save(new Astro(93.0000, "Aufgang --", null));
|
||||
astroRepository.save(new Astro(94.0000, "Bürgerlich ++", null));
|
||||
astroRepository.save(new Astro(95.0000, "Bürgerlich +", null));
|
||||
astroRepository.save(new Astro(96.0000, "Bürgerlich", null));
|
||||
astroRepository.save(new Astro(97.0000, "Bürgerlich -", null));
|
||||
astroRepository.save(new Astro(98.0000, "Bürgerlich --", null));
|
||||
astroRepository.save(new Astro(99.0000, "Bürgerlich ---", null));
|
||||
astroRepository.save(new Astro(100.000, "Nautisch ++", null));
|
||||
astroRepository.save(new Astro(101.000, "Nautisch +", null));
|
||||
astroRepository.save(new Astro(102.000, "Nautisch", null));
|
||||
astroRepository.save(new Astro(103.000, "Nautisch -", null));
|
||||
astroRepository.save(new Astro(104.000, "Nautisch --", null));
|
||||
astroRepository.save(new Astro(105.000, "Nautisch ---", null));
|
||||
astroRepository.save(new Astro(106.000, "Astronomisch ++", null));
|
||||
astroRepository.save(new Astro(107.000, "Astronomisch +", null));
|
||||
astroRepository.save(new Astro(108.000, "Astronomisch", null));
|
||||
astroRepository.save(new Astro(110.000, "Astronomisch -", null));
|
||||
astroRepository.save(new Astro(120.000, "Astronomisch --", null));
|
||||
}
|
||||
}
|
||||
|
||||
public List<AstroDto> findAllNext() {
|
||||
final ZonedDateTime now = ZonedDateTime.now();
|
||||
return astroRepository.findAllByEnabledTrue().stream().map(astro -> next(now, astro)).filter(Objects::nonNull).toList();
|
||||
}
|
||||
|
||||
private AstroDto next(final ZonedDateTime now, final Astro astro) {
|
||||
final ZonedDateTime sunrise = astroCalculator.next(now, true, astro.getZenith());
|
||||
final ZonedDateTime sunset = astroCalculator.next(now, false, astro.getZenith());
|
||||
if (sunrise == null || sunset == null) {
|
||||
astro.setEnabled(false);
|
||||
astro.setError("sunrise (%s) or sunset (%s) NULL for %s".formatted(sunrise, sunset, now));
|
||||
return null;
|
||||
}
|
||||
return new AstroDto(astro, sunrise, sunset);
|
||||
}
|
||||
|
||||
}
|
||||
@ -74,11 +74,13 @@ public class ScheduleEntry {
|
||||
|
||||
private ZonedDateTime nextClearTimestamp;
|
||||
|
||||
private ZonedDateTime lastClearTimestamp;
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
private ZonedDateTime nextFuzzyTimestamp;
|
||||
|
||||
private ZonedDateTime lastClearTimestamp;
|
||||
|
||||
private ZonedDateTime lastFuzzyTimestamp;
|
||||
|
||||
public ScheduleEntry(final Schedule schedule) {
|
||||
this.schedule = schedule;
|
||||
this.position = schedule.getEntries().size();
|
||||
|
||||
@ -48,6 +48,8 @@ public class ScheduleEntryDto implements Serializable {
|
||||
|
||||
public final ZonedDateTime nextFuzzyTimestamp;
|
||||
|
||||
public final ZonedDateTime lastFuzzyTimestamp;
|
||||
|
||||
public final PropertyDto property;
|
||||
|
||||
public final double value;
|
||||
@ -74,6 +76,7 @@ public class ScheduleEntryDto implements Serializable {
|
||||
this.nextClearTimestamp = entry.getNextClearTimestamp();
|
||||
this.lastClearTimestamp = entry.getLastClearTimestamp();
|
||||
this.nextFuzzyTimestamp = entry.getNextFuzzyTimestamp();
|
||||
this.lastFuzzyTimestamp = entry.getLastFuzzyTimestamp();
|
||||
this.property = property;
|
||||
this.value = entry.getValue();
|
||||
this.bulk = bulk;
|
||||
|
||||
@ -4,4 +4,5 @@ cd "$(dirname "$0")" || exit 1
|
||||
|
||||
scp target/Homeautomation-1.0-SNAPSHOT.jar media@10.0.0.50:/home/media/java/Homeautomation/Homeautomation.jar.update &&
|
||||
git tag "$(date +'deploy---%Y-%m-%d---%H-%M-%S')" &&
|
||||
curl -m 2 -s http://10.0.0.50:8082/server/shutdown
|
||||
curl -m 2 -s http://10.0.0.50:8082/server/shutdown &&
|
||||
exit 0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user