mqtt, series, meter, varying, Heizung, OpenDTU, SmartMeter, PatrixJson

This commit is contained in:
Patrick Haßel 2025-04-18 21:38:39 +02:00
commit 606ee34731
62 changed files with 1835 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

10
application.properties Normal file
View File

@ -0,0 +1,10 @@
logging.level.de.ph87.home=DEBUG
#-
spring.datasource.url=jdbc:h2:./database;AUTO_SERVER=TRUE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
#-
#spring.jpa.hibernate.ddl-auto=create
#-
de.ph87.patrix.mqtt.host=10.0.0.50

32
pom.xml Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>Home</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixParent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixCrud</artifactId>
</dependency>
<dependency>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixMqtt</artifactId>
</dependency>
<dependency>
<groupId>tech.units</groupId>
<artifactId>indriya</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,15 @@
package de.ph87.home;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"de.ph87.home", "de.ph87.patrix"})
public class Backend {
public static void main(String[] args) {
SpringApplication.run(Backend.class, args);
}
}

View File

@ -0,0 +1,58 @@
package de.ph87.home.meter;
import de.ph87.home.series.Series;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
import java.time.ZonedDateTime;
import static de.ph87.home.unit.UnitHelper.convert;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class Meter {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Version
@ToString.Exclude
private long version;
@NonNull
@ToString.Exclude
@ManyToOne(optional = false)
private Series series;
@NonNull
@ToString.Include
public String series() {
return series.getName();
}
@NonNull
@Column(nullable = false)
private ZonedDateTime since;
@NonNull
@Column(nullable = false)
private String number;
@Column(nullable = false, name = "`value`")
private double value;
public Meter(@NonNull final Series series, @NonNull final ZonedDateTime since, @NonNull final String number, @NonNull final ComparableQuantity<?> value) {
this.series = series;
this.since = since;
this.number = number;
this.value = convert(value, series.getUnit()).getValue().doubleValue();
}
}

View File

@ -0,0 +1,14 @@
package de.ph87.home.meter;
import de.ph87.home.series.Series;
import lombok.NonNull;
import org.springframework.data.repository.ListCrudRepository;
import java.util.Optional;
public interface MeterRepository extends ListCrudRepository<Meter, Long> {
@NonNull
Optional<Meter> findFirstBySeriesOrderBySinceDesc(@NonNull final Series series);
}

View File

@ -0,0 +1,29 @@
package de.ph87.home.meter;
import de.ph87.home.meter.value.MeterValueInbound;
import de.ph87.home.series.Series;
import de.ph87.home.series.SeriesService;
import de.ph87.home.series.Type;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class MeterService {
private final MeterRepository meterRepository;
private final SeriesService seriesService;
@NonNull
public Meter getOrCreate(@NonNull final MeterValueInbound event) {
final Series series = seriesService.getOrCreate(event.getName(), Type.METER, event.value.getUnit());
return meterRepository.findFirstBySeriesOrderBySinceDesc(series)
.filter(meter -> meter.getNumber().equals(event.getNumber()))
.orElseGet(() -> meterRepository.save(new Meter(series, event.getDate(), event.getNumber(), event.getValue())));
}
}

View File

@ -0,0 +1,19 @@
package de.ph87.home.meter.value;
import de.ph87.home.point.PointResult;
import lombok.Getter;
import lombok.NonNull;
import java.time.ZonedDateTime;
@Getter
public class MeterPoint extends PointResult.Point {
public final double delta;
public MeterPoint(@NonNull final ZonedDateTime date, final double delta) {
super(date);
this.delta = delta;
}
}

View File

@ -0,0 +1,82 @@
package de.ph87.home.meter.value;
import de.ph87.home.meter.Meter;
import de.ph87.home.unit.Aligned;
import de.ph87.home.unit.Interval;
import de.ph87.home.unit.UnitHelper;
import jakarta.persistence.*;
import lombok.*;
import tech.units.indriya.ComparableQuantity;
import java.time.ZonedDateTime;
@Getter
@ToString
@MappedSuperclass
@NoArgsConstructor
public abstract class MeterValue {
@EmbeddedId
private Id id;
@Column(nullable = false, updatable = false)
private double min;
@Column(nullable = false)
private double max;
protected MeterValue(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
this.id = id;
this.min = UnitHelper.convert(value, id.meter.getSeries().getUnit()).getValue().doubleValue();
this.max = this.min;
}
public void update(@NonNull final ComparableQuantity<?> value) {
final double converted = UnitHelper.convert(value, id.meter.getSeries().getUnit()).getValue().doubleValue();
if (converted < min) {
throw new RuntimeException("Meter cannot run backwards: MeterValue=%s, inbound=%s".formatted(this, value));
}
min = Math.min(converted, min);
max = Math.max(converted, max);
}
@Getter
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public static class Id {
@NonNull
@ToString.Exclude
@ManyToOne(optional = false)
private Meter meter;
@NonNull
@ToString.Include
public String series() {
return meter.series();
}
@NonNull
@ToString.Include
public String meter() {
return meter.getNumber();
}
@NonNull
@Column(nullable = false, updatable = false)
private ZonedDateTime date;
public Id(@NonNull final Interval interval, @NonNull final ZonedDateTime date, @NonNull final Meter meter) {
this.date = new Aligned(date, interval).date;
this.meter = meter;
}
@Override
public String toString() {
return "%s()";
}
}
}

View File

@ -0,0 +1,37 @@
package de.ph87.home.meter.value;
import lombok.Data;
import lombok.NonNull;
import tech.units.indriya.ComparableQuantity;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@Data
public class MeterValueInbound {
@NonNull
public final String name;
@NonNull
public final String number;
@NonNull
public final ZonedDateTime date;
@NonNull
public final ComparableQuantity<?> value;
public MeterValueInbound(@NonNull final String name, @NonNull final String number, final long date, @NonNull final ComparableQuantity<?> value) {
this(name, number, ZonedDateTime.ofInstant(Instant.ofEpochSecond(date), ZoneId.systemDefault()), value);
}
public MeterValueInbound(@NonNull final String name, @NonNull final String number, @NonNull final ZonedDateTime date, @NonNull final ComparableQuantity<?> value) {
this.name = name;
this.number = number;
this.date = date;
this.value = value;
}
}

View File

@ -0,0 +1,75 @@
package de.ph87.home.meter.value;
import de.ph87.home.meter.Meter;
import de.ph87.home.meter.MeterService;
import de.ph87.home.meter.value.day.MeterDay;
import de.ph87.home.meter.value.day.MeterDayRepository;
import de.ph87.home.meter.value.five.MeterFive;
import de.ph87.home.meter.value.five.MeterFiveRepository;
import de.ph87.home.meter.value.hour.MeterHour;
import de.ph87.home.meter.value.hour.MeterHourRepository;
import de.ph87.home.meter.value.month.MeterMonth;
import de.ph87.home.meter.value.month.MeterMonthRepository;
import de.ph87.home.meter.value.week.MeterWeek;
import de.ph87.home.meter.value.week.MeterWeekRepository;
import de.ph87.home.meter.value.year.MeterYear;
import de.ph87.home.meter.value.year.MeterYearRepository;
import de.ph87.home.unit.Interval;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.units.indriya.ComparableQuantity;
import java.util.function.BiFunction;
@Slf4j
@Service
@RequiredArgsConstructor
public class MeterValueReceiver {
private final MeterFiveRepository meterFiveRepository;
private final MeterHourRepository meterHourRepository;
private final MeterDayRepository meterDayRepository;
private final MeterWeekRepository meterWeekRepository;
private final MeterMonthRepository meterMonthRepository;
private final MeterYearRepository meterYearRepository;
private final MeterService meterService;
@Transactional
@EventListener
public void onMeterInbound(@NonNull final MeterValueInbound event) {
final Meter meter = meterService.getOrCreate(event);
updateOrCreate(event, meter, Interval.FIVE, MeterFive::new, meterFiveRepository);
updateOrCreate(event, meter, Interval.HOUR, MeterHour::new, meterHourRepository);
updateOrCreate(event, meter, Interval.DAY, MeterDay::new, meterDayRepository);
updateOrCreate(event, meter, Interval.WEEK, MeterWeek::new, meterWeekRepository);
updateOrCreate(event, meter, Interval.MONTH, MeterMonth::new, meterMonthRepository);
updateOrCreate(event, meter, Interval.YEAR, MeterYear::new, meterYearRepository);
}
private <T extends MeterValue> void updateOrCreate(
@NonNull final MeterValueInbound event,
@NonNull final Meter meter,
@NonNull final Interval interval,
@NonNull final BiFunction<MeterValue.Id, ComparableQuantity<?>, T> create,
@NonNull final MeterValueRepository<T> repository
) {
final MeterYear.Id id = new MeterValue.Id(interval, event.getDate(), meter);
repository
.findById(id)
.ifPresentOrElse(
existing -> existing.update(event.getValue()),
() -> repository.save(create.apply(id, event.getValue()))
);
}
}

View File

@ -0,0 +1,17 @@
package de.ph87.home.meter.value;
import lombok.NonNull;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import java.time.ZonedDateTime;
import java.util.List;
@NoRepositoryBean
public interface MeterValueRepository<T extends MeterValue> extends ListCrudRepository<T, MeterValue.Id> {
@Query("select new de.ph87.home.meter.value.MeterPoint(v.id.date, sum(v.max - v.min)) from #{#entityName} v where v.id.meter.series.id = :id and v.id.date >= :first and v.id.date <= :last group by v.id.date")
List<MeterPoint> points(@NonNull long id, @NonNull ZonedDateTime first, @NonNull ZonedDateTime last);
}

View File

@ -0,0 +1,47 @@
package de.ph87.home.meter.value;
import de.ph87.home.meter.value.day.MeterDayRepository;
import de.ph87.home.meter.value.five.MeterFiveRepository;
import de.ph87.home.meter.value.hour.MeterHourRepository;
import de.ph87.home.meter.value.month.MeterMonthRepository;
import de.ph87.home.meter.value.week.MeterWeekRepository;
import de.ph87.home.meter.value.year.MeterYearRepository;
import de.ph87.home.point.PointRequest;
import de.ph87.home.series.SeriesDto;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class MeterValueService {
private final MeterFiveRepository meterFiveRepository;
private final MeterHourRepository meterHourRepository;
private final MeterDayRepository meterDayRepository;
private final MeterWeekRepository meterWeekRepository;
private final MeterMonthRepository meterMonthRepository;
private final MeterYearRepository meterYearRepository;
@NonNull
public List<MeterPoint> points(@NonNull final SeriesDto series, @NonNull final PointRequest request) {
return switch (request.getInner()) {
case FIVE -> meterFiveRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case HOUR -> meterHourRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case DAY -> meterDayRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case WEEK -> meterWeekRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case MONTH -> meterMonthRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case YEAR -> meterYearRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
};
}
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.meter.value.day;
import de.ph87.home.meter.value.MeterValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class MeterDay extends MeterValue {
public MeterDay(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.meter.value.day;
import de.ph87.home.meter.value.MeterValueRepository;
public interface MeterDayRepository extends MeterValueRepository<MeterDay> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.meter.value.five;
import de.ph87.home.meter.value.MeterValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class MeterFive extends MeterValue {
public MeterFive(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.meter.value.five;
import de.ph87.home.meter.value.MeterValueRepository;
public interface MeterFiveRepository extends MeterValueRepository<MeterFive> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.meter.value.hour;
import de.ph87.home.meter.value.MeterValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class MeterHour extends MeterValue {
public MeterHour(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.meter.value.hour;
import de.ph87.home.meter.value.MeterValueRepository;
public interface MeterHourRepository extends MeterValueRepository<MeterHour> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.meter.value.month;
import de.ph87.home.meter.value.MeterValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class MeterMonth extends MeterValue {
public MeterMonth(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.meter.value.month;
import de.ph87.home.meter.value.MeterValueRepository;
public interface MeterMonthRepository extends MeterValueRepository<MeterMonth> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.meter.value.week;
import de.ph87.home.meter.value.MeterValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class MeterWeek extends MeterValue {
public MeterWeek(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.meter.value.week;
import de.ph87.home.meter.value.MeterValueRepository;
public interface MeterWeekRepository extends MeterValueRepository<MeterWeek> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.meter.value.year;
import de.ph87.home.meter.value.MeterValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class MeterYear extends MeterValue {
public MeterYear(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.meter.value.year;
import de.ph87.home.meter.value.MeterValueRepository;
public interface MeterYearRepository extends MeterValueRepository<MeterYear> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.point;
import lombok.RequiredArgsConstructor;
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;
@RestController
@RequiredArgsConstructor
@RequestMapping("points")
public class PointController {
private final PointService pointService;
@PostMapping
public PointResult request(@RequestBody final PointRequest request) {
return pointService.request(request);
}
}

View File

@ -0,0 +1,45 @@
package de.ph87.home.point;
import com.fasterxml.jackson.annotation.JsonIgnore;
import de.ph87.home.unit.Aligned;
import de.ph87.home.unit.Interval;
import lombok.Data;
import lombok.NonNull;
import java.time.ZonedDateTime;
@Data
public class PointRequest {
@NonNull
public final String seriesName;
public final long offset;
public final long count;
@NonNull
public final Interval outer;
@NonNull
public final Interval inner;
@NonNull
@JsonIgnore
public final Aligned beginIncluding;
@NonNull
@JsonIgnore
public final Aligned endIncluding;
public PointRequest(@NonNull final String seriesName, final long offset, final long count, @NonNull final Interval outer, @NonNull final Interval inner) {
this.seriesName = seriesName;
this.offset = offset;
this.count = count;
this.outer = outer;
this.inner = inner;
this.endIncluding = new Aligned(ZonedDateTime.now(), outer).minus(offset);
this.beginIncluding = endIncluding.minus(this.count - 1);
}
}

View File

@ -0,0 +1,28 @@
package de.ph87.home.point;
import de.ph87.home.meter.value.MeterValue;
import de.ph87.home.series.SeriesDto;
import de.ph87.home.varying.VaryingValue;
import lombok.Data;
import lombok.Getter;
import lombok.NonNull;
import java.time.ZonedDateTime;
import java.util.List;
@Data
public class PointResult {
public final SeriesDto series;
public final List<? extends Point> points;
@Data
public static abstract class Point {
@NonNull
public final ZonedDateTime date;
}
}

View File

@ -0,0 +1,35 @@
package de.ph87.home.point;
import de.ph87.home.meter.value.MeterValueService;
import de.ph87.home.series.SeriesDto;
import de.ph87.home.series.SeriesRepository;
import de.ph87.home.varying.VaryingValueService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class PointService {
private final SeriesRepository seriesRepository;
private final MeterValueService meterValueService;
private final VaryingValueService varyingValueService;
@NonNull
public PointResult request(@NonNull final PointRequest request) {
final SeriesDto series = seriesRepository.findDtoByName(request.getSeriesName()).orElseThrow();
final List<? extends PointResult.Point> points = switch (series.getType()) {
case METER -> meterValueService.points(series, request);
case VARYING -> varyingValueService.points(series, request);
};
return new PointResult(series, points);
}
}

View File

@ -0,0 +1,62 @@
package de.ph87.home.receive;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.ph87.home.varying.VaryingValueInbound;
import de.ph87.patrix.mqtt.MqttEvent;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import tech.units.indriya.ComparableQuantity;
import tech.units.indriya.quantity.Quantities;
import tech.units.indriya.unit.Units;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
@Service
@RequiredArgsConstructor
public class HeizungHandler {
private static final Pattern REGEX = Pattern.compile("^aggregation/(?<property>heizung/.+)/temperatur$");
private final ObjectMapper objectMapper;
private final ApplicationEventPublisher applicationEventPublisher;
@EventListener
public void handle(@NonNull final MqttEvent event) throws Exception {
final Matcher matcher = REGEX.matcher(event.topic);
if (!matcher.find()) {
return;
}
final String property = matcher.group("property");
final Inbound inbound = objectMapper.readValue(event.payload, Inbound.class);
applicationEventPublisher.publishEvent(new VaryingValueInbound(property, inbound.date, inbound.value));
}
@Getter
@ToString
private static class Inbound {
@NonNull
public final ZonedDateTime date;
public final ComparableQuantity<?> value;
public Inbound(final long timestamp, final double sum, final int count) {
this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
this.value = Quantities.getQuantity(sum / count, Units.CELSIUS);
}
}
}

View File

@ -0,0 +1,83 @@
package de.ph87.home.receive;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.ph87.home.meter.value.MeterValueInbound;
import de.ph87.home.varying.VaryingValueInbound;
import de.ph87.patrix.mqtt.MqttEvent;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import tech.units.indriya.ComparableQuantity;
import tech.units.indriya.quantity.Quantities;
import tech.units.indriya.unit.Units;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import static de.ph87.home.unit.UnitHelper.ENERGY_KILOWATT_HOUR;
@Slf4j
@Service
@RequiredArgsConstructor
public class OpenDTUHandler {
private final ObjectMapper objectMapper;
private final ApplicationEventPublisher applicationEventPublisher;
@EventListener
public void handle(@NonNull final MqttEvent event) throws Exception {
if (!"openDTU/pv/patrix/json2".equals(event.topic)) {
return;
}
final Inbound inbound = objectMapper.readValue(event.payload, Inbound.class);
applicationEventPublisher.publishEvent(new VaryingValueInbound("electricity/producing", inbound.date, inbound.producing));
applicationEventPublisher.publishEvent(new MeterValueInbound("electricity/produced", inbound.inverter, inbound.date, inbound.produced));
applicationEventPublisher.publishEvent(new VaryingValueInbound("electricity/producing/string0", inbound.date, inbound.producing0));
applicationEventPublisher.publishEvent(new MeterValueInbound("electricity/produced/string0", inbound.inverter, inbound.date, inbound.produced0));
applicationEventPublisher.publishEvent(new VaryingValueInbound("electricity/producing/string1", inbound.date, inbound.producing1));
applicationEventPublisher.publishEvent(new MeterValueInbound("electricity/produced/string1", inbound.inverter, inbound.date, inbound.produced1));
}
@Getter
@ToString
public static class Inbound {
@NonNull
public final String inverter;
@NonNull
public final ZonedDateTime date;
public final ComparableQuantity<?> produced;
public final ComparableQuantity<?> producing;
public final ComparableQuantity<?> produced0;
public final ComparableQuantity<?> producing0;
public final ComparableQuantity<?> produced1;
public final ComparableQuantity<?> producing1;
public Inbound(final long timestamp, @NonNull final String inverter, final double totalKWh, final double totalW, final double string0KWh, final double string0W, final double string1KWh, final double string1W) {
this.inverter = inverter;
this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
this.produced = Quantities.getQuantity(totalKWh, ENERGY_KILOWATT_HOUR);
this.producing = Quantities.getQuantity(totalW, Units.WATT);
this.produced0 = Quantities.getQuantity(string0KWh, ENERGY_KILOWATT_HOUR);
this.producing0 = Quantities.getQuantity(string0W, Units.WATT);
this.produced1 = Quantities.getQuantity(string1KWh, ENERGY_KILOWATT_HOUR);
this.producing1 = Quantities.getQuantity(string1W, Units.WATT);
}
}
}

View File

@ -0,0 +1,73 @@
package de.ph87.home.receive;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.ph87.home.meter.value.MeterValueInbound;
import de.ph87.home.unit.PatrixUnit;
import de.ph87.home.varying.VaryingValueInbound;
import de.ph87.patrix.mqtt.MqttEvent;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import tech.units.indriya.quantity.Quantities;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@Slf4j
@Service
@RequiredArgsConstructor
public class PatrixJsonHandler {
private final ObjectMapper objectMapper;
private final ApplicationEventPublisher applicationEventPublisher;
@EventListener
public void handle(@NonNull final MqttEvent event) throws Exception {
if (!event.topic.endsWith("/PatrixJson")) {
return;
}
final Inbound inbound = objectMapper.readValue(event.payload, Inbound.class);
applicationEventPublisher.publishEvent(inbound);
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(name = "VARYING", value = Varying.class),
@JsonSubTypes.Type(name = "METER", value = Meter.class),
})
private interface Inbound {
enum Type {
VARYING,
METER,
}
}
@ToString(callSuper = true)
public static class Varying extends VaryingValueInbound implements Inbound {
protected Varying(@NonNull final String name, final long date, final double value, @NonNull final PatrixUnit unit) {
super(name, ZonedDateTime.ofInstant(Instant.ofEpochSecond(date), ZoneId.systemDefault()), Quantities.getQuantity(value, unit.unit));
}
}
@ToString(callSuper = true)
public static class Meter extends MeterValueInbound implements Inbound {
protected Meter(@NonNull final String name, @NonNull final String number, final long date, final double value, @NonNull final PatrixUnit unit) {
super(name, number, ZonedDateTime.ofInstant(Instant.ofEpochSecond(date), ZoneId.systemDefault()), Quantities.getQuantity(value, unit.unit));
}
}
}

View File

@ -0,0 +1,86 @@
package de.ph87.home.receive;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.ph87.home.meter.value.MeterValueInbound;
import de.ph87.home.varying.VaryingValueInbound;
import de.ph87.patrix.mqtt.MqttEvent;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import tech.units.indriya.ComparableQuantity;
import tech.units.indriya.quantity.Quantities;
import javax.measure.quantity.Energy;
import javax.measure.quantity.Power;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import static de.ph87.home.unit.UnitHelper.*;
import static tech.units.indriya.unit.Units.WATT;
@Slf4j
@Service
@RequiredArgsConstructor
public class SmartMeterHandler {
private final ObjectMapper objectMapper;
private final ApplicationEventPublisher applicationEventPublisher;
@EventListener
public void handle(@NonNull final MqttEvent event) throws Exception {
if (!"electricity/grid/json".equals(event.topic)) {
return;
}
final Inbound inbound = objectMapper.readValue(event.payload, Inbound.class);
applicationEventPublisher.publishEvent(new MeterValueInbound("electricity/purchased", inbound.number, inbound.date, inbound.purchased));
applicationEventPublisher.publishEvent(new MeterValueInbound("electricity/delivered", inbound.number, inbound.date, inbound.delivered));
applicationEventPublisher.publishEvent(new VaryingValueInbound("electricity/purchasing", inbound.date, inbound.purchasing));
applicationEventPublisher.publishEvent(new VaryingValueInbound("electricity/delivering", inbound.date, inbound.delivering));
}
@Getter
@ToString
public static class Inbound {
@NonNull
public String number = "1ZPA0020300305"; // TODO implement in smart-meter firmware
@NonNull
public final ZonedDateTime date;
public final ComparableQuantity<Energy> purchased;
@NonNull
public final ComparableQuantity<Energy> delivered;
@NonNull
public final ComparableQuantity<Power> purchasing;
@NonNull
public final ComparableQuantity<Power> delivering;
public Inbound(
final long timestamp,
@NonNull final BigDecimal purchaseWh,
@NonNull final BigDecimal deliveryWh,
@NonNull final BigDecimal powerW
) {
this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
this.purchased = Quantities.getQuantity(purchaseWh, ENERGY_WATT_HOUR);
this.delivered = Quantities.getQuantity(deliveryWh, ENERGY_WATT_HOUR);
final ComparableQuantity<Power> power = Quantities.getQuantity(powerW, WATT);
this.purchasing = zeroIfNegative(power);
this.delivering = zeroIfNegative(negate(power));
}
}
}

View File

@ -0,0 +1,49 @@
package de.ph87.home.series;
import de.ph87.home.unit.PatrixUnit;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class Series {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Version
@ToString.Exclude
private long version;
@NonNull
@Column(nullable = false)
private String name;
@NonNull
@Column(nullable = false)
private String title;
@NonNull
@Column(nullable = false, updatable = false)
@Enumerated(EnumType.STRING)
private Type type;
@NonNull
@Column(nullable = false, updatable = false)
@Enumerated(EnumType.STRING)
private PatrixUnit unit;
public Series(@NonNull final String name, @NonNull final Type type, @NonNull final PatrixUnit unit) {
this.name = name;
this.title = name;
this.type = type;
this.unit = unit;
}
}

View File

@ -0,0 +1,32 @@
package de.ph87.home.series;
import de.ph87.home.unit.PatrixUnit;
import lombok.Data;
import lombok.NonNull;
@Data
public class SeriesDto {
public final long id;
@NonNull
public final String name;
@NonNull
public final String title;
@NonNull
private final Type type;
@NonNull
public final PatrixUnit unit;
public SeriesDto(@NonNull final Series series) {
this.id = series.getId();
this.name = series.getName();
this.title = series.getTitle();
this.type = series.getType();
this.unit = series.getUnit();
}
}

View File

@ -0,0 +1,16 @@
package de.ph87.home.series;
import lombok.NonNull;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.ListCrudRepository;
import java.util.Optional;
public interface SeriesRepository extends ListCrudRepository<Series, Long> {
Optional<Series> findByName(@NonNull String name);
@Query("select new de.ph87.home.series.SeriesDto(s) from Series s where s.name = :name")
Optional<SeriesDto> findDtoByName(@NonNull String name);
}

View File

@ -0,0 +1,29 @@
package de.ph87.home.series;
import de.ph87.home.unit.PatrixUnit;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.measure.Unit;
@Slf4j
@Service
@RequiredArgsConstructor
public class SeriesService {
private final SeriesRepository seriesRepository;
@NonNull
@Transactional
public Series getOrCreate(@NonNull final String name, final @NonNull Type type, @NonNull final Unit<?> unit) {
return seriesRepository.findByName(name).orElseGet(() -> {
final Series fresh = new Series(name, type, PatrixUnit.from(unit));
log.info("Series CREATED: {}", fresh);
return seriesRepository.save(fresh);
});
}
}

View File

@ -0,0 +1,6 @@
package de.ph87.home.series;
public enum Type {
VARYING,
METER,
}

View File

@ -0,0 +1,32 @@
package de.ph87.home.unit;
import lombok.Data;
import lombok.NonNull;
import java.time.ZonedDateTime;
@Data
public class Aligned {
@NonNull
public final ZonedDateTime date;
@NonNull
public final Interval interval;
public Aligned(@NonNull final ZonedDateTime date, @NonNull final Interval interval) {
this.date = interval.align.apply(date);
this.interval = interval;
}
public Aligned(@NonNull final Aligned aligned, final long plusAmount) {
this.date = aligned.interval.plus.apply(aligned.date, plusAmount);
this.interval = aligned.interval;
}
@NonNull
public Aligned minus(final long amount) {
return new Aligned(this, -amount);
}
}

View File

@ -0,0 +1,29 @@
package de.ph87.home.unit;
import lombok.NonNull;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.function.BiFunction;
import java.util.function.Function;
public enum Interval {
FIVE(date -> date.truncatedTo(ChronoUnit.MINUTES).minusMinutes(date.getMinute() % 5), (date, amount) -> date.plusMinutes(5 * amount)),
HOUR(date -> date.truncatedTo(ChronoUnit.HOURS), ZonedDateTime::plusHours),
DAY(date -> date.truncatedTo(ChronoUnit.DAYS), ZonedDateTime::plusDays),
WEEK(date -> date.truncatedTo(ChronoUnit.DAYS).minusDays(date.getDayOfWeek().getValue() - 1), (date, amount) -> date.plusDays(7 * amount)),
MONTH(date -> date.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1), ZonedDateTime::plusMonths),
YEAR(date -> date.truncatedTo(ChronoUnit.DAYS).withDayOfYear(1), ZonedDateTime::plusYears),
;
@NonNull
public final Function<ZonedDateTime, ZonedDateTime> align;
public final BiFunction<ZonedDateTime, Long, ZonedDateTime> plus;
Interval(@NonNull final Function<ZonedDateTime, ZonedDateTime> align, final BiFunction<ZonedDateTime, Long, ZonedDateTime> plus) {
this.align = align;
this.plus = plus;
}
}

View File

@ -0,0 +1,45 @@
package de.ph87.home.unit;
import lombok.NonNull;
import tech.units.indriya.AbstractUnit;
import tech.units.indriya.unit.Units;
import javax.measure.Quantity;
import javax.measure.Unit;
import java.util.Arrays;
import static de.ph87.home.unit.UnitHelper.ENERGY_KILOWATT_HOUR;
public enum PatrixUnit {
BOOLEAN(AbstractUnit.ONE),
VOLUME_L(Units.LITRE),
VOLUME_MM3(Units.LITRE.divide(1000000)),
LENGTH_CM(Units.METRE.divide(100)),
POWER_W(Units.WATT),
ENERGY_KWH(ENERGY_KILOWATT_HOUR),
// TODO
IAQ(AbstractUnit.ONE),
IAQ_CO2_EQUIVALENT(AbstractUnit.ONE),
IAQ_VOC_EQUIVALENT(AbstractUnit.ONE),
PRESSURE_HPA(AbstractUnit.ONE),
TEMPERATURE_C(Units.CELSIUS),
HUMIDITY_RELATIVE_PERCENT(AbstractUnit.ONE),
HUMIDITY_ABSOLUTE_GM3(AbstractUnit.ONE),
SUN_DC(AbstractUnit.ONE),
;
@NonNull
public final Unit<? extends Quantity<?>> unit;
<T extends Quantity<T>> PatrixUnit(@NonNull final Unit<T> unit) {
this.unit = unit;
}
@NonNull
public static PatrixUnit from(@NonNull final Unit<?> unit) {
return Arrays.stream(values()).filter(u -> u.unit.isCompatible(unit)).findFirst().orElseThrow();
}
}

View File

@ -0,0 +1,28 @@
package de.ph87.home.unit;
import jakarta.annotation.Nullable;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import tech.units.indriya.format.SimpleQuantityFormat;
import javax.measure.Quantity;
import javax.measure.format.QuantityFormat;
@Converter(autoApply = true)
public class QuantityConverter implements AttributeConverter<Quantity<?>, String> {
public static final QuantityFormat FORMAT = SimpleQuantityFormat.getInstance();
@Nullable
@Override
public String convertToDatabaseColumn(@Nullable Quantity<?> quantity) {
return quantity == null ? null : FORMAT.format(quantity);
}
@Nullable
@Override
public Quantity<?> convertToEntityAttribute(@Nullable String string) {
return string == null ? null : FORMAT.parse(string);
}
}

View File

@ -0,0 +1,41 @@
package de.ph87.home.unit;
import lombok.NonNull;
import tech.units.indriya.ComparableQuantity;
import tech.units.indriya.quantity.Quantities;
import javax.measure.MetricPrefix;
import javax.measure.Quantity;
import javax.measure.Unit;
import javax.measure.quantity.Energy;
import static tech.units.indriya.unit.Units.HOUR;
import static tech.units.indriya.unit.Units.WATT;
public class UnitHelper {
public static final Unit<Energy> ENERGY_WATT_HOUR = WATT.multiply(HOUR).asType(Energy.class);
public static final Unit<Energy> ENERGY_KILOWATT_HOUR = MetricPrefix.KILO(ENERGY_WATT_HOUR);
@NonNull
public static <T extends Quantity<T>> ComparableQuantity<T> zeroIfNegative(final ComparableQuantity<T> value) {
final ComparableQuantity<T> zero = Quantities.getQuantity(0, value.getUnit());
return value.isGreaterThanOrEqualTo(zero) ? value : zero;
}
@NonNull
public static <T extends Quantity<T>> ComparableQuantity<T> negate(final ComparableQuantity<T> value) {
return value.multiply(-1);
}
@NonNull
public static <Q extends Quantity<Q>> ComparableQuantity<Q> convert(@NonNull final ComparableQuantity<?> value, @NonNull final PatrixUnit unit) {
if (!value.getUnit().isCompatible(unit.unit)) {
throw new RuntimeException("Incompatible units: value=%s, targetUnit=%s".formatted(value, unit));
}
//noinspection unchecked
return ((ComparableQuantity<Q>) value).to((Unit<Q>) unit.unit);
}
}

View File

@ -0,0 +1,25 @@
package de.ph87.home.varying;
import de.ph87.home.point.PointResult;
import lombok.Getter;
import lombok.NonNull;
import java.time.ZonedDateTime;
@Getter
public class VaryingPoint extends PointResult.Point {
public final double min;
public final double max;
public final double avg;
public VaryingPoint(@NonNull final ZonedDateTime date, final double min, final double max, final double avg) {
super(date);
this.min = min;
this.max = max;
this.avg = avg;
}
}

View File

@ -0,0 +1,80 @@
package de.ph87.home.varying;
import de.ph87.home.series.Series;
import de.ph87.home.unit.Aligned;
import de.ph87.home.unit.Interval;
import jakarta.persistence.*;
import lombok.*;
import tech.units.indriya.ComparableQuantity;
import java.time.ZonedDateTime;
import static de.ph87.home.unit.UnitHelper.convert;
@Getter
@ToString
@MappedSuperclass
@NoArgsConstructor
public abstract class VaryingValue {
@EmbeddedId
private Id id;
@Column(nullable = false)
private double min;
@Column(nullable = false)
private double max;
@Column(nullable = false)
private double avg;
@Column(nullable = false)
private long count;
protected VaryingValue(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
this.id = id;
final double converted = convert(value, id.series.getUnit()).getValue().doubleValue();
this.min = converted;
this.max = converted;
this.avg = converted;
}
public void update(@NonNull final ComparableQuantity<?> value) {
final double converted = convert(value, id.series.getUnit()).getValue().doubleValue();
min = Math.min(min, converted);
max = Math.max(max, converted);
avg = (avg * count + converted) / (count + 1);
count++;
}
@Getter
@ToString
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public static class Id {
@NonNull
@ToString.Exclude
@ManyToOne(optional = false)
private Series series;
@NonNull
@ToString.Include
public String series() {
return series.getName();
}
@NonNull
@Column(nullable = false, updatable = false)
private ZonedDateTime date;
public Id(@NonNull final Interval interval, @NonNull final ZonedDateTime date, @NonNull final Series series) {
this.date = new Aligned(date, interval).date;
this.series = series;
}
}
}

View File

@ -0,0 +1,33 @@
package de.ph87.home.varying;
import lombok.Data;
import lombok.NonNull;
import tech.units.indriya.ComparableQuantity;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@Data
public class VaryingValueInbound {
@NonNull
public final String name;
@NonNull
public final ZonedDateTime date;
@NonNull
public final ComparableQuantity<?> value;
public VaryingValueInbound(@NonNull final String name, final long date, @NonNull final ComparableQuantity<?> value) {
this(name, ZonedDateTime.ofInstant(Instant.ofEpochSecond(date), ZoneId.systemDefault()), value);
}
public VaryingValueInbound(@NonNull final String name, @NonNull final ZonedDateTime date, @NonNull final ComparableQuantity<?> value) {
this.name = name;
this.date = date;
this.value = value;
}
}

View File

@ -0,0 +1,76 @@
package de.ph87.home.varying;
import de.ph87.home.series.Series;
import de.ph87.home.series.SeriesService;
import de.ph87.home.series.Type;
import de.ph87.home.unit.Interval;
import de.ph87.home.varying.day.VaryingDay;
import de.ph87.home.varying.day.VaryingDayRepository;
import de.ph87.home.varying.five.VaryingFive;
import de.ph87.home.varying.five.VaryingFiveRepository;
import de.ph87.home.varying.hour.VaryingHour;
import de.ph87.home.varying.hour.VaryingHourRepository;
import de.ph87.home.varying.month.VaryingMonth;
import de.ph87.home.varying.month.VaryingMonthRepository;
import de.ph87.home.varying.week.VaryingWeek;
import de.ph87.home.varying.week.VaryingWeekRepository;
import de.ph87.home.varying.year.VaryingYear;
import de.ph87.home.varying.year.VaryingYearRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.units.indriya.ComparableQuantity;
import java.util.function.BiFunction;
@Slf4j
@Service
@RequiredArgsConstructor
public class VaryingValueReceiver {
private final VaryingFiveRepository varyingFiveRepository;
private final VaryingHourRepository varyingHourRepository;
private final VaryingDayRepository varyingDayRepository;
private final VaryingWeekRepository varyingWeekRepository;
private final VaryingMonthRepository varyingMonthRepository;
private final VaryingYearRepository varyingYearRepository;
private final SeriesService seriesService;
@Transactional
@EventListener
public void onVaryingInbound(@NonNull final VaryingValueInbound event) {
final Series series = seriesService.getOrCreate(event.getName(), Type.VARYING, event.value.getUnit());
updateOrCreate(event, series, Interval.FIVE, VaryingFive::new, varyingFiveRepository);
updateOrCreate(event, series, Interval.HOUR, VaryingHour::new, varyingHourRepository);
updateOrCreate(event, series, Interval.DAY, VaryingDay::new, varyingDayRepository);
updateOrCreate(event, series, Interval.WEEK, VaryingWeek::new, varyingWeekRepository);
updateOrCreate(event, series, Interval.MONTH, VaryingMonth::new, varyingMonthRepository);
updateOrCreate(event, series, Interval.YEAR, VaryingYear::new, varyingYearRepository);
}
private <T extends VaryingValue> void updateOrCreate(
@NonNull final VaryingValueInbound event,
@NonNull final Series series,
@NonNull final Interval interval,
@NonNull final BiFunction<VaryingValue.Id, ComparableQuantity<?>, T> create,
@NonNull final VaryingValueRepository<T> repository
) {
final VaryingYear.Id id = new VaryingValue.Id(interval, event.getDate(), series);
repository
.findById(id)
.ifPresentOrElse(
existing -> existing.update(event.getValue()),
() -> repository.save(create.apply(id, event.getValue()))
);
}
}

View File

@ -0,0 +1,17 @@
package de.ph87.home.varying;
import lombok.NonNull;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import java.time.ZonedDateTime;
import java.util.List;
@NoRepositoryBean
public interface VaryingValueRepository<T extends VaryingValue> extends ListCrudRepository<T, VaryingValue.Id> {
@Query("select new de.ph87.home.varying.VaryingPoint(v.id.date, v.min, v.max, v.avg) from #{#entityName} v where v.id.series.id = :id and v.id.date >= :begin and v.id.date < :end")
List<VaryingPoint> points(@NonNull final long seriesId, @NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end);
}

View File

@ -0,0 +1,45 @@
package de.ph87.home.varying;
import de.ph87.home.point.PointRequest;
import de.ph87.home.series.SeriesDto;
import de.ph87.home.varying.day.VaryingDayRepository;
import de.ph87.home.varying.five.VaryingFiveRepository;
import de.ph87.home.varying.hour.VaryingHourRepository;
import de.ph87.home.varying.month.VaryingMonthRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class VaryingValueService {
private final VaryingFiveRepository varyingFiveRepository;
private final VaryingHourRepository varyingHourRepository;
private final VaryingDayRepository varyingDayRepository;
private final de.ph87.home.varying.week.VaryingWeekRepository varyingWeekRepository;
private final VaryingMonthRepository varyingMonthRepository;
private final de.ph87.home.varying.year.VaryingYearRepository varyingYearRepository;
@NonNull
public List<VaryingPoint> points(@NonNull final SeriesDto series, @NonNull final PointRequest request) {
return switch (request.getInner()) {
case FIVE -> varyingFiveRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case HOUR -> varyingHourRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case DAY -> varyingDayRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case WEEK -> varyingWeekRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case MONTH -> varyingMonthRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
case YEAR -> varyingYearRepository.points(series.id, request.beginIncluding.date, request.endIncluding.date);
};
}
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.varying.day;
import de.ph87.home.varying.VaryingValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class VaryingDay extends VaryingValue {
public VaryingDay(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.varying.day;
import de.ph87.home.varying.VaryingValueRepository;
public interface VaryingDayRepository extends VaryingValueRepository<VaryingDay> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.varying.five;
import de.ph87.home.varying.VaryingValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class VaryingFive extends VaryingValue {
public VaryingFive(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.varying.five;
import de.ph87.home.varying.VaryingValueRepository;
public interface VaryingFiveRepository extends VaryingValueRepository<VaryingFive> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.varying.hour;
import de.ph87.home.varying.VaryingValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class VaryingHour extends VaryingValue {
public VaryingHour(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.varying.hour;
import de.ph87.home.varying.VaryingValueRepository;
public interface VaryingHourRepository extends VaryingValueRepository<VaryingHour> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.varying.month;
import de.ph87.home.varying.VaryingValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class VaryingMonth extends VaryingValue {
public VaryingMonth(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.varying.month;
import de.ph87.home.varying.VaryingValueRepository;
public interface VaryingMonthRepository extends VaryingValueRepository<VaryingMonth> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.varying.week;
import de.ph87.home.varying.VaryingValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class VaryingWeek extends VaryingValue {
public VaryingWeek(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.varying.week;
import de.ph87.home.varying.VaryingValueRepository;
public interface VaryingWeekRepository extends VaryingValueRepository<VaryingWeek> {
}

View File

@ -0,0 +1,21 @@
package de.ph87.home.varying.year;
import de.ph87.home.varying.VaryingValue;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import tech.units.indriya.ComparableQuantity;
@Entity
@Getter
@NoArgsConstructor
@ToString(callSuper = true)
public class VaryingYear extends VaryingValue {
public VaryingYear(@NonNull final Id id, @NonNull final ComparableQuantity<?> value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.home.varying.year;
import de.ph87.home.varying.VaryingValueRepository;
public interface VaryingYearRepository extends VaryingValueRepository<VaryingYear> {
}

View File

@ -0,0 +1,10 @@
logging.level.root=WARN
logging.level.de.ph87=INFO
#-
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false
#-
spring.jackson.serialization.indent_output=true
#-
spring.main.banner-mode=off