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 name: string,
|
||||||
readonly latitude: number,
|
readonly latitude: number,
|
||||||
readonly longitude: number,
|
readonly longitude: number,
|
||||||
readonly purchase: Series | null,
|
readonly energyPurchase: Series | null,
|
||||||
readonly delivery: Series | null,
|
readonly energyDeliver: Series | null,
|
||||||
readonly produce: Series | null,
|
readonly energyProduce: Series | null,
|
||||||
readonly power: Series | null,
|
readonly powerPurchase: Series | null,
|
||||||
|
readonly powerDeliver: Series | null,
|
||||||
|
readonly powerProduce: Series | null,
|
||||||
) {
|
) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@ -22,10 +24,12 @@ export class Location {
|
|||||||
validateString(json.name),
|
validateString(json.name),
|
||||||
validateNumber(json.latitude),
|
validateNumber(json.latitude),
|
||||||
validateNumber(json.longitude),
|
validateNumber(json.longitude),
|
||||||
or(json.purchase, Series.fromJson, null),
|
or(json.energyPurchase, Series.fromJson, null),
|
||||||
or(json.delivery, Series.fromJson, null),
|
or(json.energyDeliver, Series.fromJson, null),
|
||||||
or(json.produce, Series.fromJson, null),
|
or(json.energyProduce, Series.fromJson, null),
|
||||||
or(json.power, 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-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.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-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.energyPurchase" (onChange)="locationService.energyPurchase(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.energyDeliver" (onChange)="locationService.energyDeliver(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.energyProduce" (onChange)="locationService.energyProduce(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.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 {Injectable} from '@angular/core';
|
||||||
import {ApiService, CrudService, Next, WebsocketService} from '../common';
|
import {ApiService, CrudService, Next, WebsocketService} from '../common';
|
||||||
import {Location} from './Location'
|
import {Location} from './Location'
|
||||||
import {Series} from '../series/Series';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -32,20 +31,28 @@ export class LocationService extends CrudService<Location> {
|
|||||||
this.postSingle([location.id, 'longitude'], longitude, next);
|
this.postSingle([location.id, 'longitude'], longitude, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
purchase(location: Location, purchase: Series | null, next?: Next<Location>) {
|
energyPurchase(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||||
this.postSingle([location.id, 'purchase'], purchase?.id, next);
|
this.postSingle([location.id, 'energyPurchase'], seriesId, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
delivery(location: Location, delivery: Series | null, next?: Next<Location>) {
|
energyDeliver(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||||
this.postSingle([location.id, 'delivery'], delivery?.id, next);
|
this.postSingle([location.id, 'energyDeliver'], seriesId, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
produce(location: Location, produce: Series | null, next?: Next<Location>) {
|
energyProduce(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||||
this.postSingle([location.id, 'produce'], produce?.id, next);
|
this.postSingle([location.id, 'energyProduce'], seriesId, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
power(location: Location, power: Series | null, next?: Next<Location>) {
|
powerPurchase(location: Location, seriesId: number | null, next?: Next<Location>) {
|
||||||
this.postSingle([location.id, 'power'], power?.id, next);
|
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"
|
(mouseenter)="showPen = true"
|
||||||
(mouseleave)="showPen = false"
|
(mouseleave)="showPen = false"
|
||||||
>
|
>
|
||||||
<div class="value" (click)="start()">
|
|
||||||
@if (editing) {
|
|
||||||
<select
|
<select
|
||||||
#input
|
#input
|
||||||
[(ngModel)]="model"
|
[(ngModel)]="model"
|
||||||
(blur)="apply()"
|
(blur)="apply()"
|
||||||
(keydown.enter)="apply()"
|
(keydown.enter)="apply()"
|
||||||
(keydown.escape)="cancel()"
|
|
||||||
>
|
>
|
||||||
|
<option [ngValue]="null">-</option>
|
||||||
@for (series of list; track series.id) {
|
@for (series of list; track series.id) {
|
||||||
<option [ngValue]="series">{{ series.name }}</option>
|
<option [ngValue]="series.id">{{ series.name }}</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
} @else {
|
@if (showPen) {
|
||||||
{{ initial?.name || ' ' }}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@if (editing || showPen) {
|
|
||||||
<fa-icon [icon]="faPen"></fa-icon>
|
<fa-icon [icon]="faPen"></fa-icon>
|
||||||
}
|
}
|
||||||
</div>
|
</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 {NgClass} from '@angular/common';
|
||||||
import {faPen} from '@fortawesome/free-solid-svg-icons';
|
import {faPen} from '@fortawesome/free-solid-svg-icons';
|
||||||
import {Series} from '../Series';
|
import {Series} from '../Series';
|
||||||
|
import {or} from '../../common';
|
||||||
|
|
||||||
export enum SeriesType {
|
export enum SeriesType {
|
||||||
BOOL = 'BOOL',
|
BOOL = 'BOOL',
|
||||||
@ -35,47 +36,36 @@ export class SeriesSelect {
|
|||||||
list: Series[] = [];
|
list: Series[] = [];
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
readonly onChange = new EventEmitter<Series | null>();
|
readonly onChange = new EventEmitter<number | null>();
|
||||||
|
|
||||||
protected showPen: boolean = false;
|
protected showPen: boolean = false;
|
||||||
|
|
||||||
protected model: Series | null = null;
|
protected model: number | null = null;
|
||||||
|
|
||||||
protected editing: boolean = false;
|
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set initial(value: Series | null) {
|
set initial(value: Series | null) {
|
||||||
this._initial = value;
|
this._initial = value;
|
||||||
if (!this.editing) {
|
this.reset();
|
||||||
this.model = this.initial;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private reset() {
|
||||||
|
this.model = or(this.initial, i => i.id, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
get initial(): Series | null {
|
get initial(): Series | null {
|
||||||
return this._initial;
|
return this._initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected start() {
|
|
||||||
this.editing = true;
|
|
||||||
setTimeout(() => this.input.nativeElement.focus(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected apply() {
|
protected apply() {
|
||||||
if (this.model !== this.initial) {
|
if (this.model !== this.initial) {
|
||||||
this.onChange.emit(this.model);
|
this.onChange.emit(this.model);
|
||||||
}
|
}
|
||||||
this.editing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected cancel() {
|
|
||||||
this.model = this.initial;
|
|
||||||
this.editing = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected classes(): {} {
|
protected classes(): {} {
|
||||||
return {
|
return {
|
||||||
"unchanged": this.editing && this.model === this.initial,
|
"unchanged": this.model === this.initial,
|
||||||
"changed": this.model !== this.initial,
|
"changed": this.model !== or(this.initial, i => i.id, null),
|
||||||
"invalid": !this.allowEmpty && this.model === null,
|
"invalid": !this.allowEmpty && this.model === null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,8 @@
|
|||||||
package de.ph87.data;
|
package de.ph87.data;
|
||||||
|
|
||||||
import de.ph87.data.location.Location;
|
import de.ph87.data.topic.TopicDto;
|
||||||
import de.ph87.data.location.LocationRepository;
|
import de.ph87.data.topic.TopicType;
|
||||||
import de.ph87.data.plot.Plot;
|
import de.ph87.data.topic.TopicService;
|
||||||
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 lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
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.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class DemoService {
|
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 DemoConfig demoConfig;
|
||||||
|
|
||||||
private final LocationRepository locationRepository;
|
private final TopicService topicService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@EventListener(ApplicationReadyEvent.class)
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
@ -51,331 +25,12 @@ public class DemoService {
|
|||||||
if (!demoConfig.isEnabled()) {
|
if (!demoConfig.isEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
location("Eppelborn", 49.4086, 6.9645);
|
final TopicDto dto = topicService.create();
|
||||||
location("Friedrichsthal", 49.3270, 7.0947);
|
topicService.modify(dto.id, topic -> {
|
||||||
topics();
|
topic.setEnabled(true);
|
||||||
}
|
topic.setName("openDTU/pv/patrix/json2");
|
||||||
|
topic.setType(TopicType.PatrixOpenDtu);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
private Series purchase;
|
private Series energyPurchase;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
private Series delivery;
|
private Series energyDeliver;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
private Series produce;
|
private Series energyProduce;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
@ManyToOne
|
@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")
|
@GetMapping("{id}/delete")
|
||||||
public LocationDto delete(@PathVariable final long id) {
|
public LocationDto delete(@PathVariable final long id) {
|
||||||
return locationService.delete(id);
|
return locationService.set(id, locationRepository::delete);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/name")
|
@PostMapping("{id}/name")
|
||||||
public LocationDto name(@PathVariable final long id, @RequestBody(required = false) @Nullable final String 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")
|
@PostMapping("{id}/latitude")
|
||||||
public LocationDto latitude(@PathVariable final long id, @RequestBody final double 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")
|
@PostMapping("{id}/longitude")
|
||||||
public LocationDto longitude(@PathVariable final long id, @RequestBody final double 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")
|
@PostMapping("{id}/energyPurchase")
|
||||||
public LocationDto purchase(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
|
public LocationDto energyPurchase(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
|
||||||
return locationService.purchase(id, seriesId);
|
return locationService.setSeries(id, seriesId, Location::setEnergyPurchase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/delivery")
|
@PostMapping("{id}/energyDeliver")
|
||||||
public LocationDto delivery(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
|
public LocationDto energyDeliver(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
|
||||||
return locationService.delivery(id, seriesId);
|
return locationService.setSeries(id, seriesId, Location::setEnergyDeliver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/produce")
|
@PostMapping("{id}/energyProduce")
|
||||||
public LocationDto produce(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
|
public LocationDto energyProduce(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
|
||||||
return locationService.produce(id, seriesId);
|
return locationService.setSeries(id, seriesId, Location::setEnergyProduce);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/power")
|
@PostMapping("{id}/powerPurchase")
|
||||||
public LocationDto power(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
|
public LocationDto powerPurchase(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long seriesId) {
|
||||||
return locationService.power(id, 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;
|
public final double longitude;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public final SeriesDto purchase;
|
public final SeriesDto energyPurchase;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public final SeriesDto delivery;
|
public final SeriesDto energyDeliver;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public final SeriesDto produce;
|
public final SeriesDto energyProduce;
|
||||||
|
|
||||||
@Nullable
|
@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) {
|
public LocationDto(@NonNull final Location location) {
|
||||||
this.id = location.getId();
|
this.id = location.getId();
|
||||||
@ -38,10 +44,12 @@ public class LocationDto {
|
|||||||
this.name = location.getName();
|
this.name = location.getName();
|
||||||
this.latitude = location.getLatitude();
|
this.latitude = location.getLatitude();
|
||||||
this.longitude = location.getLongitude();
|
this.longitude = location.getLongitude();
|
||||||
this.purchase = map(location.getPurchase(), SeriesDto::new);
|
this.energyPurchase = map(location.getEnergyPurchase(), SeriesDto::new);
|
||||||
this.delivery = map(location.getDelivery(), SeriesDto::new);
|
this.energyDeliver = map(location.getEnergyDeliver(), SeriesDto::new);
|
||||||
this.produce = map(location.getProduce(), SeriesDto::new);
|
this.energyProduce = map(location.getEnergyProduce(), SeriesDto::new);
|
||||||
this.power = map(location.getPower(), 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;
|
package de.ph87.data.location;
|
||||||
|
|
||||||
import de.ph87.data.series.Series;
|
import de.ph87.data.series.Series;
|
||||||
import de.ph87.data.series.SeriesRepository;
|
import de.ph87.data.series.SeriesService;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -21,7 +21,7 @@ public class LocationService {
|
|||||||
|
|
||||||
private final LocationRepository locationRepository;
|
private final LocationRepository locationRepository;
|
||||||
|
|
||||||
private final SeriesRepository seriesRepository;
|
private final SeriesService seriesService;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -31,60 +31,14 @@ public class LocationService {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Transactional
|
@Transactional
|
||||||
public LocationDto delete(final long id) {
|
public LocationDto setSeries(final long id, @Nullable final Long seriesId, @NonNull final BiConsumer<Location, Series> setter) {
|
||||||
return _set(id, locationRepository::delete);
|
final Series series = seriesId == null ? null : seriesService.getById(seriesId);
|
||||||
|
return set(id, location -> setter.accept(location, series));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Transactional
|
@Transactional
|
||||||
public LocationDto name(final long id, @NonNull final String name) {
|
public LocationDto set(final long id, @NonNull final Consumer<Location> setter) {
|
||||||
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) {
|
|
||||||
final Location location = locationRepository.findById(id).orElseThrow(notFound(Location.class, "id", id));
|
final Location location = locationRepository.findById(id).orElseThrow(notFound(Location.class, "id", id));
|
||||||
setter.accept(location);
|
setter.accept(location);
|
||||||
return new LocationDto(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.PlotService;
|
||||||
import de.ph87.data.plot.axis.graph.Graph;
|
import de.ph87.data.plot.axis.graph.Graph;
|
||||||
import de.ph87.data.plot.axis.graph.GraphRepository;
|
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.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -23,8 +21,6 @@ public class AxisService {
|
|||||||
|
|
||||||
private final GraphRepository graphRepository;
|
private final GraphRepository graphRepository;
|
||||||
|
|
||||||
private final SeriesRepository seriesRepository;
|
|
||||||
|
|
||||||
private final PlotService plotService;
|
private final PlotService plotService;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -40,8 +36,7 @@ public class AxisService {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Transactional
|
@Transactional
|
||||||
public PlotDto addGraph(final long axisId) {
|
public PlotDto addGraph(final long axisId) {
|
||||||
final Series series = seriesRepository.findFirstByOrderByNameAsc().orElseThrow();
|
return set(axisId, axis -> axis.addGraph(graphRepository.save(new Graph(axis))));
|
||||||
return set(axisId, axis -> axis.addGraph(graphRepository.save(new Graph(axis, series))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@ -38,8 +38,8 @@ public class Graph {
|
|||||||
private Axis axis;
|
private Axis axis;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@NonNull
|
@Nullable
|
||||||
@ManyToOne(optional = false)
|
@ManyToOne
|
||||||
private Series series;
|
private Series series;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@ -108,9 +108,8 @@ public class Graph {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private boolean avg = true;
|
private boolean avg = true;
|
||||||
|
|
||||||
public Graph(@NonNull final Axis axis, @NonNull final Series series) {
|
public Graph(@NonNull final Axis axis) {
|
||||||
this.axis = axis;
|
this.axis = axis;
|
||||||
this.series = series;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Graph(@NonNull final Axis axis, @NonNull final Graph graph) {
|
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.PlotDto;
|
||||||
import de.ph87.data.plot.axis.AxisRepository;
|
import de.ph87.data.plot.axis.AxisRepository;
|
||||||
import de.ph87.data.series.SeriesRepository;
|
import de.ph87.data.series.SeriesService;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -26,10 +26,10 @@ public class GraphController {
|
|||||||
|
|
||||||
private final GraphService graphService;
|
private final GraphService graphService;
|
||||||
|
|
||||||
private final SeriesRepository seriesRepository;
|
|
||||||
|
|
||||||
private final AxisRepository axisRepository;
|
private final AxisRepository axisRepository;
|
||||||
|
|
||||||
|
private final SeriesService seriesService;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@GetMapping("{id}/delete")
|
@GetMapping("{id}/delete")
|
||||||
public PlotDto delete(@PathVariable final long id) {
|
public PlotDto delete(@PathVariable final long id) {
|
||||||
@ -37,86 +37,86 @@ public class GraphController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/visible")
|
@PostMapping("{id}/visible")
|
||||||
public PlotDto visible(@PathVariable final long id, @RequestBody final boolean value) {
|
public PlotDto visible(@PathVariable final long id, @RequestBody final boolean visible) {
|
||||||
return graphService.set(id, graph -> graph.setVisible(value));
|
return graphService.set(id, graph -> graph.setVisible(visible));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/type")
|
@PostMapping("{id}/type")
|
||||||
public PlotDto type(@PathVariable final long id, @RequestBody @NonNull final String value) {
|
public PlotDto type(@PathVariable final long id, @RequestBody @NonNull final String typeName) {
|
||||||
return graphService.set(id, graph -> graph.setType(GraphType.valueOf(value)));
|
return graphService.set(id, graph -> graph.setType(GraphType.valueOf(typeName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/name")
|
@PostMapping("{id}/name")
|
||||||
public PlotDto name(@PathVariable final long id, @RequestBody(required = false) @Nullable final String value) {
|
public PlotDto name(@PathVariable final long id, @RequestBody(required = false) @Nullable final String name) {
|
||||||
return graphService.set(id, graph -> graph.setName(or(value, "")));
|
return graphService.set(id, graph -> graph.setName(or(name, "")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/series")
|
@PostMapping("{id}/series")
|
||||||
public PlotDto series(@PathVariable final long id, @RequestBody final long value) {
|
public PlotDto series(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long series) {
|
||||||
return graphService.set(id, graph -> graph.setSeries(seriesRepository.findById(value).orElseThrow()));
|
return graphService.set(id, graph -> graph.setSeries(series == null ? null : seriesService.getById(series)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/factor")
|
@PostMapping("{id}/factor")
|
||||||
public PlotDto factor(@PathVariable final long id, @RequestBody final double value) {
|
public PlotDto factor(@PathVariable final long id, @RequestBody final double factor) {
|
||||||
return graphService.set(id, graph -> graph.setFactor(value));
|
return graphService.set(id, graph -> graph.setFactor(factor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/operation")
|
@PostMapping("{id}/operation")
|
||||||
public PlotDto operation(@PathVariable final long id, @RequestBody @NonNull final String value) {
|
public PlotDto operation(@PathVariable final long id, @RequestBody @NonNull final String operationName) {
|
||||||
return graphService.set(id, graph -> graph.setOperation(GraphOperation.valueOf(value)));
|
return graphService.set(id, graph -> graph.setOperation(GraphOperation.valueOf(operationName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/series2")
|
@PostMapping("{id}/series2")
|
||||||
public PlotDto series2(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long value) {
|
public PlotDto series2(@PathVariable final long id, @RequestBody(required = false) @Nullable final Long series2) {
|
||||||
return graphService.set(id, graph -> graph.setSeries2(value == null ? null : seriesRepository.findById(value).orElseThrow()));
|
return graphService.set(id, graph -> graph.setSeries2(series2 == null ? null : seriesService.getById(series2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/factor2")
|
@PostMapping("{id}/factor2")
|
||||||
public PlotDto factor2(@PathVariable final long id, @RequestBody final double value) {
|
public PlotDto factor2(@PathVariable final long id, @RequestBody final double factor2) {
|
||||||
return graphService.set(id, graph -> graph.setFactor2(value));
|
return graphService.set(id, graph -> graph.setFactor2(factor2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/color")
|
@PostMapping("{id}/color")
|
||||||
public PlotDto color(@PathVariable final long id, @RequestBody @NonNull final String value) {
|
public PlotDto color(@PathVariable final long id, @RequestBody @NonNull final String color) {
|
||||||
return graphService.set(id, graph -> graph.setColor(value));
|
return graphService.set(id, graph -> graph.setColor(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/group")
|
@PostMapping("{id}/group")
|
||||||
public PlotDto group(@PathVariable final long id, @RequestBody @NonNull final String value) {
|
public PlotDto group(@PathVariable final long id, @RequestBody @NonNull final String group) {
|
||||||
return graphService.set(id, graph -> graph.setGroup(Group.valueOf(value)));
|
return graphService.set(id, graph -> graph.setGroup(Group.valueOf(group)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/stack")
|
@PostMapping("{id}/stack")
|
||||||
public PlotDto stack(@PathVariable final long id, @RequestBody(required = false) @Nullable final String value) {
|
public PlotDto stack(@PathVariable final long id, @RequestBody(required = false) @Nullable final String stack) {
|
||||||
return graphService.set(id, graph -> graph.setStack(or(value, "")));
|
return graphService.set(id, graph -> graph.setStack(or(stack, "")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/min")
|
@PostMapping("{id}/min")
|
||||||
public PlotDto min(@PathVariable final long id, @RequestBody final boolean value) {
|
public PlotDto min(@PathVariable final long id, @RequestBody final boolean min) {
|
||||||
return graphService.set(id, graph -> graph.setMin(value));
|
return graphService.set(id, graph -> graph.setMin(min));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/max")
|
@PostMapping("{id}/max")
|
||||||
public PlotDto max(@PathVariable final long id, @RequestBody final boolean value) {
|
public PlotDto max(@PathVariable final long id, @RequestBody final boolean max) {
|
||||||
return graphService.set(id, graph -> graph.setMax(value));
|
return graphService.set(id, graph -> graph.setMax(max));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/avg")
|
@PostMapping("{id}/avg")
|
||||||
public PlotDto avg(@PathVariable final long id, @RequestBody final boolean value) {
|
public PlotDto avg(@PathVariable final long id, @RequestBody final boolean avg) {
|
||||||
return graphService.set(id, graph -> graph.setAvg(value));
|
return graphService.set(id, graph -> graph.setAvg(avg));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/axis")
|
@PostMapping("{id}/axis")
|
||||||
public PlotDto axis(@PathVariable final long id, @RequestBody final long value) {
|
public PlotDto axis(@PathVariable final long id, @RequestBody final long axis) {
|
||||||
return graphService.set(id, graph -> graph.setAxis(axisRepository.findById(value).orElseThrow()));
|
return graphService.set(id, graph -> graph.setAxis(axisRepository.findById(axis).orElseThrow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("{id}/position")
|
@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 -> {
|
return graphService.set(id, graph -> {
|
||||||
final List<Graph> list = graph.getAxis().getGraphs();
|
final List<Graph> list = graph.getAxis().getGraphs();
|
||||||
list.remove(graph);
|
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
|
@NonNull
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
@Enumerated(EnumType.STRING)
|
@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) {
|
public Series(@NonNull final String name, @NonNull final String unit, final int decimals, final int seconds, @NonNull final SeriesType type) {
|
||||||
this.name = name;
|
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;
|
private final SeriesPointService seriesPointService;
|
||||||
|
|
||||||
@PostMapping("create")
|
@GetMapping("create")
|
||||||
public SeriesDto create() {
|
public SeriesDto create() {
|
||||||
return seriesService.create();
|
return seriesService.create();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static de.ph87.data.location.NotFoundException.notFound;
|
import static de.ph87.data.location.NotFoundException.notFound;
|
||||||
@ -29,7 +31,7 @@ public class SeriesService {
|
|||||||
private String _generateUniqueName() {
|
private String _generateUniqueName() {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
final String name = "series" + index;
|
final String name = "series/unnamed/" + index++;
|
||||||
if (!seriesRepository.existsByName(name)) {
|
if (!seriesRepository.existsByName(name)) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@ -38,14 +40,19 @@ public class SeriesService {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Transactional
|
@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);
|
final Series series = getById(id);
|
||||||
modifier.accept(series);
|
modifier.accept(series);
|
||||||
return publish(series, CrudAction.MODIFIED);
|
return publish(series, CrudAction.MODIFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@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));
|
return seriesRepository.findById(id).orElseThrow(notFound(Series.class, "id", id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,4 +63,12 @@ public class SeriesService {
|
|||||||
return dto;
|
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;
|
package de.ph87.data.series.data.bool;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import de.ph87.data.series.SeriesDto;
|
import de.ph87.data.series.SeriesDto;
|
||||||
import de.ph87.data.websocket.IWebsocketMessage;
|
import de.ph87.data.websocket.IWebsocketMessage;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -33,6 +34,7 @@ public class BoolDto implements IWebsocketMessage {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
public String getWebsocketTopic() {
|
public String getWebsocketTopic() {
|
||||||
return "Bool/%d".formatted(series.id);
|
return "Bool/%d".formatted(series.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
package de.ph87.data.series.data.bool;
|
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.Series;
|
||||||
|
import de.ph87.data.series.SeriesService;
|
||||||
import de.ph87.data.series.data.DataId;
|
import de.ph87.data.series.data.DataId;
|
||||||
|
import de.ph87.data.series.point.ISeriesPointRequest;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -18,12 +19,16 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BoolService {
|
public class BoolService {
|
||||||
|
|
||||||
private final BoolRepo boolRepo;
|
|
||||||
|
|
||||||
private final ApplicationEventPublisher applicationEventPublisher;
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
|
private final SeriesService seriesService;
|
||||||
|
|
||||||
|
private final BoolRepo boolRepo;
|
||||||
|
|
||||||
@Transactional
|
@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);
|
final Bool bool = updateOrCreate(series, begin, end, state, terminated);
|
||||||
log.debug("Bool written: {}", bool);
|
log.debug("Bool written: {}", bool);
|
||||||
applicationEventPublisher.publishEvent(new BoolDto(bool));
|
applicationEventPublisher.publishEvent(new BoolDto(bool));
|
||||||
@ -32,11 +37,7 @@ public class BoolService {
|
|||||||
@NonNull
|
@NonNull
|
||||||
private Bool updateOrCreate(@NonNull final Series series, @NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end, final boolean state, final boolean terminated) {
|
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);
|
final DataId id = new DataId(series, begin);
|
||||||
return boolRepo
|
return boolRepo.findById(id).stream().peek(existing -> {
|
||||||
.findById(id)
|
|
||||||
.stream()
|
|
||||||
.peek(
|
|
||||||
existing -> {
|
|
||||||
if (existing.isState() != state) {
|
if (existing.isState() != state) {
|
||||||
log.error("Differing states: received=(begin={}, end={}, state={}, terminated={}), existing={}", begin, end, state, terminated, existing);
|
log.error("Differing states: received=(begin={}, end={}, state={}, terminated={}), existing={}", begin, end, state, terminated, existing);
|
||||||
return;
|
return;
|
||||||
@ -51,11 +52,7 @@ public class BoolService {
|
|||||||
}
|
}
|
||||||
existing.setEnd(end);
|
existing.setEnd(end);
|
||||||
existing.setTerminated(terminated);
|
existing.setTerminated(terminated);
|
||||||
})
|
}).findFirst().orElseGet(() -> boolRepo.save(new Bool(id, end, state, terminated)));
|
||||||
.findFirst()
|
|
||||||
.orElseGet(
|
|
||||||
() -> boolRepo.save(new Bool(id, end, state, terminated))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package de.ph87.data.series.data.delta;
|
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.Interval;
|
||||||
import de.ph87.data.series.data.delta.meter.MeterDto;
|
import de.ph87.data.series.data.delta.meter.MeterDto;
|
||||||
import de.ph87.data.websocket.IWebsocketMessage;
|
import de.ph87.data.websocket.IWebsocketMessage;
|
||||||
@ -36,6 +37,7 @@ public abstract class DeltaDto implements IWebsocketMessage {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
public String getWebsocketTopic() {
|
public String getWebsocketTopic() {
|
||||||
return "Delta/%d/%s".formatted(meter.series.id, interval);
|
return "Delta/%d/%s".formatted(meter.series.id, interval);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
package de.ph87.data.series.data.delta;
|
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.Series;
|
||||||
|
import de.ph87.data.series.SeriesService;
|
||||||
import de.ph87.data.series.data.Interval;
|
import de.ph87.data.series.data.Interval;
|
||||||
import de.ph87.data.series.data.delta.meter.Meter;
|
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.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -21,6 +22,12 @@ import java.util.function.BiFunction;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class DeltaService {
|
public class DeltaService {
|
||||||
|
|
||||||
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
|
private final SeriesService seriesService;
|
||||||
|
|
||||||
|
private final MeterRepository meterRepository;
|
||||||
|
|
||||||
private final DeltaRepoFive five;
|
private final DeltaRepoFive five;
|
||||||
|
|
||||||
private final DeltaRepoHour hour;
|
private final DeltaRepoHour hour;
|
||||||
@ -33,13 +40,10 @@ public class DeltaService {
|
|||||||
|
|
||||||
private final DeltaRepoYear year;
|
private final DeltaRepoYear year;
|
||||||
|
|
||||||
private final ApplicationEventPublisher applicationEventPublisher;
|
|
||||||
|
|
||||||
private final MeterService meterService;
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void write(@NonNull final Series series, @NonNull final String meterNumber, @NonNull final ZonedDateTime date, final double value) {
|
public void write(final long seriesId, @NonNull final String meterNumber, @NonNull final ZonedDateTime date, final double value) {
|
||||||
final Meter meter = meterService.getLastValidBySeriesAndNumberOrCreate(series, meterNumber, date);
|
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, 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, hour, Interval.HOUR, date, value, Delta.Hour::new, DeltaDto.Hour::new);
|
||||||
write(meter, day, Interval.DAY, date, value, Delta.Day::new, DeltaDto.Day::new);
|
write(meter, day, Interval.DAY, date, value, Delta.Day::new, DeltaDto.Day::new);
|
||||||
@ -55,6 +59,18 @@ public class DeltaService {
|
|||||||
applicationEventPublisher.publishEvent(toDto.apply(delta, interval));
|
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
|
@NonNull
|
||||||
public List<DeltaPoint> points(@NonNull final Series series, @NonNull final ISeriesPointRequest request) {
|
public List<DeltaPoint> points(@NonNull final Series series, @NonNull final ISeriesPointRequest request) {
|
||||||
return switch (request.getInterval()) {
|
return switch (request.getInterval()) {
|
||||||
|
|||||||
@ -29,6 +29,7 @@ public class Meter {
|
|||||||
private long version;
|
private long version;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ToString.Exclude
|
||||||
@ManyToOne(optional = false)
|
@ManyToOne(optional = false)
|
||||||
private Series series;
|
private Series series;
|
||||||
|
|
||||||
@ -36,6 +37,12 @@ public class Meter {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String number;
|
private String number;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@ToString.Include
|
||||||
|
public String series() {
|
||||||
|
return series.toShortString();
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private ZonedDateTime first;
|
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;
|
package de.ph87.data.series.data.varying;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import de.ph87.data.series.SeriesDto;
|
import de.ph87.data.series.SeriesDto;
|
||||||
import de.ph87.data.series.data.Interval;
|
import de.ph87.data.series.data.Interval;
|
||||||
import de.ph87.data.websocket.IWebsocketMessage;
|
import de.ph87.data.websocket.IWebsocketMessage;
|
||||||
@ -42,6 +43,7 @@ public abstract class VaryingDto implements IWebsocketMessage {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
public String getWebsocketTopic() {
|
public String getWebsocketTopic() {
|
||||||
return "Varying/%d/%s".formatted(series.id, interval);
|
return "Varying/%d/%s".formatted(series.id, interval);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
package de.ph87.data.series.data.varying;
|
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.Series;
|
||||||
|
import de.ph87.data.series.SeriesService;
|
||||||
import de.ph87.data.series.data.DataId;
|
import de.ph87.data.series.data.DataId;
|
||||||
import de.ph87.data.series.data.Interval;
|
import de.ph87.data.series.data.Interval;
|
||||||
|
import de.ph87.data.series.point.ISeriesPointRequest;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -20,6 +21,10 @@ import java.util.function.BiFunction;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class VaryingService {
|
public class VaryingService {
|
||||||
|
|
||||||
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
|
private final SeriesService seriesService;
|
||||||
|
|
||||||
private final VaryingRepoFive five;
|
private final VaryingRepoFive five;
|
||||||
|
|
||||||
private final VaryingRepoHour hour;
|
private final VaryingRepoHour hour;
|
||||||
@ -32,10 +37,9 @@ public class VaryingService {
|
|||||||
|
|
||||||
private final VaryingRepoYear year;
|
private final VaryingRepoYear year;
|
||||||
|
|
||||||
private final ApplicationEventPublisher applicationEventPublisher;
|
|
||||||
|
|
||||||
@Transactional
|
@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, 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, hour, Interval.HOUR, date, value, Varying.Hour::new, VaryingDto.Hour::new);
|
||||||
write(series, day, Interval.DAY, date, value, Varying.Day::new, VaryingDto.Day::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.Series;
|
||||||
import de.ph87.data.series.SeriesDto;
|
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.bool.BoolService;
|
||||||
import de.ph87.data.series.data.delta.DeltaService;
|
import de.ph87.data.series.data.delta.DeltaService;
|
||||||
import de.ph87.data.series.data.varying.VaryingService;
|
import de.ph87.data.series.data.varying.VaryingService;
|
||||||
@ -10,9 +10,7 @@ import jakarta.annotation.Nullable;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -21,20 +19,20 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SeriesPointService {
|
public class SeriesPointService {
|
||||||
|
|
||||||
private final SeriesRepository seriesRepository;
|
|
||||||
|
|
||||||
private final BoolService boolService;
|
private final BoolService boolService;
|
||||||
|
|
||||||
private final DeltaService deltaService;
|
private final DeltaService deltaService;
|
||||||
|
|
||||||
private final VaryingService varyingService;
|
private final VaryingService varyingService;
|
||||||
|
|
||||||
|
private final SeriesService seriesService;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public OneSeriesPointsResponse oneSeriesPoints(@NonNull final OneSeriesPointsRequest request) {
|
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);
|
final List<? extends SeriesPoint<?>> points1 = getSeriesPoints(series1, request, request.factor);
|
||||||
if (request.id2 != null) {
|
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);
|
final List<? extends SeriesPoint<?>> points2 = getSeriesPoints(series2, request, request.factor2);
|
||||||
return new OneSeriesPointsResponse(SeriesPoint.combine(points1, points2, request.operation));
|
return new OneSeriesPointsResponse(SeriesPoint.combine(points1, points2, request.operation));
|
||||||
}
|
}
|
||||||
@ -43,7 +41,7 @@ public class SeriesPointService {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public AllSeriesPointResponse allSeriesPoint(@NonNull final AllSeriesPointRequest request) {
|
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);
|
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;
|
package de.ph87.data.topic;
|
||||||
|
|
||||||
|
import de.ph87.data.series.Series;
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.EnumType;
|
||||||
|
import jakarta.persistence.Enumerated;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import jakarta.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.Version;
|
import jakarta.persistence.Version;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -24,8 +30,43 @@ public class Topic {
|
|||||||
@Version
|
@Version
|
||||||
private long version;
|
private long version;
|
||||||
|
|
||||||
|
@Setter
|
||||||
@NonNull
|
@NonNull
|
||||||
@Column(nullable = false, unique = true)
|
@Column(nullable = false, unique = true)
|
||||||
private String name;
|
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;
|
package de.ph87.data.topic;
|
||||||
|
|
||||||
|
import de.ph87.data.series.SeriesDto;
|
||||||
import de.ph87.data.websocket.IWebsocketMessage;
|
import de.ph87.data.websocket.IWebsocketMessage;
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import static de.ph87.data.Helpers.map;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class TopicDto implements IWebsocketMessage {
|
public class TopicDto implements IWebsocketMessage {
|
||||||
|
|
||||||
@ -12,9 +16,29 @@ public class TopicDto implements IWebsocketMessage {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public final String name;
|
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) {
|
public TopicDto(@NonNull final Topic topic) {
|
||||||
this.id = topic.getId();
|
this.id = topic.getId();
|
||||||
this.name = topic.getName();
|
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.context.event.EventListener;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class TopicReceiver {
|
public class TopicReceiver {
|
||||||
|
|
||||||
|
private final TopicRepository topicRepository;
|
||||||
|
|
||||||
|
private final Set<ITopicParser> parsers;
|
||||||
|
|
||||||
@EventListener(MqttMessage.class)
|
@EventListener(MqttMessage.class)
|
||||||
public void receive(@NonNull final MqttMessage mqttMessage) {
|
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.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.ListCrudRepository;
|
import org.springframework.data.repository.ListCrudRepository;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface TopicRepository extends ListCrudRepository<Topic, Long> {
|
public interface TopicRepository extends ListCrudRepository<Topic, Long> {
|
||||||
|
|
||||||
@NonNull
|
boolean existsByName(@NonNull String name);
|
||||||
Optional<Topic> findByName(@NonNull String name);
|
|
||||||
|
|
||||||
@Query("select new de.ph87.data.topic.TopicDto(t) from Topic t")
|
@Query("select new de.ph87.data.topic.TopicDto(t) from Topic t where t.name = :name")
|
||||||
List<TopicDto> findAllDto();
|
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