new topic parsers: PatrixOpenDtu, PatrixSmartMeter, ShellyPlus1PM, TasmotaSmartMeter
This commit is contained in:
parent
278fe60906
commit
2ad140589c
@ -8,10 +8,12 @@ export class Location {
|
||||
readonly name: string,
|
||||
readonly latitude: number,
|
||||
readonly longitude: number,
|
||||
readonly purchase: Series | null,
|
||||
readonly delivery: Series | null,
|
||||
readonly produce: Series | null,
|
||||
readonly power: Series | null,
|
||||
readonly energyPurchase: Series | null,
|
||||
readonly energyDeliver: Series | null,
|
||||
readonly energyProduce: Series | null,
|
||||
readonly powerPurchase: Series | null,
|
||||
readonly powerDeliver: Series | null,
|
||||
readonly powerProduce: Series | null,
|
||||
) {
|
||||
//
|
||||
}
|
||||
@ -22,10 +24,12 @@ export class Location {
|
||||
validateString(json.name),
|
||||
validateNumber(json.latitude),
|
||||
validateNumber(json.longitude),
|
||||
or(json.purchase, Series.fromJson, null),
|
||||
or(json.delivery, Series.fromJson, null),
|
||||
or(json.produce, Series.fromJson, null),
|
||||
or(json.power, Series.fromJson, null),
|
||||
or(json.energyPurchase, Series.fromJson, null),
|
||||
or(json.energyDeliver, Series.fromJson, null),
|
||||
or(json.energyProduce, Series.fromJson, null),
|
||||
or(json.powerPurchase, Series.fromJson, null),
|
||||
or(json.powerDeliver, Series.fromJson, null),
|
||||
or(json.powerProduce, Series.fromJson, null),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
<app-text [initial]="location.name" (onChange)="locationService.name(location, $event, update)"></app-text>
|
||||
<app-number [initial]="location.latitude" (onChange)="locationService.latitude(location, $event, update)" unit="°"></app-number>
|
||||
<app-number [initial]="location.longitude" (onChange)="locationService.longitude(location, $event, update)" unit="°"></app-number>
|
||||
<app-series-select [initial]="location.purchase" (onChange)="locationService.purchase(location, $event, update)" [list]="filterEnergy()"></app-series-select>
|
||||
<app-series-select [initial]="location.delivery" (onChange)="locationService.delivery(location, $event, update)" [list]="filterEnergy()"></app-series-select>
|
||||
<app-series-select [initial]="location.produce" (onChange)="locationService.produce(location, $event, update)" [list]="filterEnergy()"></app-series-select>
|
||||
<app-series-select [initial]="location.power" (onChange)="locationService.power(location, $event, update)" [list]="filterPower()"></app-series-select>
|
||||
<app-series-select [initial]="location.energyPurchase" (onChange)="locationService.energyPurchase(location, $event, update)" [list]="filterEnergy()"></app-series-select>
|
||||
<app-series-select [initial]="location.energyDeliver" (onChange)="locationService.energyDeliver(location, $event, update)" [list]="filterEnergy()"></app-series-select>
|
||||
<app-series-select [initial]="location.energyProduce" (onChange)="locationService.energyProduce(location, $event, update)" [list]="filterEnergy()"></app-series-select>
|
||||
<app-series-select [initial]="location.powerPurchase" (onChange)="locationService.powerPurchase(location, $event, update)" [list]="filterPower()"></app-series-select>
|
||||
<app-series-select [initial]="location.powerDeliver" (onChange)="locationService.powerDeliver(location, $event, update)" [list]="filterPower()"></app-series-select>
|
||||
<app-series-select [initial]="location.powerProduce" (onChange)="locationService.powerProduce(location, $event, update)" [list]="filterPower()"></app-series-select>
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiService, CrudService, Next, WebsocketService} from '../common';
|
||||
import {Location} from './Location'
|
||||
import {Series} from '../series/Series';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -32,20 +31,28 @@ export class LocationService extends CrudService<Location> {
|
||||
this.postSingle([location.id, 'longitude'], longitude, next);
|
||||
}
|
||||
|
||||
purchase(location: Location, purchase: Series | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'purchase'], purchase?.id, next);
|
||||
energyPurchase(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'energyPurchase'], seriesId, next);
|
||||
}
|
||||
|
||||
delivery(location: Location, delivery: Series | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'delivery'], delivery?.id, next);
|
||||
energyDeliver(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'energyDeliver'], seriesId, next);
|
||||
}
|
||||
|
||||
produce(location: Location, produce: Series | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'produce'], produce?.id, next);
|
||||
energyProduce(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'energyProduce'], seriesId, next);
|
||||
}
|
||||
|
||||
power(location: Location, power: Series | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'power'], power?.id, next);
|
||||
powerPurchase(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'powerPurchase'], seriesId, next);
|
||||
}
|
||||
|
||||
powerDeliver(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'powerDeliver'], seriesId, next);
|
||||
}
|
||||
|
||||
powerProduce(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||
this.postSingle([location.id, 'powerProduce'], seriesId, next);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -4,24 +4,18 @@
|
||||
(mouseenter)="showPen = true"
|
||||
(mouseleave)="showPen = false"
|
||||
>
|
||||
<div class="value" (click)="start()">
|
||||
@if (editing) {
|
||||
<select
|
||||
#input
|
||||
[(ngModel)]="model"
|
||||
(blur)="apply()"
|
||||
(keydown.enter)="apply()"
|
||||
(keydown.escape)="cancel()"
|
||||
>
|
||||
@for (series of list; track series.id) {
|
||||
<option [ngValue]="series">{{ series.name }}</option>
|
||||
}
|
||||
</select>
|
||||
} @else {
|
||||
{{ initial?.name || ' ' }}
|
||||
<select
|
||||
#input
|
||||
[(ngModel)]="model"
|
||||
(blur)="apply()"
|
||||
(keydown.enter)="apply()"
|
||||
>
|
||||
<option [ngValue]="null">-</option>
|
||||
@for (series of list; track series.id) {
|
||||
<option [ngValue]="series.id">{{ series.name }}</option>
|
||||
}
|
||||
</div>
|
||||
@if (editing || showPen) {
|
||||
</select>
|
||||
@if (showPen) {
|
||||
<fa-icon [icon]="faPen"></fa-icon>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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<Series | null>();
|
||||
readonly onChange = new EventEmitter<number | null>();
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<Location, Series> 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<Location, Series> 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<Location> setter) {
|
||||
public LocationDto set(final long id, @NonNull final Consumer<Location> setter) {
|
||||
final Location location = locationRepository.findById(id).orElseThrow(notFound(Location.class, "id", id));
|
||||
setter.accept(location);
|
||||
return new LocationDto(location);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<Graph> 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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ public class SeriesController {
|
||||
|
||||
private final SeriesPointService seriesPointService;
|
||||
|
||||
@PostMapping("create")
|
||||
@GetMapping("create")
|
||||
public SeriesDto create() {
|
||||
return seriesService.create();
|
||||
}
|
||||
|
||||
@ -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<Series> modifier) {
|
||||
public SeriesDto modify(final long id, @NonNull Consumer<Series> modifier) {
|
||||
final Series series = getById(id);
|
||||
modifier.accept(series);
|
||||
return publish(series, CrudAction.MODIFIED);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Series getById(final long id) {
|
||||
public List<Series> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<DeltaPoint> points(@NonNull final Series series, @NonNull final ISeriesPointRequest request) {
|
||||
return switch (request.getInterval()) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<AllSeriesPointResponse.Entry> seriesPoints = seriesRepository.findAll().stream().map(series -> map(series, request)).toList();
|
||||
final List<AllSeriesPointResponse.Entry> seriesPoints = seriesService.findAll().stream().map(series -> map(series, request)).toList();
|
||||
return new AllSeriesPointResponse(request, seriesPoints);
|
||||
}
|
||||
|
||||
|
||||
10
src/main/java/de/ph87/data/topic/ITopicParser.java
Normal file
10
src/main/java/de/ph87/data/topic/ITopicParser.java
Normal file
@ -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);
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
58
src/main/java/de/ph87/data/topic/TopicController.java
Normal file
58
src/main/java/de/ph87/data/topic/TopicController.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
45
src/main/java/de/ph87/data/topic/TopicParserAbstract.java
Normal file
45
src/main/java/de/ph87/data/topic/TopicParserAbstract.java
Normal file
@ -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<INBOUND> implements ITopicParser {
|
||||
|
||||
protected final Class<INBOUND> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<ITopicParser> 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<TopicDto> topicOptional = topicRepository.findDtoByEnabledTrueAndName(message.getTopic());
|
||||
if (topicOptional.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final TopicDto topic = topicOptional.get();
|
||||
final Optional<ITopicParser> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<Topic, Long> {
|
||||
|
||||
@NonNull
|
||||
Optional<Topic> findByName(@NonNull String name);
|
||||
boolean existsByName(@NonNull String name);
|
||||
|
||||
@Query("select new de.ph87.data.topic.TopicDto(t) from Topic t")
|
||||
List<TopicDto> findAllDto();
|
||||
@Query("select new de.ph87.data.topic.TopicDto(t) from Topic t where t.name = :name")
|
||||
Optional<TopicDto> findDtoByEnabledTrueAndName(@NonNull String name);
|
||||
|
||||
}
|
||||
|
||||
72
src/main/java/de/ph87/data/topic/TopicService.java
Normal file
72
src/main/java/de/ph87/data/topic/TopicService.java
Normal file
@ -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<Topic> 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<Topic, Series> 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;
|
||||
}
|
||||
|
||||
}
|
||||
23
src/main/java/de/ph87/data/topic/TopicType.java
Normal file
23
src/main/java/de/ph87/data/topic/TopicType.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
50
src/main/java/de/ph87/data/topic/parser/PatrixOpenDtu.java
Normal file
50
src/main/java/de/ph87/data/topic/parser/PatrixOpenDtu.java
Normal file
@ -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<PatrixOpenDtu.Dto> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<PatrixSmartMeter.Dto> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
112
src/main/java/de/ph87/data/topic/parser/ShellyPlus1PM.java
Normal file
112
src/main/java/de/ph87/data/topic/parser/ShellyPlus1PM.java
Normal file
@ -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<ShellyPlus1PM.Dto> {
|
||||
|
||||
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<Double> byMinute;
|
||||
|
||||
public final ZonedDateTime date;
|
||||
|
||||
public Energy(
|
||||
@JsonProperty("total") double totalWh,
|
||||
@JsonProperty("by_minute") List<Double> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<TasmotaSmartMeter.Dto> {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user