) {
+ this.postSingle([location.id, 'powerProduce'], seriesId, next);
}
}
diff --git a/src/main/angular/src/app/series/select/series-select.html b/src/main/angular/src/app/series/select/series-select.html
index f4985d1..592c64a 100644
--- a/src/main/angular/src/app/series/select/series-select.html
+++ b/src/main/angular/src/app/series/select/series-select.html
@@ -4,24 +4,18 @@
(mouseenter)="showPen = true"
(mouseleave)="showPen = false"
>
-
- @if (editing) {
-
- } @else {
- {{ initial?.name || ' ' }}
+
- @if (editing || showPen) {
+
+ @if (showPen) {
}
diff --git a/src/main/angular/src/app/series/select/series-select.less b/src/main/angular/src/app/series/select/series-select.less
index e69de29..5546c96 100644
--- a/src/main/angular/src/app/series/select/series-select.less
+++ b/src/main/angular/src/app/series/select/series-select.less
@@ -0,0 +1,29 @@
+.container {
+ display: flex;
+
+ .value {
+ flex: 1;
+ }
+
+}
+
+.container:hover {
+ background-color: #0002;
+}
+
+select {
+ all: unset;
+ width: 100%;
+}
+
+.unchanged {
+ background-color: lightgreen !important;
+}
+
+.invalid {
+ background-color: red !important;
+}
+
+.changed {
+ background-color: yellow !important;
+}
diff --git a/src/main/angular/src/app/series/select/series-select.ts b/src/main/angular/src/app/series/select/series-select.ts
index 61af75b..147d1c8 100644
--- a/src/main/angular/src/app/series/select/series-select.ts
+++ b/src/main/angular/src/app/series/select/series-select.ts
@@ -4,6 +4,7 @@ import {FormsModule} from '@angular/forms';
import {NgClass} from '@angular/common';
import {faPen} from '@fortawesome/free-solid-svg-icons';
import {Series} from '../Series';
+import {or} from '../../common';
export enum SeriesType {
BOOL = 'BOOL',
@@ -35,47 +36,36 @@ export class SeriesSelect {
list: Series[] = [];
@Output()
- readonly onChange = new EventEmitter();
+ readonly onChange = new EventEmitter();
protected showPen: boolean = false;
- protected model: Series | null = null;
-
- protected editing: boolean = false;
+ protected model: number | null = null;
@Input()
set initial(value: Series | null) {
this._initial = value;
- if (!this.editing) {
- this.model = this.initial;
- }
+ this.reset();
+ }
+
+ private reset() {
+ this.model = or(this.initial, i => i.id, null);
}
get initial(): Series | null {
return this._initial;
}
- protected start() {
- this.editing = true;
- setTimeout(() => this.input.nativeElement.focus(), 0);
- }
-
protected apply() {
if (this.model !== this.initial) {
this.onChange.emit(this.model);
}
- this.editing = false;
- }
-
- protected cancel() {
- this.model = this.initial;
- this.editing = false;
}
protected classes(): {} {
return {
- "unchanged": this.editing && this.model === this.initial,
- "changed": this.model !== this.initial,
+ "unchanged": this.model === this.initial,
+ "changed": this.model !== or(this.initial, i => i.id, null),
"invalid": !this.allowEmpty && this.model === null,
};
}
diff --git a/src/main/java/de/ph87/data/DemoService.java b/src/main/java/de/ph87/data/DemoService.java
index 3ac1e88..50842e4 100644
--- a/src/main/java/de/ph87/data/DemoService.java
+++ b/src/main/java/de/ph87/data/DemoService.java
@@ -1,22 +1,8 @@
package de.ph87.data;
-import de.ph87.data.location.Location;
-import de.ph87.data.location.LocationRepository;
-import de.ph87.data.plot.Plot;
-import de.ph87.data.plot.PlotRepository;
-import de.ph87.data.plot.axis.Axis;
-import de.ph87.data.plot.axis.AxisRepository;
-import de.ph87.data.plot.axis.graph.Graph;
-import de.ph87.data.plot.axis.graph.GraphRepository;
-import de.ph87.data.plot.axis.graph.GraphType;
-import de.ph87.data.series.Series;
-import de.ph87.data.series.SeriesRepository;
-import de.ph87.data.series.SeriesType;
-import de.ph87.data.topic.TimestampType;
-import de.ph87.data.topic.Topic;
-import de.ph87.data.topic.TopicRepository;
-import de.ph87.data.topic.query.TopicQuery;
-import lombok.NonNull;
+import de.ph87.data.topic.TopicDto;
+import de.ph87.data.topic.TopicType;
+import de.ph87.data.topic.TopicService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
@@ -24,26 +10,14 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import java.util.List;
-
@Slf4j
@Service
@RequiredArgsConstructor
public class DemoService {
- private final SeriesRepository seriesRepository;
-
- private final TopicRepository topicRepository;
-
- private final PlotRepository plotRepository;
-
- private final AxisRepository axisRepository;
-
- private final GraphRepository graphRepository;
-
private final DemoConfig demoConfig;
- private final LocationRepository locationRepository;
+ private final TopicService topicService;
@Transactional
@EventListener(ApplicationReadyEvent.class)
@@ -51,331 +25,12 @@ public class DemoService {
if (!demoConfig.isEnabled()) {
return;
}
- location("Eppelborn", 49.4086, 6.9645);
- location("Friedrichsthal", 49.3270, 7.0947);
- topics();
- }
-
- private void location(@NonNull final String name, final double latitude, final double longitude) {
- locationRepository.findByName(name).orElseGet(() -> {
- final Location created = new Location();
- created.setName(name);
- created.setLatitude(latitude);
- created.setLongitude(longitude);
- return locationRepository.save(created);
+ final TopicDto dto = topicService.create();
+ topicService.modify(dto.id, topic -> {
+ topic.setEnabled(true);
+ topic.setName("openDTU/pv/patrix/json2");
+ topic.setType(TopicType.PatrixOpenDtu);
});
}
- private void topics() {
- final Series fallbackRelay0 = series("fallback/relay0", "", SeriesType.BOOL, 5);
- topic(
- "fallback/relay0",
- "$.timestamp",
- new TopicQuery(fallbackRelay0, "$.state", "$.stateEpoch", "true")
- );
-
- final Series infraredHeater = series("infraredHeater/state", "", SeriesType.BOOL, 5);
- topic(
- "Infrarotheizung",
- "now",
- new TopicQuery(infraredHeater, "$.state", "timestamp", "true")
- );
-
- final Series electricityEnergyProduce = series("electricity/energy/produce", "kWh", SeriesType.DELTA, 5);
- final Series electricityPowerProduce = series("electricity/power/produce", "W", SeriesType.VARYING, 5);
- topicMeterNumber(
- "openDTU/pv/patrix/json2",
- TimestampType.EPOCH_SECONDS,
- "$.timestamp",
- "$.inverter",
- new TopicQuery(electricityEnergyProduce, "$.totalKWh"),
- new TopicQuery(electricityPowerProduce, "$.totalW")
- );
-
- final Series electricityEnergyPurchase = series("electricity/energy/purchase", "kWh", SeriesType.DELTA, 5);
- 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);
- topicMeterNumber(
- "electricity/grid/json",
- TimestampType.EPOCH_SECONDS,
- "$.timestamp",
- "\"1ZPA0020300305\"",
- new TopicQuery(electricityEnergyPurchase, "$.purchaseWh", 0.001),
- new TopicQuery(electricityPowerPurchase, "$.powerW", 1, TopicQuery.Function.ONLY_POSITIVE),
- new TopicQuery(electricityEnergyDelivery, "$.deliveryWh", 0.001),
- new TopicQuery(electricityPowerDelivery, "$.powerW", 1, TopicQuery.Function.ONLY_NEGATIVE_BUT_NEGATE)
- );
-
- final Series elternElectricityEnergyProduce = series("eltern/electricity/energy/produce", "kWh", SeriesType.DELTA, 60);
- final Series elternElectricityPowerProduce = series("eltern/electricity/power/produce", "W", SeriesType.VARYING, 60);
- topicMeterNumber(
- "Eltern/Solar/Shelly/status/switch:0",
- TimestampType.EPOCH_SECONDS,
- "$.aenergy.minute_ts",
- "\"2025-10-27-shelly\"",
- new TopicQuery(elternElectricityEnergyProduce, "$.aenergy.total", 0.001),
- new TopicQuery(elternElectricityPowerProduce, "$.apower")
- );
-
- final Series elternElectricityEnergyPurchase = series("eltern/electricity/energy/purchase", "kWh", SeriesType.DELTA, 10);
- final Series elternElectricityPowerPurchase = series("eltern/electricity/power/purchase", "W", SeriesType.VARYING, 10);
- final Series elternElectricityEnergyDelivery = series("eltern/electricity/energy/delivery", "kWh", SeriesType.DELTA, 10);
- final Series elternElectricityPowerDelivery = series("eltern/electricity/power/delivery", "W", SeriesType.VARYING, 10);
- topicMeterNumber(
- "Eltern/SmartMeter/SENSOR",
- TimestampType.ISO_LOCAL_DATE_TIME,
- "$.Time",
- "sml_meter_number_raw:$.meter.number",
- new TopicQuery(elternElectricityEnergyPurchase, "$.meter.energy_purchased_kwh"),
- new TopicQuery(elternElectricityPowerPurchase, "$.meter.power_w", 1, TopicQuery.Function.ONLY_POSITIVE),
- new TopicQuery(elternElectricityEnergyDelivery, "$.meter.energy_delivered_kwh"),
- new TopicQuery(elternElectricityPowerDelivery, "$.meter.power_w", 1, TopicQuery.Function.ONLY_NEGATIVE_BUT_NEGATE)
- );
-
- final Series gardenPressure = series("garden/pressure", "hPa", SeriesType.VARYING, 5);
- final Series gardenTemperature = series("garden/temperature", "°C", SeriesType.VARYING, 5);
- final Series gardenHumidityAbsolute = series("garden/humidity/absolute", "mg/L", SeriesType.VARYING, 5);
- final Series gardenHumidityRelative = series("garden/humidity/relative", "%", SeriesType.VARYING, 5);
- topic("garten/sensor/pressure", new TopicQuery(gardenPressure, "$.value"));
- topic("garten/sensor/temperature", new TopicQuery(gardenTemperature, "$.value"));
- topic("garten/sensor/humidity_absolute", new TopicQuery(gardenHumidityAbsolute, "$.value"));
- topic("garten/sensor/humidity_relative", new TopicQuery(gardenHumidityRelative, "$.value"));
-
- final Series bedroomPressure = series("bedroom/pressure", "hPa", SeriesType.VARYING, 5);
- final Series bedroomTemperature = series("bedroom/temperature", "°C", SeriesType.VARYING, 5);
- final Series bedroomHumidityAbsolute = series("bedroom/humidity/absolute", "mg/L", SeriesType.VARYING, 5);
- final Series bedroomHumidityRelative = series("bedroom/humidity/relative", "%", SeriesType.VARYING, 5);
- topic("schlafzimmer/sensor/pressure", new TopicQuery(bedroomPressure, "$.value"));
- topic("schlafzimmer/sensor/temperature", new TopicQuery(bedroomTemperature, "$.value"));
- topic("schlafzimmer/sensor/humidity_absolute", new TopicQuery(bedroomHumidityAbsolute, "$.value"));
- topic("schlafzimmer/sensor/humidity_relative", new TopicQuery(bedroomHumidityRelative, "$.value"));
-
- final Series basementTemperature = series("basement/temperature", "°C", SeriesType.VARYING, 60);
- final Series basementHumidityAbsolute = series("basement/humidity/absolute", "mg/L", SeriesType.VARYING, 60);
- final Series basementHumidityRelative = series("basement/humidity/relative", "%", SeriesType.VARYING, 60);
- topic("aggregation/heizraum/luftfeuchte/absolut", "$.lastTime", new TopicQuery(basementTemperature, "$.lastValue"));
- topic("aggregation/heizraum/luftfeuchte/relativ", "$.lastTime", new TopicQuery(basementHumidityAbsolute, "$.lastValue"));
- topic("aggregation/heizraum/temperatur", "$.lastTime", new TopicQuery(basementHumidityRelative, "$.lastValue"));
-
- final Series heatingExhaustTemperature = series("heating/exhaust/temperature", "°C", SeriesType.VARYING, 60);
- topic("aggregation/heizung/abgas/temperatur", "$.lastTime", new TopicQuery(heatingExhaustTemperature, "$.lastValue"));
-
- final Series heatingCircuitReturnTemperature = series("heating/circuit/return/temperature", "°C", SeriesType.VARYING, 60);
- final Series heatingCircuitSupplyTemperature = series("heating/circuit/supply/temperature", "°C", SeriesType.VARYING, 60);
- topic("aggregation/heizung/heizkreis/ruecklauf/temperatur", "$.lastTime", new TopicQuery(heatingCircuitReturnTemperature, "$.lastValue"));
- topic("aggregation/heizung/heizkreis/vorlauf/temperatur", "$.lastTime", new TopicQuery(heatingCircuitSupplyTemperature, "$.lastValue"));
-
- final Series heatingBufferInletTemperature = series("heating/buffer/inlet/temperature", "°C", SeriesType.VARYING, 60);
- final Series heatingBufferOutletTemperature = series("heating/buffer/outlet/temperature", "°C", SeriesType.VARYING, 60);
- final Series heatingBufferCirculationTemperature = series("heating/buffer/circulation/temperature", "°C", SeriesType.VARYING, 60);
- topic("aggregation/heizung/puffer/eingang/temperatur", "$.lastTime", new TopicQuery(heatingBufferInletTemperature, "$.lastValue"));
- topic("aggregation/heizung/puffer/ausgang/temperatur", "$.lastTime", new TopicQuery(heatingBufferOutletTemperature, "$.lastValue"));
- topic("aggregation/heizung/puffer/zirkulation/temperatur", "$.lastTime", new TopicQuery(heatingBufferCirculationTemperature, "$.lastValue"));
-
- final Series heatingBufferInsideTemperature = series("heating/buffer/inside/temperature", "°C", SeriesType.VARYING, 60);
- topic("aggregation/heizung/puffer/speicher/temperatur", "$.lastTime", new TopicQuery(heatingBufferInsideTemperature, "$.lastValue"));
-
- final Series heatingBufferSupplyTemperature = series("heating/buffer/supply/temperature", "°C", SeriesType.VARYING, 60);
- final Series heatingBufferReturnTemperature = series("heating/buffer/return/temperature", "°C", SeriesType.VARYING, 60);
- topic("aggregation/heizung/puffer/vorlauf/temperatur", "$.lastTime", new TopicQuery(heatingBufferSupplyTemperature, "$.lastValue"));
- topic("aggregation/heizung/puffer/ruecklauf/temperatur", "$.lastTime", new TopicQuery(heatingBufferReturnTemperature, "$.lastValue"));
-
- final Series cisternVolume = series("cistern/volume", "L", SeriesType.VARYING, 5);
- topic("cistern/volume/PatrixJson", "$.date", new TopicQuery(cisternVolume, "$.value"));
-
- plotRepository.deleteAll();
- zuhauseEnergie();
- zuhauseTemperatur();
- eltern();
- leistungVergleich();
- }
-
- private void zuhauseEnergie() {
- final Plot plot = plotRepository.save(new Plot(plotRepository.count()));
- plot.setName("Zuhause Energie");
- plot.setDashboard(true);
-
- final Axis energy = axisRepository.save(new Axis(plot));
- plot.addAxis(energy);
- energy.setRight(true);
- energy.setName("Energie");
- energy.setUnit("kWh");
-
- final String stack = "a";
-
- final Series electricityEnergyDelivery = seriesRepository.findByName("electricity/energy/delivery").orElseThrow();
- final Graph electricityEnergyDeliveryGraph = graphRepository.save(new Graph(energy, electricityEnergyDelivery));
- electricityEnergyDeliveryGraph.setType(GraphType.BAR);
- electricityEnergyDeliveryGraph.setStack(stack);
- electricityEnergyDeliveryGraph.setName("Zuhause Überschuss");
- electricityEnergyDeliveryGraph.setColor("#FF00FF");
- electricityEnergyDeliveryGraph.setFactor(-1);
- energy.addGraph(electricityEnergyDeliveryGraph);
-
- final Series electricityEnergyProduce = seriesRepository.findByName("electricity/energy/produce").orElseThrow();
- final Graph electricityEnergyProduceGraph = graphRepository.save(new Graph(energy, electricityEnergyProduce));
- electricityEnergyProduceGraph.setSeries2(electricityEnergyDelivery);
- electricityEnergyProduceGraph.setName("Zuhause Eigenverbrauch");
- electricityEnergyProduceGraph.setType(GraphType.BAR);
- electricityEnergyProduceGraph.setStack(stack);
- electricityEnergyProduceGraph.setColor("#008800");
- energy.addGraph(electricityEnergyProduceGraph);
-
- final Series electricityEnergyPurchase = seriesRepository.findByName("electricity/energy/purchase").orElseThrow();
- final Graph electricityEnergyPurchaseGraph = graphRepository.save(new Graph(energy, electricityEnergyPurchase));
- electricityEnergyPurchaseGraph.setType(GraphType.BAR);
- electricityEnergyPurchaseGraph.setStack(stack);
- electricityEnergyPurchaseGraph.setName("Zuhause Bezug");
- electricityEnergyPurchaseGraph.setColor("#FF8800");
- energy.addGraph(electricityEnergyPurchaseGraph);
- }
-
- private void zuhauseTemperatur() {
- final Plot plot = plotRepository.save(new Plot(plotRepository.count()));
- plot.setName("Zuhause Temperaturen");
- plot.setDashboard(true);
-
- final Axis temperature = axisRepository.save(new Axis(plot));
- plot.addAxis(temperature);
- temperature.setRight(true);
- temperature.setMin(0.0);
- temperature.setName("Temperatur");
- temperature.setUnit("°C");
-
- final Series bedroomTemperature = seriesRepository.findByName("bedroom/temperature").orElseThrow();
- final Graph bedroomTemperatureGraph = graphRepository.save(new Graph(temperature, bedroomTemperature));
- bedroomTemperatureGraph.setName("Schlafzimmer");
- bedroomTemperatureGraph.setColor("#0000FF");
- bedroomTemperatureGraph.setMin(true);
- bedroomTemperatureGraph.setAvg(false);
- bedroomTemperatureGraph.setMax(true);
- temperature.addGraph(bedroomTemperatureGraph);
-
- final Series gardenTemperature = seriesRepository.findByName("garden/temperature").orElseThrow();
- final Graph gardenTemperatureGraph = graphRepository.save(new Graph(temperature, gardenTemperature));
- gardenTemperatureGraph.setName("Garten");
- gardenTemperatureGraph.setColor("#00FF00");
- gardenTemperatureGraph.setMin(true);
- gardenTemperatureGraph.setAvg(false);
- gardenTemperatureGraph.setMax(true);
- temperature.addGraph(gardenTemperatureGraph);
-
- final Series bufferTemperature = seriesRepository.findByName("heating/buffer/inside/temperature").orElseThrow();
- final Graph bufferTemperatureGraph = graphRepository.save(new Graph(temperature, bufferTemperature));
- bufferTemperatureGraph.setName("Puffer");
- bufferTemperatureGraph.setColor("#FF00FF");
- bufferTemperatureGraph.setMin(true);
- bufferTemperatureGraph.setAvg(false);
- bufferTemperatureGraph.setMax(true);
- temperature.addGraph(bufferTemperatureGraph);
-
- final Series circuitTemperature = seriesRepository.findByName("heating/circuit/supply/temperature").orElseThrow();
- final Graph circuitTemperatureGraph = graphRepository.save(new Graph(temperature, circuitTemperature));
- circuitTemperatureGraph.setName("Heizkreis");
- circuitTemperatureGraph.setColor("#FF0000");
- circuitTemperatureGraph.setMin(true);
- circuitTemperatureGraph.setAvg(false);
- circuitTemperatureGraph.setMax(true);
- temperature.addGraph(circuitTemperatureGraph);
- }
-
- private void eltern() {
- final Plot plot = plotRepository.save(new Plot(plotRepository.count()));
- plot.setName("Eltern Energie");
-
- final Axis energy = axisRepository.save(new Axis(plot));
- plot.addAxis(energy);
- plot.setDashboard(true);
- energy.setRight(true);
- energy.setName("Energie");
- energy.setUnit("kWh");
-
- final Series electricityEnergyDelivery = seriesRepository.findByName("eltern/electricity/energy/delivery").orElseThrow();
- final Graph electricityEnergyDeliveryGraph = graphRepository.save(new Graph(energy, electricityEnergyDelivery));
- electricityEnergyDeliveryGraph.setName("Überschuss");
- electricityEnergyDeliveryGraph.setType(GraphType.BAR);
- electricityEnergyDeliveryGraph.setFactor(-1);
- electricityEnergyDeliveryGraph.setStack("a");
- electricityEnergyDeliveryGraph.setColor("#FF00FF");
- energy.addGraph(electricityEnergyDeliveryGraph);
-
- final Series electricityEnergyProduce = seriesRepository.findByName("eltern/electricity/energy/produce").orElseThrow();
- final Graph electricityEnergyProduceGraph = graphRepository.save(new Graph(energy, electricityEnergyProduce));
- electricityEnergyProduceGraph.setSeries2(electricityEnergyDelivery);
- electricityEnergyProduceGraph.setName("Eigenverbrauch");
- electricityEnergyProduceGraph.setType(GraphType.BAR);
- electricityEnergyProduceGraph.setStack("a");
- electricityEnergyProduceGraph.setColor("#008800");
- energy.addGraph(electricityEnergyProduceGraph);
-
- final Series electricityEnergyPurchase = seriesRepository.findByName("eltern/electricity/energy/purchase").orElseThrow();
- final Graph electricityEnergyPurchaseGraph = graphRepository.save(new Graph(energy, electricityEnergyPurchase));
- electricityEnergyPurchaseGraph.setName("Bezug");
- electricityEnergyPurchaseGraph.setType(GraphType.BAR);
- electricityEnergyPurchaseGraph.setStack("a");
- electricityEnergyPurchaseGraph.setColor("#FF8800");
- energy.addGraph(electricityEnergyPurchaseGraph);
- }
-
- private void leistungVergleich() {
- final Plot plot = plotRepository.save(new Plot(plotRepository.count()));
- plot.setName("Leistung Vergleich");
-
- final Axis power = axisRepository.save(new Axis(plot));
- plot.addAxis(power);
- plot.setDashboard(true);
- power.setRight(true);
- power.setName("Leistung");
- power.setUnit("W");
-
- final Series electricityPowerProduce = seriesRepository.findByName("electricity/power/produce").orElseThrow();
- final Graph electricityPowerProduceGraph = graphRepository.save(new Graph(power, electricityPowerProduce));
- electricityPowerProduceGraph.setName("Zuhause");
- electricityPowerProduceGraph.setType(GraphType.BAR);
- electricityPowerProduceGraph.setColor("#008800");
- power.addGraph(electricityPowerProduceGraph);
-
- final Series elternElectricityPowerProduce = seriesRepository.findByName("eltern/electricity/power/produce").orElseThrow();
- final Graph elternElectricityPowerProduceGraph = graphRepository.save(new Graph(power, elternElectricityPowerProduce));
- elternElectricityPowerProduceGraph.setName("Eltern");
- elternElectricityPowerProduceGraph.setType(GraphType.BAR);
- elternElectricityPowerProduceGraph.setColor("#0088FF");
- power.addGraph(elternElectricityPowerProduceGraph);
- }
-
- @NonNull
- private Series series(@NonNull final String name, @NonNull final String unit, @NonNull final SeriesType type, final int seconds) {
- return seriesRepository
- .findByName(name)
- .stream()
- .peek(existing -> {
- existing.setUnit(unit);
- existing.setType(type);
- existing.setSeconds(seconds);
- })
- .findFirst()
- .orElseGet(() -> seriesRepository.save(new Series(name, unit, 1, seconds, type)));
- }
-
- private void topic(@NonNull final String name, @NonNull final TopicQuery... queries) {
- topic(name, "$.timestamp", queries);
- }
-
- private void topic(@NonNull final String name, @NonNull final String timestampQuery, @NonNull final TopicQuery... queries) {
- final Topic topic = topicRepository.findByName(name).orElseGet(() -> topicRepository.save(new Topic(name)));
- topic.setTimestampQuery(timestampQuery);
- topic.getQueries().clear();
- topic.getQueries().addAll(List.of(queries));
- }
-
- private void topicMeterNumber(@NonNull final String name, @NonNull final TimestampType timestampType, @NonNull final String timestampQuery, @NonNull final String meterNumberQuery, @NonNull final TopicQuery... queries) {
- final Topic topic = topicRepository.findByName(name).orElseGet(() -> topicRepository.save(new Topic(name)));
- topic.setMeterNumberQuery(meterNumberQuery);
- topic.setTimestampType(timestampType);
- topic.setTimestampQuery(timestampQuery);
- topic.getQueries().clear();
- topic.getQueries().addAll(List.of(queries));
- }
-
}
diff --git a/src/main/java/de/ph87/data/location/Location.java b/src/main/java/de/ph87/data/location/Location.java
index 4de284f..515f903 100644
--- a/src/main/java/de/ph87/data/location/Location.java
+++ b/src/main/java/de/ph87/data/location/Location.java
@@ -44,21 +44,31 @@ public class Location {
@Setter
@Nullable
@ManyToOne
- private Series purchase;
+ private Series energyPurchase;
@Setter
@Nullable
@ManyToOne
- private Series delivery;
+ private Series energyDeliver;
@Setter
@Nullable
@ManyToOne
- private Series produce;
+ private Series energyProduce;
@Setter
@Nullable
@ManyToOne
- private Series power;
+ private Series powerPurchase;
+
+ @Setter
+ @Nullable
+ @ManyToOne
+ private Series powerDeliver;
+
+ @Setter
+ @Nullable
+ @ManyToOne
+ private Series powerProduce;
}
diff --git a/src/main/java/de/ph87/data/location/LocationController.java b/src/main/java/de/ph87/data/location/LocationController.java
index 84df576..91ea3e1 100644
--- a/src/main/java/de/ph87/data/location/LocationController.java
+++ b/src/main/java/de/ph87/data/location/LocationController.java
@@ -39,42 +39,52 @@ public class LocationController {
@GetMapping("{id}/delete")
public LocationDto delete(@PathVariable final long id) {
- return locationService.delete(id);
+ return locationService.set(id, locationRepository::delete);
}
@PostMapping("{id}/name")
public LocationDto name(@PathVariable final long id, @RequestBody(required = false) @Nullable final String name) {
- return locationService.name(id, name == null ? "" : name);
+ return locationService.set(id, location -> location.setName(name == null ? "" : name));
}
@PostMapping("{id}/latitude")
public LocationDto latitude(@PathVariable final long id, @RequestBody final double latitude) {
- return locationService.latitude(id, latitude);
+ return locationService.set(id, location -> location.setLatitude(latitude));
}
@PostMapping("{id}/longitude")
public LocationDto longitude(@PathVariable final long id, @RequestBody final double longitude) {
- return locationService.longitude(id, longitude);
+ return locationService.set(id, location -> location.setLongitude(longitude));
}
- @PostMapping("{id}/purchase")
- public LocationDto purchase(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
- return locationService.purchase(id, seriesId);
+ @PostMapping("{id}/energyPurchase")
+ public LocationDto energyPurchase(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
+ return locationService.setSeries(id, seriesId, Location::setEnergyPurchase);
}
- @PostMapping("{id}/delivery")
- public LocationDto delivery(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
- return locationService.delivery(id, seriesId);
+ @PostMapping("{id}/energyDeliver")
+ public LocationDto energyDeliver(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
+ return locationService.setSeries(id, seriesId, Location::setEnergyDeliver);
}
- @PostMapping("{id}/produce")
- public LocationDto produce(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
- return locationService.produce(id, seriesId);
+ @PostMapping("{id}/energyProduce")
+ public LocationDto energyProduce(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
+ return locationService.setSeries(id, seriesId, Location::setEnergyProduce);
}
- @PostMapping("{id}/power")
- public LocationDto power(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
- return locationService.power(id, seriesId);
+ @PostMapping("{id}/powerPurchase")
+ public LocationDto powerPurchase(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
+ return locationService.setSeries(id, seriesId, Location::setPowerPurchase);
+ }
+
+ @PostMapping("{id}/powerDeliver")
+ public LocationDto powerDeliver(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
+ return locationService.setSeries(id, seriesId, Location::setPowerDeliver);
+ }
+
+ @PostMapping("{id}/powerProduce")
+ public LocationDto powerProduce(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
+ return locationService.setSeries(id, seriesId, Location::setPowerProduce);
}
}
diff --git a/src/main/java/de/ph87/data/location/LocationDto.java b/src/main/java/de/ph87/data/location/LocationDto.java
index 9ca60b2..9d06cdc 100644
--- a/src/main/java/de/ph87/data/location/LocationDto.java
+++ b/src/main/java/de/ph87/data/location/LocationDto.java
@@ -21,16 +21,22 @@ public class LocationDto {
public final double longitude;
@Nullable
- public final SeriesDto purchase;
+ public final SeriesDto energyPurchase;
@Nullable
- public final SeriesDto delivery;
+ public final SeriesDto energyDeliver;
@Nullable
- public final SeriesDto produce;
+ public final SeriesDto energyProduce;
@Nullable
- public final SeriesDto power;
+ public final SeriesDto powerPurchase;
+
+ @Nullable
+ public final SeriesDto powerDeliver;
+
+ @Nullable
+ public final SeriesDto powerProduce;
public LocationDto(@NonNull final Location location) {
this.id = location.getId();
@@ -38,10 +44,12 @@ public class LocationDto {
this.name = location.getName();
this.latitude = location.getLatitude();
this.longitude = location.getLongitude();
- this.purchase = map(location.getPurchase(), SeriesDto::new);
- this.delivery = map(location.getDelivery(), SeriesDto::new);
- this.produce = map(location.getProduce(), SeriesDto::new);
- this.power = map(location.getPower(), SeriesDto::new);
+ this.energyPurchase = map(location.getEnergyPurchase(), SeriesDto::new);
+ this.energyDeliver = map(location.getEnergyDeliver(), SeriesDto::new);
+ this.energyProduce = map(location.getEnergyProduce(), SeriesDto::new);
+ this.powerPurchase = map(location.getPowerPurchase(), SeriesDto::new);
+ this.powerDeliver = map(location.getPowerDeliver(), SeriesDto::new);
+ this.powerProduce = map(location.getPowerProduce(), SeriesDto::new);
}
}
diff --git a/src/main/java/de/ph87/data/location/LocationService.java b/src/main/java/de/ph87/data/location/LocationService.java
index 0fb560b..5cb2d6b 100644
--- a/src/main/java/de/ph87/data/location/LocationService.java
+++ b/src/main/java/de/ph87/data/location/LocationService.java
@@ -1,7 +1,7 @@
package de.ph87.data.location;
import de.ph87.data.series.Series;
-import de.ph87.data.series.SeriesRepository;
+import de.ph87.data.series.SeriesService;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@@ -21,7 +21,7 @@ public class LocationService {
private final LocationRepository locationRepository;
- private final SeriesRepository seriesRepository;
+ private final SeriesService seriesService;
@NonNull
@Transactional
@@ -31,60 +31,14 @@ public class LocationService {
@NonNull
@Transactional
- public LocationDto delete(final long id) {
- return _set(id, locationRepository::delete);
+ public LocationDto setSeries(final long id, @Nullable final Long seriesId, @NonNull final BiConsumer setter) {
+ final Series series = seriesId == null ? null : seriesService.getById(seriesId);
+ return set(id, location -> setter.accept(location, series));
}
@NonNull
@Transactional
- public LocationDto name(final long id, @NonNull final String name) {
- return _set(id, location -> location.setName(name));
- }
-
- @NonNull
- @Transactional
- public LocationDto latitude(final long id, final double latitude) {
- return _set(id, location -> location.setLatitude(latitude));
- }
-
- @NonNull
- @Transactional
- public LocationDto longitude(final long id, final double longitude) {
- return _set(id, location -> location.setLongitude(longitude));
- }
-
- @NonNull
- @Transactional
- public LocationDto purchase(final long id, final Long seriesId) {
- return _setSeries(id, seriesId, Location::setPurchase);
- }
-
- @NonNull
- @Transactional
- public LocationDto delivery(final long id, final Long seriesId) {
- return _setSeries(id, seriesId, Location::setDelivery);
- }
-
- @NonNull
- @Transactional
- public LocationDto produce(final long id, final Long seriesId) {
- return _setSeries(id, seriesId, Location::setProduce);
- }
-
- @NonNull
- @Transactional
- public LocationDto power(final long id, final Long seriesId) {
- return _setSeries(id, seriesId, Location::setPower);
- }
-
- @NonNull
- private LocationDto _setSeries(final long id, @Nullable final Long seriesId, @NonNull final BiConsumer setter) {
- final Series series = seriesId == null ? null : seriesRepository.findById(seriesId).orElseThrow(notFound(Series.class, "id", seriesId));
- return _set(id, location -> setter.accept(location, series));
- }
-
- @NonNull
- private LocationDto _set(final long id, @NonNull final Consumer setter) {
+ public LocationDto set(final long id, @NonNull final Consumer setter) {
final Location location = locationRepository.findById(id).orElseThrow(notFound(Location.class, "id", id));
setter.accept(location);
return new LocationDto(location);
diff --git a/src/main/java/de/ph87/data/plot/axis/AxisService.java b/src/main/java/de/ph87/data/plot/axis/AxisService.java
index 40cf438..b8a961c 100644
--- a/src/main/java/de/ph87/data/plot/axis/AxisService.java
+++ b/src/main/java/de/ph87/data/plot/axis/AxisService.java
@@ -4,8 +4,6 @@ import de.ph87.data.plot.PlotDto;
import de.ph87.data.plot.PlotService;
import de.ph87.data.plot.axis.graph.Graph;
import de.ph87.data.plot.axis.graph.GraphRepository;
-import de.ph87.data.series.Series;
-import de.ph87.data.series.SeriesRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -23,8 +21,6 @@ public class AxisService {
private final GraphRepository graphRepository;
- private final SeriesRepository seriesRepository;
-
private final PlotService plotService;
@NonNull
@@ -40,8 +36,7 @@ public class AxisService {
@NonNull
@Transactional
public PlotDto addGraph(final long axisId) {
- final Series series = seriesRepository.findFirstByOrderByNameAsc().orElseThrow();
- return set(axisId, axis -> axis.addGraph(graphRepository.save(new Graph(axis, series))));
+ return set(axisId, axis -> axis.addGraph(graphRepository.save(new Graph(axis))));
}
@NonNull
diff --git a/src/main/java/de/ph87/data/plot/axis/graph/Graph.java b/src/main/java/de/ph87/data/plot/axis/graph/Graph.java
index d0fc775..06eb24a 100644
--- a/src/main/java/de/ph87/data/plot/axis/graph/Graph.java
+++ b/src/main/java/de/ph87/data/plot/axis/graph/Graph.java
@@ -38,8 +38,8 @@ public class Graph {
private Axis axis;
@Setter
- @NonNull
- @ManyToOne(optional = false)
+ @Nullable
+ @ManyToOne
private Series series;
@Setter
@@ -108,9 +108,8 @@ public class Graph {
@Column(nullable = false)
private boolean avg = true;
- public Graph(@NonNull final Axis axis, @NonNull final Series series) {
+ public Graph(@NonNull final Axis axis) {
this.axis = axis;
- this.series = series;
}
public Graph(@NonNull final Axis axis, @NonNull final Graph graph) {
diff --git a/src/main/java/de/ph87/data/plot/axis/graph/GraphController.java b/src/main/java/de/ph87/data/plot/axis/graph/GraphController.java
index e8cb18c..382f955 100644
--- a/src/main/java/de/ph87/data/plot/axis/graph/GraphController.java
+++ b/src/main/java/de/ph87/data/plot/axis/graph/GraphController.java
@@ -2,7 +2,7 @@ package de.ph87.data.plot.axis.graph;
import de.ph87.data.plot.PlotDto;
import de.ph87.data.plot.axis.AxisRepository;
-import de.ph87.data.series.SeriesRepository;
+import de.ph87.data.series.SeriesService;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@@ -26,10 +26,10 @@ public class GraphController {
private final GraphService graphService;
- private final SeriesRepository seriesRepository;
-
private final AxisRepository axisRepository;
+ private final SeriesService seriesService;
+
@NonNull
@GetMapping("{id}/delete")
public PlotDto delete(@PathVariable final long id) {
@@ -37,86 +37,86 @@ public class GraphController {
}
@PostMapping("{id}/visible")
- public PlotDto visible(@PathVariable final long id, @RequestBody final boolean value) {
- return graphService.set(id, graph -> graph.setVisible(value));
+ public PlotDto visible(@PathVariable final long id, @RequestBody final boolean visible) {
+ return graphService.set(id, graph -> graph.setVisible(visible));
}
@PostMapping("{id}/type")
- public PlotDto type(@PathVariable final long id, @RequestBody @NonNull final String value) {
- return graphService.set(id, graph -> graph.setType(GraphType.valueOf(value)));
+ public PlotDto type(@PathVariable final long id, @RequestBody @NonNull final String typeName) {
+ return graphService.set(id, graph -> graph.setType(GraphType.valueOf(typeName)));
}
@PostMapping("{id}/name")
- public PlotDto name(@PathVariable final long id, @RequestBody(required = false) @Nullable final String value) {
- return graphService.set(id, graph -> graph.setName(or(value, "")));
+ public PlotDto name(@PathVariable final long id, @RequestBody(required = false) @Nullable final String name) {
+ return graphService.set(id, graph -> graph.setName(or(name, "")));
}
@PostMapping("{id}/series")
- public PlotDto series(@PathVariable final long id, @RequestBody final long value) {
- return graphService.set(id, graph -> graph.setSeries(seriesRepository.findById(value).orElseThrow()));
+ public PlotDto series(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long series) {
+ return graphService.set(id, graph -> graph.setSeries(series == null ? null : seriesService.getById(series)));
}
@PostMapping("{id}/factor")
- public PlotDto factor(@PathVariable final long id, @RequestBody final double value) {
- return graphService.set(id, graph -> graph.setFactor(value));
+ public PlotDto factor(@PathVariable final long id, @RequestBody final double factor) {
+ return graphService.set(id, graph -> graph.setFactor(factor));
}
@PostMapping("{id}/operation")
- public PlotDto operation(@PathVariable final long id, @RequestBody @NonNull final String value) {
- return graphService.set(id, graph -> graph.setOperation(GraphOperation.valueOf(value)));
+ public PlotDto operation(@PathVariable final long id, @RequestBody @NonNull final String operationName) {
+ return graphService.set(id, graph -> graph.setOperation(GraphOperation.valueOf(operationName)));
}
@PostMapping("{id}/series2")
- public PlotDto series2(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long value) {
- return graphService.set(id, graph -> graph.setSeries2(value == null ? null : seriesRepository.findById(value).orElseThrow()));
+ public PlotDto series2(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long series2) {
+ return graphService.set(id, graph -> graph.setSeries2(series2 == null ? null : seriesService.getById(series2)));
}
@PostMapping("{id}/factor2")
- public PlotDto factor2(@PathVariable final long id, @RequestBody final double value) {
- return graphService.set(id, graph -> graph.setFactor2(value));
+ public PlotDto factor2(@PathVariable final long id, @RequestBody final double factor2) {
+ return graphService.set(id, graph -> graph.setFactor2(factor2));
}
@PostMapping("{id}/color")
- public PlotDto color(@PathVariable final long id, @RequestBody @NonNull final String value) {
- return graphService.set(id, graph -> graph.setColor(value));
+ public PlotDto color(@PathVariable final long id, @RequestBody @NonNull final String color) {
+ return graphService.set(id, graph -> graph.setColor(color));
}
@PostMapping("{id}/group")
- public PlotDto group(@PathVariable final long id, @RequestBody @NonNull final String value) {
- return graphService.set(id, graph -> graph.setGroup(Group.valueOf(value)));
+ public PlotDto group(@PathVariable final long id, @RequestBody @NonNull final String group) {
+ return graphService.set(id, graph -> graph.setGroup(Group.valueOf(group)));
}
@PostMapping("{id}/stack")
- public PlotDto stack(@PathVariable final long id, @RequestBody(required = false) @Nullable final String value) {
- return graphService.set(id, graph -> graph.setStack(or(value, "")));
+ public PlotDto stack(@PathVariable final long id, @RequestBody(required = false) @Nullable final String stack) {
+ return graphService.set(id, graph -> graph.setStack(or(stack, "")));
}
@PostMapping("{id}/min")
- public PlotDto min(@PathVariable final long id, @RequestBody final boolean value) {
- return graphService.set(id, graph -> graph.setMin(value));
+ public PlotDto min(@PathVariable final long id, @RequestBody final boolean min) {
+ return graphService.set(id, graph -> graph.setMin(min));
}
@PostMapping("{id}/max")
- public PlotDto max(@PathVariable final long id, @RequestBody final boolean value) {
- return graphService.set(id, graph -> graph.setMax(value));
+ public PlotDto max(@PathVariable final long id, @RequestBody final boolean max) {
+ return graphService.set(id, graph -> graph.setMax(max));
}
@PostMapping("{id}/avg")
- public PlotDto avg(@PathVariable final long id, @RequestBody final boolean value) {
- return graphService.set(id, graph -> graph.setAvg(value));
+ public PlotDto avg(@PathVariable final long id, @RequestBody final boolean avg) {
+ return graphService.set(id, graph -> graph.setAvg(avg));
}
@PostMapping("{id}/axis")
- public PlotDto axis(@PathVariable final long id, @RequestBody final long value) {
- return graphService.set(id, graph -> graph.setAxis(axisRepository.findById(value).orElseThrow()));
+ public PlotDto axis(@PathVariable final long id, @RequestBody final long axis) {
+ return graphService.set(id, graph -> graph.setAxis(axisRepository.findById(axis).orElseThrow()));
}
@PostMapping("{id}/position")
- public PlotDto position(@PathVariable final long id, @RequestBody final int value) {
+ public PlotDto position(@PathVariable final long id, @RequestBody final int position) {
return graphService.set(id, graph -> {
final List list = graph.getAxis().getGraphs();
list.remove(graph);
- list.add(Math.max(0, Math.min(value, list.size())), graph);
+ list.add(Math.max(0, Math.min(position, list.size())), graph);
});
}
diff --git a/src/main/java/de/ph87/data/series/Series.java b/src/main/java/de/ph87/data/series/Series.java
index ebd35a8..d44ff4a 100644
--- a/src/main/java/de/ph87/data/series/Series.java
+++ b/src/main/java/de/ph87/data/series/Series.java
@@ -64,7 +64,7 @@ public class Series {
@NonNull
@Column(nullable = false)
@Enumerated(EnumType.STRING)
- private SeriesType type;
+ private SeriesType type = SeriesType.VARYING;
public Series(@NonNull final String name, @NonNull final String unit, final int decimals, final int seconds, @NonNull final SeriesType type) {
this.name = name;
@@ -85,4 +85,9 @@ public class Series {
}
}
+ @NonNull
+ public String toShortString() {
+ return "#%d \"%s\"".formatted(id, name);
+ }
+
}
diff --git a/src/main/java/de/ph87/data/series/SeriesController.java b/src/main/java/de/ph87/data/series/SeriesController.java
index be0a207..f4dbe3f 100644
--- a/src/main/java/de/ph87/data/series/SeriesController.java
+++ b/src/main/java/de/ph87/data/series/SeriesController.java
@@ -29,7 +29,7 @@ public class SeriesController {
private final SeriesPointService seriesPointService;
- @PostMapping("create")
+ @GetMapping("create")
public SeriesDto create() {
return seriesService.create();
}
diff --git a/src/main/java/de/ph87/data/series/SeriesService.java b/src/main/java/de/ph87/data/series/SeriesService.java
index 8d8a508..316465c 100644
--- a/src/main/java/de/ph87/data/series/SeriesService.java
+++ b/src/main/java/de/ph87/data/series/SeriesService.java
@@ -7,6 +7,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.time.ZonedDateTime;
+import java.util.List;
import java.util.function.Consumer;
import static de.ph87.data.location.NotFoundException.notFound;
@@ -29,7 +31,7 @@ public class SeriesService {
private String _generateUniqueName() {
int index = 0;
while (true) {
- final String name = "series" + index;
+ final String name = "series/unnamed/" + index++;
if (!seriesRepository.existsByName(name)) {
return name;
}
@@ -38,14 +40,19 @@ public class SeriesService {
@NonNull
@Transactional
- SeriesDto modify(final long id, @NonNull Consumer modifier) {
+ public SeriesDto modify(final long id, @NonNull Consumer modifier) {
final Series series = getById(id);
modifier.accept(series);
return publish(series, CrudAction.MODIFIED);
}
@NonNull
- private Series getById(final long id) {
+ public List findAll() {
+ return seriesRepository.findAll();
+ }
+
+ @NonNull
+ public Series getById(final long id) {
return seriesRepository.findById(id).orElseThrow(notFound(Series.class, "id", id));
}
@@ -56,4 +63,12 @@ public class SeriesService {
return dto;
}
+ @NonNull
+ @Transactional
+ public Series update(final long seriesId, @NonNull final ZonedDateTime date, final double value) {
+ final Series series = getById(seriesId);
+ series.update(date, value);
+ return series;
+ }
+
}
diff --git a/src/main/java/de/ph87/data/series/data/bool/BoolDto.java b/src/main/java/de/ph87/data/series/data/bool/BoolDto.java
index 192a805..069fa6a 100644
--- a/src/main/java/de/ph87/data/series/data/bool/BoolDto.java
+++ b/src/main/java/de/ph87/data/series/data/bool/BoolDto.java
@@ -1,5 +1,6 @@
package de.ph87.data.series.data.bool;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import de.ph87.data.series.SeriesDto;
import de.ph87.data.websocket.IWebsocketMessage;
import lombok.Data;
@@ -33,6 +34,7 @@ public class BoolDto implements IWebsocketMessage {
@NonNull
@Override
+ @JsonIgnore
public String getWebsocketTopic() {
return "Bool/%d".formatted(series.id);
}
diff --git a/src/main/java/de/ph87/data/series/data/bool/BoolService.java b/src/main/java/de/ph87/data/series/data/bool/BoolService.java
index c7885f3..d49d2fa 100644
--- a/src/main/java/de/ph87/data/series/data/bool/BoolService.java
+++ b/src/main/java/de/ph87/data/series/data/bool/BoolService.java
@@ -1,8 +1,9 @@
package de.ph87.data.series.data.bool;
-import de.ph87.data.series.point.ISeriesPointRequest;
import de.ph87.data.series.Series;
+import de.ph87.data.series.SeriesService;
import de.ph87.data.series.data.DataId;
+import de.ph87.data.series.point.ISeriesPointRequest;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -18,12 +19,16 @@ import java.util.List;
@RequiredArgsConstructor
public class BoolService {
- private final BoolRepo boolRepo;
-
private final ApplicationEventPublisher applicationEventPublisher;
+ private final SeriesService seriesService;
+
+ private final BoolRepo boolRepo;
+
@Transactional
- public void write(@NonNull final Series series, @NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end, final boolean state, final boolean terminated) {
+ @SuppressWarnings("unused")
+ public void write(@NonNull final long seriesId, @NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end, final boolean state, final boolean terminated) {
+ final Series series = seriesService.update(seriesId, end, state ? 1 : 0);
final Bool bool = updateOrCreate(series, begin, end, state, terminated);
log.debug("Bool written: {}", bool);
applicationEventPublisher.publishEvent(new BoolDto(bool));
@@ -32,30 +37,22 @@ public class BoolService {
@NonNull
private Bool updateOrCreate(@NonNull final Series series, @NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end, final boolean state, final boolean terminated) {
final DataId id = new DataId(series, begin);
- return boolRepo
- .findById(id)
- .stream()
- .peek(
- existing -> {
- if (existing.isState() != state) {
- log.error("Differing states: received=(begin={}, end={}, state={}, terminated={}), existing={}", begin, end, state, terminated, existing);
- return;
- }
- if (existing.getEnd().isAfter(end)) {
- log.error("End ran backwards: received=(begin={}, end={}, state={}, terminated={}), existing={}", begin, end, state, terminated, existing);
- return;
- }
- if (existing.isTerminated() && (!terminated || !existing.getEnd().equals(end))) {
- log.error("Already terminated: received=(begin={}, end={}, state={}, terminated={}), existing={}", begin, end, state, terminated, existing);
- return;
- }
- existing.setEnd(end);
- existing.setTerminated(terminated);
- })
- .findFirst()
- .orElseGet(
- () -> boolRepo.save(new Bool(id, end, state, terminated))
- );
+ return boolRepo.findById(id).stream().peek(existing -> {
+ if (existing.isState() != state) {
+ log.error("Differing states: received=(begin={}, end={}, state={}, terminated={}), existing={}", begin, end, state, terminated, existing);
+ return;
+ }
+ if (existing.getEnd().isAfter(end)) {
+ log.error("End ran backwards: received=(begin={}, end={}, state={}, terminated={}), existing={}", begin, end, state, terminated, existing);
+ return;
+ }
+ if (existing.isTerminated() && (!terminated || !existing.getEnd().equals(end))) {
+ log.error("Already terminated: received=(begin={}, end={}, state={}, terminated={}), existing={}", begin, end, state, terminated, existing);
+ return;
+ }
+ existing.setEnd(end);
+ existing.setTerminated(terminated);
+ }).findFirst().orElseGet(() -> boolRepo.save(new Bool(id, end, state, terminated)));
}
@NonNull
diff --git a/src/main/java/de/ph87/data/series/data/delta/DeltaDto.java b/src/main/java/de/ph87/data/series/data/delta/DeltaDto.java
index 05f3627..c34dd0e 100644
--- a/src/main/java/de/ph87/data/series/data/delta/DeltaDto.java
+++ b/src/main/java/de/ph87/data/series/data/delta/DeltaDto.java
@@ -1,5 +1,6 @@
package de.ph87.data.series.data.delta;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import de.ph87.data.series.data.Interval;
import de.ph87.data.series.data.delta.meter.MeterDto;
import de.ph87.data.websocket.IWebsocketMessage;
@@ -36,6 +37,7 @@ public abstract class DeltaDto implements IWebsocketMessage {
@NonNull
@Override
+ @JsonIgnore
public String getWebsocketTopic() {
return "Delta/%d/%s".formatted(meter.series.id, interval);
}
diff --git a/src/main/java/de/ph87/data/series/data/delta/DeltaService.java b/src/main/java/de/ph87/data/series/data/delta/DeltaService.java
index 52d4b39..02fc31a 100644
--- a/src/main/java/de/ph87/data/series/data/delta/DeltaService.java
+++ b/src/main/java/de/ph87/data/series/data/delta/DeltaService.java
@@ -1,10 +1,11 @@
package de.ph87.data.series.data.delta;
-import de.ph87.data.series.point.ISeriesPointRequest;
import de.ph87.data.series.Series;
+import de.ph87.data.series.SeriesService;
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 de.ph87.data.series.data.delta.meter.MeterRepository;
+import de.ph87.data.series.point.ISeriesPointRequest;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -21,6 +22,12 @@ import java.util.function.BiFunction;
@RequiredArgsConstructor
public class DeltaService {
+ private final ApplicationEventPublisher applicationEventPublisher;
+
+ private final SeriesService seriesService;
+
+ private final MeterRepository meterRepository;
+
private final DeltaRepoFive five;
private final DeltaRepoHour hour;
@@ -33,13 +40,10 @@ public class DeltaService {
private final DeltaRepoYear year;
- private final ApplicationEventPublisher applicationEventPublisher;
-
- private final MeterService meterService;
-
@Transactional
- 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);
+ public void write(final long seriesId, @NonNull final String meterNumber, @NonNull final ZonedDateTime date, final double value) {
+ final Series series = seriesService.update(seriesId, date, value);
+ final Meter meter = getOrCreateMeter(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);
@@ -55,6 +59,18 @@ public class DeltaService {
applicationEventPublisher.publishEvent(toDto.apply(delta, interval));
}
+ @NonNull
+ private Meter getOrCreateMeter(@NonNull final Series series, @NonNull final String number, @NonNull final ZonedDateTime date) {
+ return meterRepository
+ .findFirstBySeriesOrderByFirstDesc(series)
+ .filter(meter -> meter.getNumber().equals(number))
+ .orElseGet(() -> {
+ final Meter created = meterRepository.save(new Meter(series, number, date));
+ log.info("Meter created: {}", created);
+ return created;
+ });
+ }
+
@NonNull
public List points(@NonNull final Series series, @NonNull final ISeriesPointRequest request) {
return switch (request.getInterval()) {
diff --git a/src/main/java/de/ph87/data/series/data/delta/meter/Meter.java b/src/main/java/de/ph87/data/series/data/delta/meter/Meter.java
index 64e5ab9..0445266 100644
--- a/src/main/java/de/ph87/data/series/data/delta/meter/Meter.java
+++ b/src/main/java/de/ph87/data/series/data/delta/meter/Meter.java
@@ -29,6 +29,7 @@ public class Meter {
private long version;
@NonNull
+ @ToString.Exclude
@ManyToOne(optional = false)
private Series series;
@@ -36,6 +37,12 @@ public class Meter {
@Column(nullable = false)
private String number;
+ @NonNull
+ @ToString.Include
+ public String series() {
+ return series.toShortString();
+ }
+
@NonNull
@Column(nullable = false)
private ZonedDateTime first;
diff --git a/src/main/java/de/ph87/data/series/data/delta/meter/MeterService.java b/src/main/java/de/ph87/data/series/data/delta/meter/MeterService.java
deleted file mode 100644
index 4c49e15..0000000
--- a/src/main/java/de/ph87/data/series/data/delta/meter/MeterService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-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)));
- }
-
-}
diff --git a/src/main/java/de/ph87/data/series/data/varying/VaryingDto.java b/src/main/java/de/ph87/data/series/data/varying/VaryingDto.java
index 1017cbf..aa600d0 100644
--- a/src/main/java/de/ph87/data/series/data/varying/VaryingDto.java
+++ b/src/main/java/de/ph87/data/series/data/varying/VaryingDto.java
@@ -1,5 +1,6 @@
package de.ph87.data.series.data.varying;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import de.ph87.data.series.SeriesDto;
import de.ph87.data.series.data.Interval;
import de.ph87.data.websocket.IWebsocketMessage;
@@ -42,6 +43,7 @@ public abstract class VaryingDto implements IWebsocketMessage {
@NonNull
@Override
+ @JsonIgnore
public String getWebsocketTopic() {
return "Varying/%d/%s".formatted(series.id, interval);
}
diff --git a/src/main/java/de/ph87/data/series/data/varying/VaryingService.java b/src/main/java/de/ph87/data/series/data/varying/VaryingService.java
index 72ab50b..2751685 100644
--- a/src/main/java/de/ph87/data/series/data/varying/VaryingService.java
+++ b/src/main/java/de/ph87/data/series/data/varying/VaryingService.java
@@ -1,9 +1,10 @@
package de.ph87.data.series.data.varying;
-import de.ph87.data.series.point.ISeriesPointRequest;
import de.ph87.data.series.Series;
+import de.ph87.data.series.SeriesService;
import de.ph87.data.series.data.DataId;
import de.ph87.data.series.data.Interval;
+import de.ph87.data.series.point.ISeriesPointRequest;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -20,6 +21,10 @@ import java.util.function.BiFunction;
@RequiredArgsConstructor
public class VaryingService {
+ private final ApplicationEventPublisher applicationEventPublisher;
+
+ private final SeriesService seriesService;
+
private final VaryingRepoFive five;
private final VaryingRepoHour hour;
@@ -32,10 +37,9 @@ public class VaryingService {
private final VaryingRepoYear year;
- private final ApplicationEventPublisher applicationEventPublisher;
-
@Transactional
- public void write(@NonNull final Series series, @NonNull final ZonedDateTime date, final double value) {
+ public void write(@NonNull final long seriesId, @NonNull final ZonedDateTime date, final double value) {
+ final Series series = seriesService.update(seriesId, date, value);
write(series, five, Interval.FIVE, date, value, Varying.Five::new, VaryingDto.Five::new);
write(series, hour, Interval.HOUR, date, value, Varying.Hour::new, VaryingDto.Hour::new);
write(series, day, Interval.DAY, date, value, Varying.Day::new, VaryingDto.Day::new);
diff --git a/src/main/java/de/ph87/data/series/point/SeriesPointService.java b/src/main/java/de/ph87/data/series/point/SeriesPointService.java
index 2d3311d..3eb86c8 100644
--- a/src/main/java/de/ph87/data/series/point/SeriesPointService.java
+++ b/src/main/java/de/ph87/data/series/point/SeriesPointService.java
@@ -2,7 +2,7 @@ package de.ph87.data.series.point;
import de.ph87.data.series.Series;
import de.ph87.data.series.SeriesDto;
-import de.ph87.data.series.SeriesRepository;
+import de.ph87.data.series.SeriesService;
import de.ph87.data.series.data.bool.BoolService;
import de.ph87.data.series.data.delta.DeltaService;
import de.ph87.data.series.data.varying.VaryingService;
@@ -10,9 +10,7 @@ import jakarta.annotation.Nullable;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
-import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@@ -21,20 +19,20 @@ import java.util.List;
@RequiredArgsConstructor
public class SeriesPointService {
- private final SeriesRepository seriesRepository;
-
private final BoolService boolService;
private final DeltaService deltaService;
private final VaryingService varyingService;
+ private final SeriesService seriesService;
+
@NonNull
public OneSeriesPointsResponse oneSeriesPoints(@NonNull final OneSeriesPointsRequest request) {
- final Series series1 = seriesRepository.findById(request.id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ final Series series1 = seriesService.getById(request.id);
final List extends SeriesPoint>> points1 = getSeriesPoints(series1, request, request.factor);
if (request.id2 != null) {
- final Series series2 = seriesRepository.findById(request.id2).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ final Series series2 = seriesService.getById(request.id2);
final List extends SeriesPoint>> points2 = getSeriesPoints(series2, request, request.factor2);
return new OneSeriesPointsResponse(SeriesPoint.combine(points1, points2, request.operation));
}
@@ -43,7 +41,7 @@ public class SeriesPointService {
@NonNull
public AllSeriesPointResponse allSeriesPoint(@NonNull final AllSeriesPointRequest request) {
- final List seriesPoints = seriesRepository.findAll().stream().map(series -> map(series, request)).toList();
+ final List seriesPoints = seriesService.findAll().stream().map(series -> map(series, request)).toList();
return new AllSeriesPointResponse(request, seriesPoints);
}
diff --git a/src/main/java/de/ph87/data/topic/ITopicParser.java b/src/main/java/de/ph87/data/topic/ITopicParser.java
new file mode 100644
index 0000000..9da9f62
--- /dev/null
+++ b/src/main/java/de/ph87/data/topic/ITopicParser.java
@@ -0,0 +1,10 @@
+package de.ph87.data.topic;
+
+import de.ph87.data.mqtt.MqttMessage;
+import lombok.NonNull;
+
+public interface ITopicParser {
+
+ void handle(@NonNull final TopicDto topic, @NonNull final MqttMessage message);
+
+}
diff --git a/src/main/java/de/ph87/data/topic/Topic.java b/src/main/java/de/ph87/data/topic/Topic.java
index 0b6bd5e..21d2f7d 100644
--- a/src/main/java/de/ph87/data/topic/Topic.java
+++ b/src/main/java/de/ph87/data/topic/Topic.java
@@ -1,14 +1,20 @@
package de.ph87.data.topic;
+import de.ph87.data.series.Series;
+import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
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.Setter;
import lombok.ToString;
@Entity
@@ -24,8 +30,43 @@ public class Topic {
@Version
private long version;
+ @Setter
@NonNull
@Column(nullable = false, unique = true)
private String name;
+ @Setter
+ @Column(nullable = false)
+ private boolean enabled;
+
+ @Setter
+ @NonNull
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false, columnDefinition = "varchar(100)")
+ private TopicType type = TopicType.PatrixOpenDtu;
+
+ @Setter
+ @Nullable
+ @ManyToOne
+ private Series series0;
+
+ @Setter
+ @Nullable
+ @ManyToOne
+ private Series series1;
+
+ @Setter
+ @Nullable
+ @ManyToOne
+ private Series series2;
+
+ @Setter
+ @Nullable
+ @ManyToOne
+ private Series series3;
+
+ public Topic(@NonNull final String name) {
+ this.name = name;
+ }
+
}
diff --git a/src/main/java/de/ph87/data/topic/TopicController.java b/src/main/java/de/ph87/data/topic/TopicController.java
new file mode 100644
index 0000000..46cb4d8
--- /dev/null
+++ b/src/main/java/de/ph87/data/topic/TopicController.java
@@ -0,0 +1,58 @@
+package de.ph87.data.topic;
+
+import jakarta.annotation.Nullable;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@CrossOrigin
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("Topic")
+public class TopicController {
+
+ private final TopicService topicService;
+
+ @GetMapping("create")
+ public TopicDto create() {
+ return topicService.create();
+ }
+
+ @PostMapping("{id}/name")
+ public TopicDto name(@PathVariable final long id, @RequestBody @NonNull final String name) {
+ return topicService.modify(id, topic -> topic.setName(name));
+ }
+
+ @PostMapping("{id}/enabled")
+ public TopicDto enabled(@PathVariable final long id, @RequestBody @NonNull final boolean enabled) {
+ return topicService.modify(id, topic -> topic.setEnabled(enabled));
+ }
+
+ @PostMapping("{id}/type")
+ public TopicDto type(@PathVariable final long id, @RequestBody @NonNull final String typeName) {
+ final TopicType type = TopicType.valueOf(typeName);
+ return topicService.modify(id, topic -> topic.setType(type));
+ }
+
+ @PostMapping("{id}/series0")
+ public TopicDto series0(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long series0) {
+ return topicService.modifySeries(id, series0, Topic::setSeries0);
+ }
+
+ @PostMapping("{id}/series1")
+ public TopicDto series1(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long series1) {
+ return topicService.modifySeries(id, series1, Topic::setSeries1);
+ }
+
+ @PostMapping("{id}/series2")
+ public TopicDto series2(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long series2) {
+ return topicService.modifySeries(id, series2, Topic::setSeries2);
+ }
+
+}
diff --git a/src/main/java/de/ph87/data/topic/TopicDto.java b/src/main/java/de/ph87/data/topic/TopicDto.java
index 2c233cd..f6e73c4 100644
--- a/src/main/java/de/ph87/data/topic/TopicDto.java
+++ b/src/main/java/de/ph87/data/topic/TopicDto.java
@@ -1,9 +1,13 @@
package de.ph87.data.topic;
+import de.ph87.data.series.SeriesDto;
import de.ph87.data.websocket.IWebsocketMessage;
+import jakarta.annotation.Nullable;
import lombok.Data;
import lombok.NonNull;
+import static de.ph87.data.Helpers.map;
+
@Data
public class TopicDto implements IWebsocketMessage {
@@ -12,9 +16,29 @@ public class TopicDto implements IWebsocketMessage {
@NonNull
public final String name;
+ @NonNull
+ public final TopicType type;
+
+ @Nullable
+ public final SeriesDto series0;
+
+ @Nullable
+ public final SeriesDto series1;
+
+ @Nullable
+ public final SeriesDto series2;
+
+ @Nullable
+ public final SeriesDto series3;
+
public TopicDto(@NonNull final Topic topic) {
this.id = topic.getId();
this.name = topic.getName();
+ this.type = topic.getType();
+ this.series0 = map(topic.getSeries0(), SeriesDto::new);
+ this.series1 = map(topic.getSeries1(), SeriesDto::new);
+ this.series2 = map(topic.getSeries2(), SeriesDto::new);
+ this.series3 = map(topic.getSeries3(), SeriesDto::new);
}
}
diff --git a/src/main/java/de/ph87/data/topic/TopicParserAbstract.java b/src/main/java/de/ph87/data/topic/TopicParserAbstract.java
new file mode 100644
index 0000000..5b1a16b
--- /dev/null
+++ b/src/main/java/de/ph87/data/topic/TopicParserAbstract.java
@@ -0,0 +1,45 @@
+package de.ph87.data.topic;
+
+import de.ph87.data.mqtt.MqttMessage;
+import de.ph87.data.series.SeriesDto;
+import de.ph87.data.series.data.delta.DeltaService;
+import de.ph87.data.series.data.varying.VaryingService;
+import jakarta.annotation.Nullable;
+import lombok.Data;
+import lombok.NonNull;
+import tools.jackson.databind.ObjectMapper;
+
+import java.time.ZonedDateTime;
+
+@Data
+public abstract class TopicParserAbstract implements ITopicParser {
+
+ protected final Class clazz;
+
+ protected final ObjectMapper mapper;
+
+ protected final DeltaService deltaService;
+
+ protected final VaryingService varyingService;
+
+ @Override
+ public void handle(@NonNull final TopicDto topic, @NonNull final MqttMessage message) {
+ final INBOUND inbound = mapper.readValue(message.getPayload(), clazz);
+ handle2(topic, inbound);
+ }
+
+ protected abstract void handle2(@NonNull final TopicDto topic, @NonNull final INBOUND inbound);
+
+ protected void delta(@Nullable final SeriesDto series, @NonNull final String meter, @NonNull final ZonedDateTime date, final double value) {
+ if (series != null) {
+ deltaService.write(series.id, meter, date, value);
+ }
+ }
+
+ protected void varying(@Nullable final SeriesDto series, @NonNull final ZonedDateTime date, final double value) {
+ if (series != null) {
+ varyingService.write(series.id, date, value);
+ }
+ }
+
+}
diff --git a/src/main/java/de/ph87/data/topic/TopicReceiver.java b/src/main/java/de/ph87/data/topic/TopicReceiver.java
index 59d1f38..40e098a 100644
--- a/src/main/java/de/ph87/data/topic/TopicReceiver.java
+++ b/src/main/java/de/ph87/data/topic/TopicReceiver.java
@@ -7,14 +7,71 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Optional;
+import java.util.Set;
+
@Slf4j
@Service
@RequiredArgsConstructor
public class TopicReceiver {
- @EventListener(MqttMessage.class)
- public void receive(@NonNull final MqttMessage mqttMessage) {
+ private final TopicRepository topicRepository;
+ private final Set parsers;
+
+ @EventListener(MqttMessage.class)
+ public void receive(@NonNull final MqttMessage message) {
+ try {
+ receive2(message);
+ } catch (Exception e) {
+ log.error("Failed to handle message:");
+ log.error(" message: {}", message);
+ log.error(" error: {}", e.toString().replaceAll("\r*\n\r*", " "));
+ log.error(" stacktrace:\n{}", stackTraceToString(e));
+ }
+ }
+
+ private void receive2(final MqttMessage message) {
+ final Optional topicOptional = topicRepository.findDtoByEnabledTrueAndName(message.getTopic());
+ if (topicOptional.isEmpty()) {
+ return;
+ }
+ final TopicDto topic = topicOptional.get();
+ final Optional parserOptional = parsers.stream().filter(p -> p.getClass() == topic.type.getClazz()).findFirst();
+ if (parserOptional.isEmpty()) {
+ log.error("No parser found:");
+ log.error(" message: {}", message);
+ log.error(" topic: {}", topic);
+ log.error(" type: {}", topic.type);
+ return;
+ }
+ final ITopicParser parser = parserOptional.get();
+ handle(message, parser, topic);
+ }
+
+ private static void handle(@NonNull final MqttMessage message, @NonNull final ITopicParser parser, @NonNull final TopicDto topic) {
+ try {
+ parser.handle(topic, message);
+ } catch (Exception e) {
+ log.error("Failed to parse message:");
+ log.error(" message: {}", message);
+ log.error(" topic: {}", topic);
+ log.error(" parser: {}", parser.getClass().getSimpleName());
+ log.error(" error: {}", e.toString().replaceAll("\r*\n\r*", " "));
+ if (log.isDebugEnabled()) {
+ log.debug(stackTraceToString(e));
+ }
+ }
+ }
+
+ @NonNull
+ public static String stackTraceToString(@NonNull final Throwable throwable) {
+ final StringWriter stringWriter = new StringWriter();
+ final PrintWriter printWriter = new PrintWriter(stringWriter);
+ throwable.printStackTrace(printWriter);
+ return stringWriter.toString();
}
}
diff --git a/src/main/java/de/ph87/data/topic/TopicRepository.java b/src/main/java/de/ph87/data/topic/TopicRepository.java
index c325c77..1e22d70 100644
--- a/src/main/java/de/ph87/data/topic/TopicRepository.java
+++ b/src/main/java/de/ph87/data/topic/TopicRepository.java
@@ -4,15 +4,13 @@ import lombok.NonNull;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.ListCrudRepository;
-import java.util.List;
import java.util.Optional;
public interface TopicRepository extends ListCrudRepository {
- @NonNull
- Optional findByName(@NonNull String name);
+ boolean existsByName(@NonNull String name);
- @Query("select new de.ph87.data.topic.TopicDto(t) from Topic t")
- List findAllDto();
+ @Query("select new de.ph87.data.topic.TopicDto(t) from Topic t where t.name = :name")
+ Optional findDtoByEnabledTrueAndName(@NonNull String name);
}
diff --git a/src/main/java/de/ph87/data/topic/TopicService.java b/src/main/java/de/ph87/data/topic/TopicService.java
new file mode 100644
index 0000000..43e5eb6
--- /dev/null
+++ b/src/main/java/de/ph87/data/topic/TopicService.java
@@ -0,0 +1,72 @@
+package de.ph87.data.topic;
+
+import de.ph87.data.common.CrudAction;
+import de.ph87.data.series.Series;
+import de.ph87.data.series.SeriesService;
+import jakarta.annotation.Nullable;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import static de.ph87.data.location.NotFoundException.notFound;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class TopicService {
+
+ private final TopicRepository topicRepository;
+
+ private final SeriesService seriesService;
+
+ @NonNull
+ @Transactional
+ public TopicDto create() {
+ final String name = _generateUniqueName();
+ return publish(topicRepository.save(new Topic(name)), CrudAction.CREATED);
+ }
+
+ @NonNull
+ private String _generateUniqueName() {
+ int index = 0;
+ while (true) {
+ final String name = "topic/unnamed/" + index++;
+ if (!topicRepository.existsByName(name)) {
+ return name;
+ }
+ }
+ }
+
+ @NonNull
+ @Transactional
+ public TopicDto modify(final long id, @NonNull final Consumer modifier) {
+ final Topic topic = getById(id);
+ modifier.accept(topic);
+ return publish(topic, CrudAction.MODIFIED);
+ }
+
+ @NonNull
+ @Transactional
+ public TopicDto modifySeries(final long topicId, @Nullable final Long seriesId, @NonNull final BiConsumer modifier) {
+ final Series series = seriesId == null ? null : seriesService.getById(seriesId);
+ return modify(topicId, topic -> modifier.accept(topic, series));
+ }
+
+ @NonNull
+ private Topic getById(final long id) {
+ return topicRepository.findById(id).orElseThrow(notFound(Topic.class, "id", id));
+ }
+
+ @NonNull
+ private TopicDto publish(@NonNull final Topic topic, @NonNull final CrudAction action) {
+ final TopicDto dto = new TopicDto(topic);
+ log.info("{} {}: {}", Topic.class.getSimpleName(), action, dto);
+ return dto;
+ }
+
+}
diff --git a/src/main/java/de/ph87/data/topic/TopicType.java b/src/main/java/de/ph87/data/topic/TopicType.java
new file mode 100644
index 0000000..2f27d13
--- /dev/null
+++ b/src/main/java/de/ph87/data/topic/TopicType.java
@@ -0,0 +1,23 @@
+package de.ph87.data.topic;
+
+import de.ph87.data.topic.parser.PatrixOpenDtu;
+import de.ph87.data.topic.parser.ShellyPlus1PM;
+import de.ph87.data.topic.parser.PatrixSmartMeter;
+import de.ph87.data.topic.parser.TasmotaSmartMeter;
+import lombok.Getter;
+
+@Getter
+public enum TopicType {
+ PatrixOpenDtu(PatrixOpenDtu.class),
+ PatrixSmartMeter(PatrixSmartMeter.class),
+ TasmotaSmartMeter(TasmotaSmartMeter.class),
+ ShellyPlus1PM(ShellyPlus1PM.class),
+ ;
+
+ public final Class extends ITopicParser> clazz;
+
+ TopicType(final Class extends ITopicParser> clazz) {
+ this.clazz = clazz;
+ }
+
+}
diff --git a/src/main/java/de/ph87/data/topic/parser/PatrixOpenDtu.java b/src/main/java/de/ph87/data/topic/parser/PatrixOpenDtu.java
new file mode 100644
index 0000000..863ea74
--- /dev/null
+++ b/src/main/java/de/ph87/data/topic/parser/PatrixOpenDtu.java
@@ -0,0 +1,50 @@
+package de.ph87.data.topic.parser;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import de.ph87.data.series.data.delta.DeltaService;
+import de.ph87.data.series.data.varying.VaryingService;
+import de.ph87.data.topic.TopicDto;
+import de.ph87.data.topic.TopicParserAbstract;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import tools.jackson.databind.ObjectMapper;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+@Slf4j
+@Service
+public class PatrixOpenDtu extends TopicParserAbstract {
+
+ public PatrixOpenDtu(@NonNull final ObjectMapper mapper, @NonNull final DeltaService deltaService, @NonNull final VaryingService varyingService) {
+ super(Dto.class, mapper, deltaService, varyingService);
+ }
+
+ @Override
+ protected void handle2(@NonNull final TopicDto topic, @NonNull final Dto dto) {
+ delta(topic.series0, dto.meter, dto.date, dto.energyKWh);
+ varying(topic.series1, dto.date, dto.powerW);
+ }
+
+ public static class Dto {
+
+ public final String meter;
+
+ public final ZonedDateTime date;
+
+ public final double energyKWh;
+
+ public final double powerW;
+
+ public Dto(@JsonProperty(value = "inverter", required = true) @NonNull final String inverter, @JsonProperty(value = "timestamp", required = true) @NonNull final long epochSeconds, @JsonProperty(value = "totalKWh", required = true) final double energyKWh, @JsonProperty(value = "totalW", required = true) final double powerW) {
+ this.meter = inverter;
+ this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSeconds), ZoneId.systemDefault());
+ this.energyKWh = energyKWh;
+ this.powerW = powerW;
+ }
+
+ }
+
+}
diff --git a/src/main/java/de/ph87/data/topic/parser/PatrixSmartMeter.java b/src/main/java/de/ph87/data/topic/parser/PatrixSmartMeter.java
new file mode 100644
index 0000000..e990eca
--- /dev/null
+++ b/src/main/java/de/ph87/data/topic/parser/PatrixSmartMeter.java
@@ -0,0 +1,60 @@
+package de.ph87.data.topic.parser;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import de.ph87.data.series.data.delta.DeltaService;
+import de.ph87.data.series.data.varying.VaryingService;
+import de.ph87.data.topic.TopicDto;
+import de.ph87.data.topic.TopicParserAbstract;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import tools.jackson.databind.ObjectMapper;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+@Slf4j
+@Service
+public class PatrixSmartMeter extends TopicParserAbstract {
+
+ public PatrixSmartMeter(@NonNull final ObjectMapper mapper, @NonNull final DeltaService deltaService, @NonNull final VaryingService varyingService) {
+ super(Dto.class, mapper, deltaService, varyingService);
+ }
+
+ @Override
+ protected void handle2(final @NonNull TopicDto topic, @NonNull final Dto dto) {
+ delta(topic.series0, dto.meter, dto.date, dto.purchaseKWh);
+ delta(topic.series1, dto.meter, dto.date, dto.deliverKWh);
+ varying(topic.series2, dto.date, Math.max(0, dto.powerW));
+ varying(topic.series3, dto.date, Math.max(0, -dto.powerW));
+ }
+
+ public static class Dto {
+
+ public final String meter;
+
+ public final ZonedDateTime date;
+
+ public final double purchaseKWh;
+
+ public final double deliverKWh;
+
+ public final double powerW;
+
+ public Dto(
+ @JsonProperty(value = "timestamp", required = true) @NonNull final long epochSeconds,
+ @JsonProperty(value = "purchaseWh", required = true) final double purchaseWh,
+ @JsonProperty(value = "deliveryWh", required = true) final double deliverWh,
+ @JsonProperty(value = "powerW", required = true) final double powerW
+ ) {
+ this.meter = "1ZPA0020300305";
+ this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSeconds), ZoneId.systemDefault());
+ this.purchaseKWh = purchaseWh / 1000;
+ this.deliverKWh = deliverWh / 1000;
+ this.powerW = powerW;
+ }
+
+ }
+
+}
diff --git a/src/main/java/de/ph87/data/topic/parser/ShellyPlus1PM.java b/src/main/java/de/ph87/data/topic/parser/ShellyPlus1PM.java
new file mode 100644
index 0000000..f21ab14
--- /dev/null
+++ b/src/main/java/de/ph87/data/topic/parser/ShellyPlus1PM.java
@@ -0,0 +1,112 @@
+package de.ph87.data.topic.parser;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import de.ph87.data.series.data.delta.DeltaService;
+import de.ph87.data.series.data.varying.VaryingService;
+import de.ph87.data.topic.TopicDto;
+import de.ph87.data.topic.TopicParserAbstract;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import tools.jackson.databind.ObjectMapper;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.List;
+
+@Slf4j
+@Service
+public class ShellyPlus1PM extends TopicParserAbstract {
+
+ public ShellyPlus1PM(@NonNull final ObjectMapper mapper, @NonNull final DeltaService deltaService, @NonNull final VaryingService varyingService) {
+ super(Dto.class, mapper, deltaService, varyingService);
+ }
+
+ @Override
+ protected void handle2(@NonNull final TopicDto topic, @NonNull final Dto dto) {
+ delta(topic.series0, "ShellyPlus1PM", dto.energy.date, dto.energy.totalKWh);
+ varying(topic.series1, dto.energy.date, dto.powerW);
+ }
+
+ @Data
+ public static class Dto {
+
+ public final int id;
+
+ public final String source;
+
+ public final boolean relayState;
+
+ public final double powerW;
+
+ public final double voltage;
+
+ public final double current;
+
+ public final Energy energy;
+
+ public final Temperature temperature;
+
+ public Dto(
+ @JsonProperty("id") int id,
+ @JsonProperty("source") String source,
+ @JsonProperty("output") boolean relayState,
+ @JsonProperty("apower") double powerW,
+ @JsonProperty("voltage") double voltageV,
+ @JsonProperty("current") double currentA,
+ @JsonProperty("aenergy") Energy energy,
+ @JsonProperty("temperature") Temperature temperature
+ ) {
+ this.id = id;
+ this.source = source;
+ this.relayState = relayState;
+ this.powerW = powerW;
+ this.voltage = voltageV;
+ this.current = currentA;
+ this.energy = energy;
+ this.temperature = temperature;
+ }
+
+ @Data
+ public static class Energy {
+
+ public final double totalKWh;
+
+ public final List byMinute;
+
+ public final ZonedDateTime date;
+
+ public Energy(
+ @JsonProperty("total") double totalWh,
+ @JsonProperty("by_minute") List byMinute,
+ @JsonProperty("minute_ts") long epochSeconds
+ ) {
+ this.totalKWh = totalWh / 1000;
+ this.byMinute = byMinute;
+ this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSeconds), ZoneId.systemDefault());
+ }
+
+ }
+
+ @Data
+ public static class Temperature {
+
+ public final double tC;
+
+ public final double tF;
+
+ public Temperature(
+ @JsonProperty("tC") double tC,
+ @JsonProperty("tF") double tF
+ ) {
+ this.tC = tC;
+ this.tF = tF;
+ }
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/de/ph87/data/topic/parser/TasmotaSmartMeter.java b/src/main/java/de/ph87/data/topic/parser/TasmotaSmartMeter.java
new file mode 100644
index 0000000..d28e98d
--- /dev/null
+++ b/src/main/java/de/ph87/data/topic/parser/TasmotaSmartMeter.java
@@ -0,0 +1,94 @@
+package de.ph87.data.topic.parser;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import de.ph87.data.series.data.delta.DeltaService;
+import de.ph87.data.series.data.varying.VaryingService;
+import de.ph87.data.topic.TopicDto;
+import de.ph87.data.topic.TopicParserAbstract;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import tools.jackson.databind.ObjectMapper;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+@Slf4j
+@Service
+public class TasmotaSmartMeter extends TopicParserAbstract {
+
+ public TasmotaSmartMeter(@NonNull final ObjectMapper mapper, @NonNull final DeltaService deltaService, @NonNull final VaryingService varyingService) {
+ super(Dto.class, mapper, deltaService, varyingService);
+ }
+
+ @Override
+ protected void handle2(final @NonNull TopicDto topic, @NonNull final Dto dto) {
+ delta(topic.series0, dto.meter.number, dto.date, dto.meter.purchaseKWh);
+ delta(topic.series1, dto.meter.number, dto.date, dto.meter.deliverKWh);
+ varying(topic.series2, dto.date, Math.max(0, dto.meter.powerW));
+ varying(topic.series3, dto.date, Math.max(0, -dto.meter.powerW));
+ }
+
+ public static class Dto {
+
+ @NonNull
+ public final ZonedDateTime date;
+
+ @NonNull
+ public final Meter meter;
+
+ public Dto(
+ @JsonProperty(value = "Time", required = true) @NonNull final String localDateTimeString,
+ @JsonProperty(value = "meter", required = true) @NonNull final Meter meter
+ ) {
+ this.date = ZonedDateTime.of(LocalDateTime.parse(localDateTimeString), ZoneId.systemDefault());
+ this.meter = meter;
+ }
+
+ public static class Meter {
+
+ @NonNull
+ public final String number;
+
+ public final double purchaseKWh;
+
+ public final double deliverKWh;
+
+ public final double powerW;
+
+ public Meter(
+ @JsonProperty(value = "number", required = true) @NonNull final String number,
+ @JsonProperty(value = "energy_purchased_kwh", required = true) final double purchaseWh,
+ @JsonProperty(value = "energy_delivered_kwh", required = true) final double deliverWh,
+ @JsonProperty(value = "power_w", required = true) final double powerW
+ ) {
+ this.number = parseMeterNumber(number);
+ this.purchaseKWh = purchaseWh;
+ this.deliverKWh = deliverWh;
+ this.powerW = powerW;
+ }
+
+ @NonNull
+ private static String parseMeterNumber(@NonNull final String input) {
+ if (input.isEmpty()) {
+ throw new NumberFormatException("Cannot parse Meter number: No Hex-chars read.");
+ }
+ if (input.length() % 2 != 0) {
+ throw new NumberFormatException("Cannot parse Meter number: Hex-char count must be multiple of 2.");
+ }
+ final int length = Integer.parseInt(input.substring(0, 2), 16);
+ if (input.length() != length * 2) {
+ throw new NumberFormatException("Cannot parse Meter number: Invalid length");
+ }
+ final int type = Integer.parseInt(input.substring(2, 4), 16);
+ final String name = "" + (char) Integer.parseInt(input.substring(4, 6), 16) + (char) Integer.parseInt(input.substring(6, 8), 16) + (char) Integer.parseInt(input.substring(8, 10), 16);
+ final int number = Integer.parseInt(input.substring(10), 16);
+ return "%d%s%s".formatted(type, name, number);
+ }
+
+ }
+
+ }
+
+}