Meter
This commit is contained in:
parent
e88ff035cf
commit
e454ed610e
@ -16,4 +16,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bodyContent">
|
||||
<router-outlet/>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
@sidebarColor: #62b0ca;
|
||||
|
||||
.sidebar {
|
||||
margin-left: -2em;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
@ -49,3 +48,7 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.bodyContent {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
@ -8,13 +8,15 @@ import {NTU, UTN} from '../../COMMON';
|
||||
import {Graph} from '../axis/graph/Graph';
|
||||
import {Axis} from '../axis/Axis';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {Delta, DeltaService} from '../../series/delta/delta-service';
|
||||
import {Bool, BoolService} from '../../series/bool/bool-service';
|
||||
import {DeltaService} from '../../series/delta/delta-service';
|
||||
import {BoolService} from '../../series/bool/bool-service';
|
||||
import {VaryingService} from '../../series/varying/varying-service';
|
||||
import {Interval} from '../../series/Interval';
|
||||
import {SeriesService} from '../../series/series.service';
|
||||
import {GraphType} from '../axis/graph/GraphType';
|
||||
import {Varying} from '../../series/varying/Varying';
|
||||
import {Delta} from '../../series/delta/Delta';
|
||||
import {Bool} from '../../series/bool/Bool';
|
||||
|
||||
type Dataset = ChartDataset<any, any>[][number];
|
||||
|
||||
|
||||
@ -49,7 +49,7 @@ export function toDelta(points: number[][], factor: number): Point[] {
|
||||
for (const p of points) {
|
||||
result.push({
|
||||
x: p[0] * 1000,
|
||||
y: (p[2] - p[1]) * factor,
|
||||
y: (p[1]) * factor,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
||||
26
src/main/angular/src/app/series/bool/Bool.ts
Normal file
26
src/main/angular/src/app/series/bool/Bool.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {Series} from "../Series";
|
||||
import {validateBoolean, validateDate} from "../../COMMON";
|
||||
|
||||
export class Bool {
|
||||
|
||||
constructor(
|
||||
readonly series: Series,
|
||||
readonly date: Date,
|
||||
readonly end: Date,
|
||||
readonly state: boolean,
|
||||
readonly terminated: boolean,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
static fromJson(json: any): Bool {
|
||||
return new Bool(
|
||||
Series.fromJson(json.series),
|
||||
validateDate(json.date),
|
||||
validateDate(json.end),
|
||||
validateBoolean(json.state),
|
||||
validateBoolean(json.terminated),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,30 +1,6 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiService, CrudService, validateBoolean, validateDate} from '../../COMMON';
|
||||
import {Series} from '../Series';
|
||||
|
||||
export class Bool {
|
||||
|
||||
constructor(
|
||||
readonly series: Series,
|
||||
readonly date: Date,
|
||||
readonly end: Date,
|
||||
readonly state: boolean,
|
||||
readonly terminated: boolean,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
static fromJson(json: any): Bool {
|
||||
return new Bool(
|
||||
Series.fromJson(json.series),
|
||||
validateDate(json.date),
|
||||
validateDate(json.end),
|
||||
validateBoolean(json.state),
|
||||
validateBoolean(json.terminated),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
import {ApiService, CrudService} from '../../COMMON';
|
||||
import {Bool} from './Bool';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
||||
24
src/main/angular/src/app/series/delta/Delta.ts
Normal file
24
src/main/angular/src/app/series/delta/Delta.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {validateDate, validateNumber} from "../../COMMON";
|
||||
import {Meter} from './meter/Meter';
|
||||
|
||||
export class Delta {
|
||||
|
||||
constructor(
|
||||
readonly meter: Meter,
|
||||
readonly date: Date,
|
||||
readonly first: number,
|
||||
readonly last: number,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
static fromJson(json: any): Delta {
|
||||
return new Delta(
|
||||
Meter.fromJson(json.meter),
|
||||
validateDate(json.date),
|
||||
validateNumber(json.first),
|
||||
validateNumber(json.last),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,28 +1,6 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiService, CrudService, validateDate, validateNumber} from '../../COMMON';
|
||||
import {Series} from '../Series';
|
||||
|
||||
export class Delta {
|
||||
|
||||
constructor(
|
||||
readonly series: Series,
|
||||
readonly date: Date,
|
||||
readonly first: number,
|
||||
readonly last: number,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
static fromJson(json: any): Delta {
|
||||
return new Delta(
|
||||
Series.fromJson(json.series),
|
||||
validateDate(json.date),
|
||||
validateNumber(json.first),
|
||||
validateNumber(json.last),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
import {ApiService, CrudService} from '../../COMMON';
|
||||
import {Delta} from './Delta';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
||||
24
src/main/angular/src/app/series/delta/meter/Meter.ts
Normal file
24
src/main/angular/src/app/series/delta/meter/Meter.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {Series} from "../../Series";
|
||||
import {validateDate, validateNumber, validateString} from "../../../COMMON";
|
||||
|
||||
export class Meter {
|
||||
|
||||
constructor(
|
||||
readonly id: number,
|
||||
readonly series: Series,
|
||||
readonly number: string,
|
||||
readonly first: Date,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
static fromJson(json: any): Meter {
|
||||
return new Meter(
|
||||
validateNumber(json.id),
|
||||
Series.fromJson(json.series),
|
||||
validateString(json.number),
|
||||
validateDate(json.first),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -17,6 +17,8 @@ export class Topic {
|
||||
readonly timestampType: TimestampType,
|
||||
readonly timestampQuery: string,
|
||||
readonly timestampLast: Date | null,
|
||||
readonly meterNumberQuery: string,
|
||||
readonly meterNumberLast: string,
|
||||
readonly queries: TopicQuery[],
|
||||
readonly payload: string,
|
||||
readonly error: string,
|
||||
@ -35,6 +37,8 @@ export class Topic {
|
||||
validateString(json.timestampType) as TimestampType,
|
||||
validateString(json.timestampQuery),
|
||||
mapNotNull(json.timestampLast, validateDate),
|
||||
validateString(json.meterNumberQuery),
|
||||
validateString(json.meterNumberLast),
|
||||
validateList(json.queries, TopicQuery.fromJson),
|
||||
validateString(json.payload),
|
||||
validateString(json.error),
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
<div class="options">
|
||||
<label><input type="checkbox" [(ngModel)]="config.topicList.unused">Ungenutzte</label>
|
||||
<label><input type="checkbox" [(ngModel)]="config.topicList.used">Genutzte</label>
|
||||
</div>
|
||||
|
||||
<table class="TopicList">
|
||||
@for (topic of sorted(); track topic.id) {
|
||||
<tr class="head">
|
||||
@ -10,6 +15,14 @@
|
||||
<td class="timestampType" [class.empty]="topic.timestampType === null">{{ topic.timestampType }}</td>
|
||||
<td class="timestampLast" [class.empty]="topic.timestampLast === null" colspan="6">{{ topic.timestampLast | date:'long':'':'de-DE' }}</td>
|
||||
</tr>
|
||||
@if (topic.queries[0]?.series?.type === SeriesType.DELTA) {
|
||||
<tr class="meter">
|
||||
<td class="meterNumberQuery" [class.empty]="!topic.meterNumberQuery">{{ topic.meterNumberQuery }}</td>
|
||||
<td class="meterNumberLast" [class.empty]="topic.meterNumberLast === null"></td>
|
||||
<td class="meterNumberLast" [class.empty]="topic.meterNumberLast === null" colspan="5">{{ topic.meterNumberLast }}</td>
|
||||
<td class="meterNumberLast" [class.empty]="topic.timestampLast === null">{{ topic.timestampLast | date:'long':'':'de-DE' }}</td>
|
||||
</tr>
|
||||
}
|
||||
@for (query of topic.queries; track $index) {
|
||||
<tr class="query">
|
||||
<td class="valueQuery" [class.empty]="query.valueQuery === null">{{ query.valueQuery }}</td>
|
||||
|
||||
@ -22,6 +22,10 @@ table.TopicList {
|
||||
background-color: darkseagreen;
|
||||
}
|
||||
|
||||
tr.meter {
|
||||
background-color: #ffe7bd;
|
||||
}
|
||||
|
||||
tr.spacer {
|
||||
th, td {
|
||||
border: none;
|
||||
@ -53,7 +57,11 @@ table.TopicList {
|
||||
}
|
||||
|
||||
.timestampLast {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.meterNumberLast {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.valueQuery {
|
||||
|
||||
@ -11,6 +11,7 @@ import {SeriesService} from '../../series/series.service';
|
||||
import {Config} from '../../config/Config';
|
||||
import {ageString} from '../../COMMON';
|
||||
import {ConfigService} from '../../config/config.service';
|
||||
import {SeriesType} from '../../series/SeriesType';
|
||||
|
||||
@Component({
|
||||
selector: 'app-topic-list',
|
||||
@ -25,6 +26,8 @@ export class TopicListComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected readonly ageString = ageString;
|
||||
|
||||
protected readonly SeriesType = SeriesType;
|
||||
|
||||
protected now: Date = new Date();
|
||||
|
||||
protected topicList: Topic[] = [];
|
||||
@ -55,10 +58,6 @@ export class TopicListComponent implements OnInit, OnDestroy {
|
||||
this.subs.forEach(sub => sub.unsubscribe());
|
||||
}
|
||||
|
||||
sorted() {
|
||||
return this.topicList.filter(this.filter).sort(this.compare);
|
||||
}
|
||||
|
||||
private readonly update = (topic: Topic) => {
|
||||
const index = this.topicList.findIndex(t => t.id === topic.id);
|
||||
if (index >= 0) {
|
||||
@ -68,6 +67,10 @@ export class TopicListComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
};
|
||||
|
||||
sorted() {
|
||||
return this.topicList.filter(this.filter).sort(this.compare);
|
||||
}
|
||||
|
||||
private readonly filter = (topic: Topic) => {
|
||||
return ((!topic.used && this.config.topicList.unused) || (topic.used && this.config.topicList.used));
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ html, body {
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
table {
|
||||
@ -23,7 +22,7 @@ input, select, textarea {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
input, select {
|
||||
input:not([type=checkbox]), select {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
|
||||
@ -62,8 +62,9 @@ public class DemoService {
|
||||
|
||||
final Series electricityEnergyProduce = series("electricity/energy/produce", "kWh", SeriesType.DELTA, 5);
|
||||
final Series electricityPowerProduce = series("electricity/power/produce", "W", SeriesType.VARYING, 5);
|
||||
topic(
|
||||
topicMeterNumber(
|
||||
"openDTU/pv/patrix/json2",
|
||||
"$.inverter",
|
||||
new TopicQuery(electricityEnergyProduce, "$.totalKWh"),
|
||||
new TopicQuery(electricityPowerProduce, "$.totalW")
|
||||
);
|
||||
@ -72,8 +73,9 @@ public class DemoService {
|
||||
final Series electricityPowerPurchase = series("electricity/power/purchase", "W", SeriesType.VARYING, 5);
|
||||
final Series electricityEnergyDelivery = series("electricity/energy/delivery", "kWh", SeriesType.DELTA, 5);
|
||||
final Series electricityPowerDelivery = series("electricity/power/delivery", "W", SeriesType.VARYING, 5);
|
||||
topic(
|
||||
topicMeterNumber(
|
||||
"electricity/grid/json",
|
||||
"\"1ZPA0020300305\"",
|
||||
new TopicQuery(electricityEnergyPurchase, "$.purchaseWh", 0.001),
|
||||
new TopicQuery(electricityPowerPurchase, "$.powerW", TopicQueryFunction.ONLY_POSITIVE),
|
||||
new TopicQuery(electricityEnergyDelivery, "$.deliveryWh", 0.001),
|
||||
@ -220,4 +222,12 @@ public class DemoService {
|
||||
topic.getQueries().addAll(List.of(queries));
|
||||
}
|
||||
|
||||
private void topicMeterNumber(@NonNull final String name, @NonNull final String meterNumberQuery, @NonNull final TopicQuery... queries) {
|
||||
final Topic topic = topicRepository.findByName(name).orElseGet(() -> topicRepository.save(new Topic(name)));
|
||||
topic.setMeterNumberQuery(meterNumberQuery);
|
||||
topic.setTimestampQuery("$.timestamp");
|
||||
topic.getQueries().clear();
|
||||
topic.getQueries().addAll(List.of(queries));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.ph87.data.series.data.delta;
|
||||
|
||||
import de.ph87.data.series.data.DataId;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
@ -19,7 +18,7 @@ public abstract class Delta {
|
||||
|
||||
@Id
|
||||
@NonNull
|
||||
private DataId id;
|
||||
private DeltaId id;
|
||||
|
||||
@Version
|
||||
private long version;
|
||||
@ -31,7 +30,7 @@ public abstract class Delta {
|
||||
@Column(nullable = false)
|
||||
private double last;
|
||||
|
||||
protected Delta(@NonNull final DataId id, final double value) {
|
||||
protected Delta(@NonNull final DeltaId id, final double value) {
|
||||
this.id = id;
|
||||
this.first = value;
|
||||
this.last = value;
|
||||
@ -47,7 +46,7 @@ public abstract class Delta {
|
||||
@Entity(name = "DeltaFive")
|
||||
public static class Five extends Delta {
|
||||
|
||||
public Five(@NonNull final DataId id, @NonNull final double value) {
|
||||
public Five(@NonNull final DeltaId id, @NonNull final double value) {
|
||||
super(id, value);
|
||||
}
|
||||
|
||||
@ -59,7 +58,7 @@ public abstract class Delta {
|
||||
@Entity(name = "DeltaHour")
|
||||
public static class Hour extends Delta {
|
||||
|
||||
public Hour(@NonNull final DataId id, @NonNull final double value) {
|
||||
public Hour(@NonNull final DeltaId id, @NonNull final double value) {
|
||||
super(id, value);
|
||||
}
|
||||
|
||||
@ -71,7 +70,7 @@ public abstract class Delta {
|
||||
@Entity(name = "DeltaDay")
|
||||
public static class Day extends Delta {
|
||||
|
||||
public Day(@NonNull final DataId id, @NonNull final double value) {
|
||||
public Day(@NonNull final DeltaId id, @NonNull final double value) {
|
||||
super(id, value);
|
||||
}
|
||||
|
||||
@ -83,7 +82,7 @@ public abstract class Delta {
|
||||
@Entity(name = "DeltaWeek")
|
||||
public static class Week extends Delta {
|
||||
|
||||
public Week(@NonNull final DataId id, @NonNull final double value) {
|
||||
public Week(@NonNull final DeltaId id, @NonNull final double value) {
|
||||
super(id, value);
|
||||
}
|
||||
|
||||
@ -95,7 +94,7 @@ public abstract class Delta {
|
||||
@Entity(name = "DeltaMonth")
|
||||
public static class Month extends Delta {
|
||||
|
||||
public Month(@NonNull final DataId id, @NonNull final double value) {
|
||||
public Month(@NonNull final DeltaId id, @NonNull final double value) {
|
||||
super(id, value);
|
||||
}
|
||||
|
||||
@ -107,7 +106,7 @@ public abstract class Delta {
|
||||
@Entity(name = "DeltaYear")
|
||||
public static class Year extends Delta {
|
||||
|
||||
public Year(@NonNull final DataId id, @NonNull final double value) {
|
||||
public Year(@NonNull final DeltaId id, @NonNull final double value) {
|
||||
super(id, value);
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package de.ph87.data.series.data.delta;
|
||||
|
||||
import de.ph87.data.series.SeriesDto;
|
||||
import de.ph87.data.series.data.Interval;
|
||||
import de.ph87.data.series.data.delta.meter.MeterDto;
|
||||
import de.ph87.data.websocket.IWebsocketMessage;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
@ -14,21 +14,20 @@ import java.time.ZonedDateTime;
|
||||
public abstract class DeltaDto implements IWebsocketMessage {
|
||||
|
||||
@NonNull
|
||||
private SeriesDto series;
|
||||
private MeterDto meter;
|
||||
|
||||
@NonNull
|
||||
private ZonedDateTime date;
|
||||
|
||||
public final double first;
|
||||
|
||||
@NonNull
|
||||
public final double last;
|
||||
|
||||
@NonNull
|
||||
public final Interval interval;
|
||||
|
||||
protected DeltaDto(@NonNull final Delta delta, @NonNull final Interval interval) {
|
||||
this.series = new SeriesDto(delta.getId().getSeries(), false);
|
||||
this.meter = new MeterDto(delta.getId().getMeter());
|
||||
this.date = delta.getId().getDate();
|
||||
this.first = delta.getFirst();
|
||||
this.last = delta.getLast();
|
||||
@ -38,7 +37,7 @@ public abstract class DeltaDto implements IWebsocketMessage {
|
||||
@NonNull
|
||||
@Override
|
||||
public String getWebsocketTopic() {
|
||||
return "Delta/%d/%s".formatted(series.id, interval);
|
||||
return "Delta/%d/%s".formatted(meter.series.id, interval);
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
||||
36
src/main/java/de/ph87/data/series/data/delta/DeltaId.java
Normal file
36
src/main/java/de/ph87/data/series/data/delta/DeltaId.java
Normal file
@ -0,0 +1,36 @@
|
||||
package de.ph87.data.series.data.delta;
|
||||
|
||||
import de.ph87.data.series.data.Interval;
|
||||
import de.ph87.data.series.data.delta.meter.Meter;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@Embeddable
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
public class DeltaId {
|
||||
|
||||
@NonNull
|
||||
@ManyToOne(optional = false)
|
||||
private Meter meter;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime date;
|
||||
|
||||
public DeltaId(@NonNull final Meter meter, @NonNull final ZonedDateTime date, @NonNull final Interval interval) {
|
||||
this.meter = meter;
|
||||
this.date = interval.align.apply(date);
|
||||
}
|
||||
|
||||
}
|
||||
@ -10,23 +10,20 @@ import java.time.ZonedDateTime;
|
||||
@SuppressWarnings("unused") // used by repository query
|
||||
public class DeltaPoint implements SeriesPoint {
|
||||
|
||||
@NonNull
|
||||
public final ZonedDateTime date;
|
||||
|
||||
public final double first;
|
||||
public final double delta;
|
||||
|
||||
public final double last;
|
||||
|
||||
public DeltaPoint(@NonNull final Delta delta) {
|
||||
this.date = delta.getId().getDate();
|
||||
this.first = delta.getFirst();
|
||||
this.last = delta.getLast();
|
||||
public DeltaPoint(@NonNull final ZonedDateTime date, final double delta) {
|
||||
this.date = date;
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(final JsonGenerator jsonGenerator) throws IOException {
|
||||
jsonGenerator.writeNumber(date.toEpochSecond());
|
||||
jsonGenerator.writeNumber(first);
|
||||
jsonGenerator.writeNumber(last);
|
||||
jsonGenerator.writeNumber(delta);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.data.series.data.delta;
|
||||
|
||||
import de.ph87.data.series.Series;
|
||||
import de.ph87.data.series.data.DataId;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
@ -11,10 +10,10 @@ import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@NoRepositoryBean
|
||||
public interface DeltaRepo<T extends Delta> extends CrudRepository<T, DataId> {
|
||||
public interface DeltaRepo<T extends Delta> extends CrudRepository<T, DeltaId> {
|
||||
|
||||
@NonNull
|
||||
@Query("select new de.ph87.data.series.data.delta.DeltaPoint(e) from #{#entityName} e where e.id.series = :series and e.id.date >= :first and e.id.date < :after")
|
||||
@Query("select new de.ph87.data.series.data.delta.DeltaPoint(e.id.date, sum(e.last - e.first)) from #{#entityName} e where e.id.meter.series = :series and e.id.date >= :first and e.id.date < :after group by e.id.date")
|
||||
List<DeltaPoint> points(@NonNull Series series, @NonNull ZonedDateTime first, @NonNull ZonedDateTime after);
|
||||
|
||||
}
|
||||
|
||||
@ -2,8 +2,9 @@ package de.ph87.data.series.data.delta;
|
||||
|
||||
import de.ph87.data.series.ISeriesPointRequest;
|
||||
import de.ph87.data.series.Series;
|
||||
import de.ph87.data.series.data.DataId;
|
||||
import de.ph87.data.series.data.Interval;
|
||||
import de.ph87.data.series.data.delta.meter.Meter;
|
||||
import de.ph87.data.series.data.delta.meter.MeterService;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -34,18 +35,21 @@ public class DeltaService {
|
||||
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private final MeterService meterService;
|
||||
|
||||
@Transactional
|
||||
public void write(@NonNull final Series series, @NonNull final ZonedDateTime date, final double value) {
|
||||
write(series, five, Interval.FIVE, date, value, Delta.Five::new, DeltaDto.Five::new);
|
||||
write(series, hour, Interval.HOUR, date, value, Delta.Hour::new, DeltaDto.Hour::new);
|
||||
write(series, day, Interval.DAY, date, value, Delta.Day::new, DeltaDto.Day::new);
|
||||
write(series, week, Interval.WEEK, date, value, Delta.Week::new, DeltaDto.Week::new);
|
||||
write(series, month, Interval.MONTH, date, value, Delta.Month::new, DeltaDto.Month::new);
|
||||
write(series, year, Interval.YEAR, date, value, Delta.Year::new, DeltaDto.Year::new);
|
||||
public void write(@NonNull final Series series, @NonNull final String meterNumber, @NonNull final ZonedDateTime date, final double value) {
|
||||
final Meter meter = meterService.getLastValidBySeriesAndNumberOrCreate(series, meterNumber, date);
|
||||
write(meter, five, Interval.FIVE, date, value, Delta.Five::new, DeltaDto.Five::new);
|
||||
write(meter, hour, Interval.HOUR, date, value, Delta.Hour::new, DeltaDto.Hour::new);
|
||||
write(meter, day, Interval.DAY, date, value, Delta.Day::new, DeltaDto.Day::new);
|
||||
write(meter, week, Interval.WEEK, date, value, Delta.Week::new, DeltaDto.Week::new);
|
||||
write(meter, month, Interval.MONTH, date, value, Delta.Month::new, DeltaDto.Month::new);
|
||||
write(meter, year, Interval.YEAR, date, value, Delta.Year::new, DeltaDto.Year::new);
|
||||
}
|
||||
|
||||
private <DELTA extends Delta, DTO extends DeltaDto> void write(@NonNull final Series series, @NonNull final DeltaRepo<DELTA> repo, @NonNull final Interval interval, @NonNull final ZonedDateTime date, final double value, @NonNull final BiFunction<DataId, Double, DELTA> create, @NonNull final BiFunction<DELTA, Interval, DTO> toDto) {
|
||||
final DataId id = new DataId(series, date, interval);
|
||||
private <DELTA extends Delta, DTO extends DeltaDto> void write(@NonNull final Meter meter, @NonNull final DeltaRepo<DELTA> repo, @NonNull final Interval interval, @NonNull final ZonedDateTime date, final double value, @NonNull final BiFunction<DeltaId, Double, DELTA> create, @NonNull final BiFunction<DELTA, Interval, DTO> toDto) {
|
||||
final DeltaId id = new DeltaId(meter, date, interval);
|
||||
final DELTA delta = repo.findById(id).stream().peek(existing -> existing.update(value)).findFirst().orElseGet(() -> repo.save(create.apply(id, value)));
|
||||
log.debug("Delta written: {}", delta);
|
||||
applicationEventPublisher.publishEvent(toDto.apply(delta, interval));
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
package de.ph87.data.series.data.delta.meter;
|
||||
|
||||
import de.ph87.data.series.Series;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class Meter {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private long id;
|
||||
|
||||
@Version
|
||||
private long version;
|
||||
|
||||
@NonNull
|
||||
@ManyToOne(optional = false)
|
||||
private Series series;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private String number;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime first;
|
||||
|
||||
public Meter(@NonNull final Series series, @NonNull final String number, @NonNull final ZonedDateTime date) {
|
||||
this.series = series;
|
||||
this.number = number;
|
||||
this.first = date;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package de.ph87.data.series.data.delta.meter;
|
||||
|
||||
import de.ph87.data.series.SeriesDto;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Data
|
||||
public class MeterDto {
|
||||
|
||||
private final long id;
|
||||
|
||||
@NonNull
|
||||
public final SeriesDto series;
|
||||
|
||||
@NonNull
|
||||
public final String number;
|
||||
|
||||
@NonNull
|
||||
public final ZonedDateTime first;
|
||||
|
||||
public MeterDto(@NonNull final Meter meter) {
|
||||
this.id = meter.getId();
|
||||
this.series = new SeriesDto(meter.getSeries(), false);
|
||||
this.number = meter.getNumber();
|
||||
this.first = meter.getFirst();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package de.ph87.data.series.data.delta.meter;
|
||||
|
||||
import de.ph87.data.series.Series;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.data.repository.ListCrudRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface MeterRepository extends ListCrudRepository<Meter, Long> {
|
||||
|
||||
@NonNull
|
||||
Optional<Meter> findFirstBySeriesOrderByFirstDesc(@NonNull Series series);
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package de.ph87.data.series.data.delta.meter;
|
||||
|
||||
import de.ph87.data.series.Series;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MeterService {
|
||||
|
||||
private final MeterRepository meterRepository;
|
||||
|
||||
@NonNull
|
||||
@Transactional
|
||||
public Meter getLastValidBySeriesAndNumberOrCreate(@NonNull final Series series, @NonNull final String number, @NonNull final ZonedDateTime date) {
|
||||
return meterRepository
|
||||
.findFirstBySeriesOrderByFirstDesc(series)
|
||||
.filter(meter -> meter.getNumber().equals(number))
|
||||
.orElseGet(() -> meterRepository.save(new Meter(series, number, date)));
|
||||
}
|
||||
|
||||
}
|
||||
@ -72,6 +72,16 @@ public class Topic extends AbstractEntityLog {
|
||||
@Column(nullable = false)
|
||||
private String timestampQuery = "";
|
||||
|
||||
@Setter
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private String meterNumberQuery = "";
|
||||
|
||||
@Setter
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private String meterNumberLast = "";
|
||||
|
||||
@NonNull
|
||||
@ToString.Exclude
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
|
||||
@ -36,6 +36,12 @@ public class TopicDto implements IWebsocketMessage {
|
||||
@Nullable
|
||||
public final ZonedDateTime timestampLast;
|
||||
|
||||
@NonNull
|
||||
public final String meterNumberQuery;
|
||||
|
||||
@Nullable
|
||||
public final String meterNumberLast;
|
||||
|
||||
@NonNull
|
||||
public final List<TopicQueryDto> queries;
|
||||
|
||||
@ -55,6 +61,8 @@ public class TopicDto implements IWebsocketMessage {
|
||||
this.timestampType = topic.getTimestampType();
|
||||
this.timestampQuery = topic.getTimestampQuery();
|
||||
this.timestampLast = topic.getTimestampLast();
|
||||
this.meterNumberQuery = topic.getMeterNumberQuery();
|
||||
this.meterNumberLast = topic.getMeterNumberLast();
|
||||
this.queries = topic.getQueries().stream().map(TopicQueryDto::new).toList();
|
||||
this.error = topic.getError();
|
||||
this.payload = topic.getPayload();
|
||||
|
||||
@ -103,6 +103,12 @@ public class TopicReceiver {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (series.getType() == SeriesType.DELTA) {
|
||||
if (topic.getMeterNumberQuery().isEmpty()) {
|
||||
log.debug("TopicQuery meterNumberQuery not set: topic={}", topic);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Object valueRaw = json.read(query.getValueQuery());
|
||||
queryValue(valueRaw).ifPresentOrElse(
|
||||
@ -110,14 +116,17 @@ public class TopicReceiver {
|
||||
final double value = query.getFunction().apply(v) * query.getFactor();
|
||||
series.update(date, value);
|
||||
applicationEventPublisher.publishEvent(new SeriesDto(series, false));
|
||||
|
||||
switch (series.getType()) {
|
||||
case BOOL -> {
|
||||
final ZonedDateTime begin = "timestamp".equals(query.getBeginQuery()) ? date : queryTimestamp(json, query.getBeginQuery(), topic.getTimestampType());
|
||||
final boolean terminated = !"true".equals(query.getTerminatedQuery()) && json.read(query.getTerminatedQuery(), Boolean.class);
|
||||
final ZonedDateTime begin = queryTimestamp(json, query.getBeginQuery(), topic.getTimestampType());
|
||||
final boolean terminated = queryBoolean(json, query.getTerminatedQuery());
|
||||
boolService.write(series, begin, date, value > 0, terminated);
|
||||
}
|
||||
case DELTA -> deltaService.write(series, date, value);
|
||||
case DELTA -> {
|
||||
final String meterNumber = queryMeterNumber(topic, json);
|
||||
topic.setMeterNumberLast(meterNumber);
|
||||
deltaService.write(series, meterNumber, date, value);
|
||||
}
|
||||
case VARYING -> varyingService.write(series, date, value);
|
||||
}
|
||||
},
|
||||
@ -128,6 +137,24 @@ public class TopicReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String queryMeterNumber(@NonNull final Topic topic, @NonNull final DocumentContext json) {
|
||||
if (topic.getMeterNumberQuery().startsWith("\"") && topic.getMeterNumberQuery().endsWith("\"")) {
|
||||
return topic.getMeterNumberQuery().substring(1, topic.getMeterNumberQuery().length() - 1);
|
||||
}
|
||||
return json.read(topic.getMeterNumberQuery(), String.class);
|
||||
}
|
||||
|
||||
private static boolean queryBoolean(@NonNull final DocumentContext json, @NonNull final String terminatedQuery) {
|
||||
if ("true".equals(terminatedQuery)) {
|
||||
return true;
|
||||
}
|
||||
if ("false".equals(terminatedQuery)) {
|
||||
return false;
|
||||
}
|
||||
return json.read(terminatedQuery, Boolean.class);
|
||||
}
|
||||
|
||||
private static Optional<Double> queryValue(final Object valueRaw) {
|
||||
if (valueRaw instanceof final Double n) {
|
||||
return Optional.of(n);
|
||||
@ -148,7 +175,7 @@ public class TopicReceiver {
|
||||
|
||||
@NonNull
|
||||
private static ZonedDateTime queryTimestamp(@NonNull final DocumentContext json, @NonNull final String query, @NonNull final TimestampType type) {
|
||||
if ("now".equals(query)) {
|
||||
if ("now".equals(query) || "timestamp".equals(query)) {
|
||||
return ZonedDateTime.now();
|
||||
}
|
||||
return switch (type) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user