locationPower update REFACTOR

This commit is contained in:
Patrick Haßel 2025-11-21 11:54:06 +01:00
parent 9213c20116
commit f7c30d71d2
13 changed files with 185 additions and 187 deletions

View File

@ -20,16 +20,12 @@
</div>
</div>
@if (ws.connected) {
<div class="MainMenuDrawer NoUserSelect" [hidden]="!showDrawer">
@for (location of locationList; track location.id) {
@if (location.id !== locationService.id) {
<div class="MainMenuItem" (click)="navigate(`Location/${location.id}`); showDrawer = false" routerLinkActive="MainMenuItemActive">{{ location.name }}</div>
}
<div class="MainMenuDrawer NoUserSelect" [hidden]="!showDrawer">
@for (location of locationList; track location.id) {
@if (location.id !== menuService.locationId) {
<div class="MainMenuItem" (click)="navigate(`Location/${location.id}`); showDrawer = false" routerLinkActive="MainMenuItemActive">{{ location.name }}</div>
}
</div>
}
}
</div>
<router-outlet/>

View File

@ -1,4 +1,4 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, OnInit} from '@angular/core';
import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
import {FaIconComponent} from '@fortawesome/angular-fontawesome';
import {faBars, faGears} from '@fortawesome/free-solid-svg-icons';
@ -14,7 +14,7 @@ import {faHome} from '@fortawesome/free-regular-svg-icons';
templateUrl: './app.html',
styleUrl: './app.less'
})
export class App implements OnInit, OnDestroy {
export class App implements OnInit {
protected readonly faHome = faHome;
@ -39,11 +39,6 @@ export class App implements OnInit, OnDestroy {
ngOnInit(): void {
this.locationService.findAll(list => this.locationList = list);
this.menuService.title = "Orte";
}
ngOnDestroy(): void {
this.menuService.title = "";
}
navigate(url: string): void {

View File

@ -1,66 +1,26 @@
import {or, validateNumber, validateString} from '../common';
import {Series} from '../series/Series';
import {Value} from '../series/Value';
export class Location {
powerSelf: Value = Value.NULL;
powerPurchasePercentConsume: Value = Value.NULL;
powerProducePercentConsume: Value = Value.NULL;
powerDeliveryPercentConsume: Value = Value.NULL;
powerDeliveryPercentProduce: Value = Value.NULL;
powerSelfPercentConsume: Value = Value.NULL;
powerSelfPercentProduce: Value = Value.NULL;
constructor(
readonly id: number,
readonly name: string,
readonly latitude: number,
readonly longitude: number,
private _energyPurchase: Series | null,
private _energyDeliver: Series | null,
private _energyProduce: Series | null,
private _powerPurchase: Series | null,
private _powerDeliver: Series | null,
private _powerProduce: Series | null,
private _outsideTemperature: Series | null,
private _outsideHumidityRelative: Series | null,
private _outsideHumidityAbsolute: Series | null,
private _powerConsume: Value = Value.NULL,
readonly energyPurchase: Series | null,
readonly energyDeliver: Series | null,
readonly energyProduce: Series | null,
readonly powerPurchase: Series | null,
readonly powerDeliver: Series | null,
readonly powerProduce: Series | null,
readonly outsideTemperature: Series | null,
readonly outsideHumidityRelative: Series | null,
readonly outsideHumidityAbsolute: Series | null,
) {
this.updateConsume();
//
}
readonly updateSeries = (series: Series) => {
if (series.equals(this._energyPurchase)) {
this._energyPurchase = series;
}
if (series.equals(this._energyDeliver)) {
this._energyDeliver = series;
}
if (series.equals(this._energyProduce)) {
this._energyProduce = series;
}
if (series.equals(this._powerProduce)) {
this._powerProduce = series;
this.updateConsume();
}
if (series.equals(this._powerPurchase)) {
this._powerPurchase = series;
this.updateConsume();
}
if (series.equals(this._powerDeliver)) {
this._powerDeliver = series;
this.updateConsume();
}
};
static fromJson(json: any): Location {
return new Location(
validateNumber(json.id),
@ -79,55 +39,18 @@ export class Location {
);
}
private updateConsume() {
this._powerConsume = Value.ZERO.plus(this._powerPurchase?.value, true).plus(this._powerProduce?.value, true).minus(this._powerDeliver?.value, true);
this.powerSelf = Value.ZERO.plus(this.powerProduce?.value.minus(this.powerDeliver?.value, true), true);
this.powerPurchasePercentConsume = Value.ZERO.plus(this.powerPurchase?.value.percent(this.powerConsume, "%", 0), true);
this.powerProducePercentConsume = Value.ZERO.plus(this.powerProduce?.value.percent(this.powerConsume, "%", 0), true);
this.powerDeliveryPercentConsume = Value.ZERO.plus(this.powerDeliver?.value.percent(this.powerConsume, "%", 0), true);
this.powerDeliveryPercentProduce = Value.ZERO.plus(this.powerDeliver?.value.percent(this.powerProduce?.value, "%", 0), true);
this.powerSelfPercentConsume = Value.ZERO.plus(this.powerSelf.percent(this.powerConsume, "%", 0), true);
this.powerSelfPercentProduce = Value.ZERO.plus(this.powerSelf.percent(this.powerProduce?.value, "%", 0), true);
}
get energyPurchase(): Series | null {
return this._energyPurchase;
}
get energyDeliver(): Series | null {
return this._energyDeliver;
}
get energyProduce(): Series | null {
return this._energyProduce;
}
get powerPurchase(): Series | null {
return this._powerPurchase;
}
get powerDeliver(): Series | null {
return this._powerDeliver;
}
get powerProduce(): Series | null {
return this._powerProduce;
}
get powerConsume(): Value | null {
return this._powerConsume;
}
get outsideTemperature(): Series | null {
return this._outsideTemperature;
}
get outsideHumidityRelative(): Series | null {
return this._outsideHumidityRelative;
}
get outsideHumidityAbsolute(): Series | null {
return this._outsideHumidityAbsolute;
getSeries(): Series[] {
return [
this.energyPurchase,
this.energyDeliver,
this.energyProduce,
this.powerPurchase,
this.powerDeliver,
this.powerProduce,
this.outsideTemperature,
this.outsideHumidityRelative,
this.outsideHumidityAbsolute,
].filter(s => s) as Series[];
}
}

View File

@ -56,6 +56,8 @@ export class LocationDetail implements OnInit, OnDestroy {
protected readonly Math = Math;
private locationId: number | null = null;
protected location: Location | null = null;
private readonly subs: Subscription [] = [];
@ -85,19 +87,22 @@ export class LocationDetail implements OnInit, OnDestroy {
this.router.navigate(["Location/" + this.configService.locationId]);
return;
}
setTimeout(() => this.locationService.id = id, 0);
this.locationId = id;
if (this.locationId) {
this.locationService.getById(this.locationId, this.onLocationChange);
}
}));
this.subs.push(this.locationService.location$.subscribe(this.onLocationChange));
this.subs.push(this.locationService.subscribe(this.onLocationChange));
}
private readonly onLocationChange = (location: Location | null): void => {
this.location = location;
this.menuService.title = this.location?.name || "";
if (this.locationId === location?.id) {
this.location = location;
this.menuService.setLocation(location);
}
};
ngOnDestroy(): void {
this.location = null;
this.menuService.title = "";
this.subs.forEach(sub => sub.unsubscribe());
this.subs.length = 0;
}

View File

@ -1,4 +1,4 @@
import {Component, Inject, Input, LOCALE_ID, OnChanges, SimpleChanges, ViewChild} from '@angular/core';
import {Component, Inject, Input, LOCALE_ID, OnChanges, ViewChild} from '@angular/core';
import {Interval} from '../../../series/Interval';
import {PointService} from '../../../point/point-service';
import {Location} from '../../Location';
@ -111,6 +111,8 @@ export class EnergyCharts implements OnChanges {
},
};
private refreshTimeout: number | undefined;
constructor(
readonly pointService: PointService,
@Inject(LOCALE_ID) readonly locale: string,
@ -118,8 +120,10 @@ export class EnergyCharts implements OnChanges {
//
}
ngOnChanges(changes: SimpleChanges): void {
console.log("ngOnChanges", changes);
ngOnChanges(): void {
clearTimeout(this.refreshTimeout);
this.refreshTimeout = setTimeout(() => this.ngOnChanges(), 60 * 1000);
const series = [
this.location.energyPurchase,
this.location.energyDeliver,

View File

@ -93,6 +93,7 @@ export class LocationEnergy implements OnInit, OnChanges, OnDestroy {
ngOnDestroy(): void {
this.subs.forEach(sub => sub.unsubscribe());
this.subs.length = 0;
}
protected readonly update = (fresh: Series): void => {
@ -109,12 +110,12 @@ export class LocationEnergy implements OnInit, OnChanges, OnDestroy {
next(value);
this.consume = this.purchase.plus(this.produce, true).minus(this.deliver, true);
this.self = this.produce.minus(this.deliver, true);
this.purchasePercentConsume = this.purchase.percent(this.consume, "%", 0);
this.producePercentConsume = this.produce.percent(this.consume, "%", 0);
this.deliveryPercentConsume = this.deliver.percent(this.consume, "%", 0);
this.deliveryPercentProduce = this.deliver.percent(this.produce, "%", 0);
this.selfPercentConsume = this.self.percent(this.consume, "%", 0);
this.selfPercentProduce = this.self.percent(this.produce, "%", 0);
this.purchasePercentConsume = this.purchase.percent(this.consume);
this.producePercentConsume = this.produce.percent(this.consume);
this.deliveryPercentConsume = this.deliver.percent(this.consume);
this.deliveryPercentProduce = this.deliver.percent(this.produce);
this.selfPercentConsume = this.self.percent(this.consume);
this.selfPercentProduce = this.self.percent(this.produce);
};
if (fresh !== null && fresh !== undefined) {
if (fresh.id !== series?.id) {

View File

@ -1,50 +1,17 @@
import {Injectable} from '@angular/core';
import {ApiService, CrudService, Next, WebsocketService} from '../common';
import {Location} from './Location'
import {SeriesService} from '../series/series-service';
import {BehaviorSubject, Observable} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LocationService extends CrudService<Location> {
private readonly _location = new BehaviorSubject<Location | null>(null);
private _id: number | null = null;
constructor(
api: ApiService,
ws: WebsocketService,
readonly seriesService: SeriesService,
) {
super(api, ws, ['Location'], Location.fromJson);
this.seriesService.subscribe(series => this._location.value?.updateSeries(series));
this.ws.onConnect(this.fetch);
this.ws.onDisconnect(() => this._location.next(null));
}
set id(id: number | null) {
if (this._id !== id) {
this._id = id;
this.fetch();
}
}
get id(): number | null {
return this._id;
}
private readonly fetch = () => {
if (this._id === null) {
this._location.next(null);
} else {
this.getById(this._id, location => this._location.next(location));
}
};
get location$(): Observable<Location | null> {
return this._location.asObservable();
}
findAll(next: Next<Location[]>) {

View File

@ -11,11 +11,11 @@
Bezug
</div>
<div class="SectionBody COLOR_FONT_PURCHASE">
{{ location.powerPurchase?.value?.toValueString(dateService.now) }}
{{ powerPurchase.toValueString(dateService.now) }}
</div>
@if (configService.energyPercent) {
<div class="SectionBody COLOR_FONT_PURCHASE percent">
{{ location.powerPurchasePercentConsume.toValueString(dateService.now) }}
{{ powerPurchasePercentConsume.toValueString(dateService.now) }}
<sub class="subscript">Verbrauch</sub>
</div>
}
@ -26,11 +26,11 @@
Solar
</div>
<div class="SectionBody COLOR_FONT_PRODUCE">
{{ location.powerProduce?.value?.toValueString(dateService.now) }}
{{ powerProduce.toValueString(dateService.now) }}
</div>
@if (configService.energyPercent) {
<div class="SectionBody COLOR_FONT_PRODUCE percent">
{{ location.powerProducePercentConsume.toValueString(dateService.now) }}
{{ powerProducePercentConsume.toValueString(dateService.now) }}
<sub class="subscript">Verbrauch</sub>
</div>
}
@ -41,15 +41,15 @@
Selbst
</div>
<div class="SectionBody COLOR_FONT_SELF">
{{ location.powerSelf.toValueString(dateService.now) }}
{{ powerSelf.toValueString(dateService.now) }}
</div>
@if (configService.energyPercent) {
<div class="SectionBody COLOR_FONT_SELF percent">
{{ location.powerSelfPercentConsume.toValueString(dateService.now) }}
{{ powerSelfPercentConsume.toValueString(dateService.now) }}
<sub class="subscript">Verbrauch</sub>
</div>
<div class="SectionBody COLOR_FONT_SELF percent">
{{ location.powerSelfPercentProduce.toValueString(dateService.now) }}
{{ powerSelfPercentProduce.toValueString(dateService.now) }}
<sub class="subscript">Produktion</sub>
</div>
}
@ -60,7 +60,7 @@
Verbrauch
</div>
<div class="SectionBody COLOR_FONT_CONSUME">
{{ location.powerConsume?.toValueString(dateService.now) }}
{{ powerConsume.toValueString(dateService.now) }}
</div>
</div>
@ -69,15 +69,15 @@
Einspeisung
</div>
<div class="SectionBody COLOR_FONT_DELIVER">
{{ location.powerDeliver?.value?.toValueString(dateService.now) }}
{{ powerDeliver.toValueString(dateService.now) }}
</div>
@if (configService.energyPercent) {
<div class="SectionBody COLOR_FONT_DELIVER percent">
{{ location.powerDeliveryPercentConsume.toValueString(dateService.now) }}
{{ powerDeliverPercentConsume.toValueString(dateService.now) }}
<sub class="subscript">Verbrauch</sub>
</div>
<div class="SectionBody COLOR_FONT_DELIVER percent">
{{ location.powerDeliveryPercentProduce.toValueString(dateService.now) }}
{{ powerDeliverPercentProduce.toValueString(dateService.now) }}
<sub class="subscript">Produktion</sub>
</div>
}

View File

@ -1,7 +1,11 @@
import {Component, Input} from '@angular/core';
import {Component, Input, OnChanges, OnDestroy, OnInit} from '@angular/core';
import {Location} from '../Location';
import {DateService} from '../../date.service';
import {ConfigService} from '../../config.service';
import {Value} from '../../series/Value';
import {SeriesService} from '../../series/series-service';
import {Subscription} from 'rxjs';
import {Series} from '../../series/Series';
@Component({
selector: 'app-location-power',
@ -9,16 +13,86 @@ import {ConfigService} from '../../config.service';
templateUrl: './location-power.html',
styleUrl: './location-power.less',
})
export class LocationPower {
export class LocationPower implements OnInit, OnChanges, OnDestroy {
@Input()
location!: Location;
private readonly subs: Subscription[] = [];
protected energyPurchase: Value = Value.NULL;
protected energyDeliver: Value = Value.NULL;
protected energyProduce: Value = Value.NULL;
protected powerPurchase: Value = Value.NULL;
protected powerDeliver: Value = Value.NULL;
protected powerProduce: Value = Value.NULL;
protected outsideTemperature: Value = Value.NULL;
protected outsideHumidityRelative: Value = Value.NULL;
protected outsideHumidityAbsolute: Value = Value.NULL;
protected powerConsume: Value = Value.NULL;
protected powerSelf: Value = Value.NULL;
protected powerPurchasePercentConsume: Value = Value.NULL;
protected powerProducePercentConsume: Value = Value.NULL;
protected powerDeliverPercentConsume: Value = Value.NULL;
protected powerDeliverPercentProduce: Value = Value.NULL;
protected powerSelfPercentConsume: Value = Value.NULL;
protected powerSelfPercentProduce: Value = Value.NULL;
constructor(
readonly dateService: DateService,
readonly configService: ConfigService,
readonly seriesService: SeriesService,
) {
//
}
ngOnInit(): void {
this.subs.push(this.seriesService.subscribe(this.seriesUpdate));
}
ngOnChanges(): void {
this.location.getSeries().forEach(this.seriesUpdate);
}
ngOnDestroy(): void {
this.subs.forEach(sub => sub.unsubscribe());
this.subs.length = 0;
}
private readonly seriesUpdate = (series: Series): void => {
if (series.id === this.location.energyPurchase?.id) this.energyPurchase = series.value;
if (series.id === this.location.energyDeliver?.id) this.energyDeliver = series.value;
if (series.id === this.location.energyProduce?.id) this.energyProduce = series.value;
if (series.id === this.location.powerPurchase?.id) this.powerPurchase = series.value;
if (series.id === this.location.powerDeliver?.id) this.powerDeliver = series.value;
if (series.id === this.location.powerProduce?.id) this.powerProduce = series.value;
if (series.id === this.location.outsideTemperature?.id) this.outsideTemperature = series.value;
if (series.id === this.location.outsideHumidityRelative?.id) this.outsideHumidityRelative = series.value;
if (series.id === this.location.outsideHumidityAbsolute?.id) this.outsideHumidityAbsolute = series.value;
this.powerSelf = Value.ZERO.plus(this.powerProduce).minus(this.powerDeliver);
this.powerConsume = Value.ZERO.plus(this.powerPurchase).plus(this.powerSelf);
this.powerPurchasePercentConsume = this.powerPurchase.percent(this.powerConsume);
this.powerProducePercentConsume = this.powerProduce.percent(this.powerConsume);
this.powerDeliverPercentConsume = this.powerDeliver.percent(this.powerConsume)
this.powerDeliverPercentProduce = this.powerDeliver.percent(this.powerProduce)
this.powerSelfPercentConsume = this.powerSelf.percent(this.powerConsume);
this.powerSelfPercentProduce = this.powerSelf.percent(this.powerProduce);
};
}

View File

@ -1,18 +1,31 @@
import {Injectable} from '@angular/core';
import {Location} from './location/Location';
@Injectable({
providedIn: 'root'
})
export class MenuService {
private _locationId: number | null = null;
private _title: string = "";
get locationId(): number | null {
return this._locationId;
}
get title(): string {
return this._title;
}
set title(value: string) {
setTimeout(() => this._title = value, 0);
setLocation(location: Location): void {
this._locationId = location.id;
this._title = location.name;
}
setNonLocation(title: string) {
this._title = title;
this._locationId = null;
}
}

View File

@ -0,0 +1,22 @@
import {Series} from "./Series";
import {validateList} from "../common";
export class SeriesListResponse {
constructor(
readonly series: Series[]
) {
//
}
static fromJson(json: any): SeriesListResponse {
return new SeriesListResponse(
validateList(json, Series.fromJson),
);
}
findSeries(series: Series | null): Series | null {
return series ? this.series.filter(s => s.id === series.id)[0] || null : null;
}
}

View File

@ -29,7 +29,7 @@ export class Value {
return `--- ${this.unit}`
}
const scale = Math.floor(Math.log10(this.value));
if(isNaN(scale)) {
if (isNaN(scale)) {
return '0';
}
const rest = scale - this.precision + 1;
@ -39,14 +39,14 @@ export class Value {
return formatNumber(this.value, "de-DE", `0.${-rest}-${-rest}`) + ' ' + this.unit;
}
plus(other: Value | null | undefined, nullToZero: boolean): Value {
plus(other: Value | null | undefined, nullToZero: boolean = true): Value {
if (!nullToZero && (other === null || other === undefined)) {
return Value.NULL;
}
return new BiValue(this, other || Value.ZERO, (a, b) => a + b);
}
minus(other: Value | null | undefined, nullToZero: boolean): Value {
minus(other: Value | null | undefined, nullToZero: boolean = true): Value {
if (!nullToZero && (other === null || other === undefined)) {
return Value.NULL;
}
@ -86,11 +86,11 @@ export class Value {
return ageSeconds > this.seconds * 2.1;
}
percent(other: Value | null | undefined, unit: string | null = null, precision: number | null = null): Value {
percent(other: Value | null | undefined): Value {
if (other === null || other === undefined) {
return Value.NULL;
}
return new BiValue(this, other, (a, b) => a / b * 100, unit, precision);
return new BiValue(this, other, (a, b) => a / b * 100, '%', 0);
}
}

View File

@ -1,6 +1,5 @@
import {Component, OnInit} from '@angular/core';
import {LocationSelect} from '../location/select/location-select';
import {LocationService} from '../location/location-service';
import {Location} from '../location/Location'
import {ConfigService} from '../config.service';
import {FormsModule} from '@angular/forms';
@ -22,7 +21,6 @@ export class SettingsComponent implements OnInit {
protected location: Location | null = null;
constructor(
readonly locationService: LocationService,
readonly configService: ConfigService,
readonly menuService: MenuService,
) {
@ -30,7 +28,7 @@ export class SettingsComponent implements OnInit {
}
ngOnInit(): void {
this.menuService.title = "Einstellungen";
this.menuService.setNonLocation("Einstellungen");
}
}