Meter
This commit is contained in:
parent
e005f1ef8c
commit
963648765f
@ -4,9 +4,9 @@ import com.fasterxml.jackson.annotation.*;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import com.fasterxml.jackson.databind.annotation.*;
|
||||
import de.ph87.data.message.*;
|
||||
import de.ph87.data.series.*;
|
||||
import de.ph87.data.series.entry.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import de.ph87.data.value.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
import org.springframework.stereotype.*;
|
||||
@ -36,15 +36,15 @@ public class EspHomeHandler implements IMessageHandler {
|
||||
final String area = matcher.group("area");
|
||||
final String property = propertyReplace(matcher.group("property"));
|
||||
final String name = "%s/%s".formatted(area, property.replace("_", "/"));
|
||||
final Value.Unit targetUnit = switch (property) {
|
||||
case "iaq" -> Value.Unit.IAQ;
|
||||
case "iaq_co2" -> Value.Unit.IAQ_CO2_EQUIVALENT;
|
||||
case "iaq_voc" -> Value.Unit.IAQ_VOC_EQUIVALENT;
|
||||
case "pressure" -> Value.Unit.PRESSURE_HPA;
|
||||
case "temperature" -> Value.Unit.TEMPERATURE_C;
|
||||
case "humidity_relative" -> Value.Unit.HUMIDITY_RELATIVE_PERCENT;
|
||||
case "humidity_absolute" -> Value.Unit.HUMIDITY_ABSOLUTE_GM3;
|
||||
case "sun" -> Value.Unit.SUN_DC;
|
||||
final Unit targetUnit = switch (property) {
|
||||
case "iaq" -> Unit.IAQ;
|
||||
case "iaq_co2" -> Unit.IAQ_CO2_EQUIVALENT;
|
||||
case "iaq_voc" -> Unit.IAQ_VOC_EQUIVALENT;
|
||||
case "pressure" -> Unit.PRESSURE_HPA;
|
||||
case "temperature" -> Unit.TEMPERATURE_C;
|
||||
case "humidity_relative" -> Unit.HUMIDITY_RELATIVE_PERCENT;
|
||||
case "humidity_absolute" -> Unit.HUMIDITY_ABSOLUTE_GM3;
|
||||
case "sun" -> Unit.SUN_DC;
|
||||
default -> null;
|
||||
};
|
||||
if (targetUnit == null) {
|
||||
@ -52,13 +52,13 @@ public class EspHomeHandler implements IMessageHandler {
|
||||
return;
|
||||
}
|
||||
final Inbound inbound = objectMapper.readValue(message.payload, Inbound.class);
|
||||
final Value.Unit unitFromPayload = inbound.units.stream().filter(ufp -> ufp.base == targetUnit.base).findFirst().orElse(null);
|
||||
final Unit unitFromPayload = inbound.units.stream().filter(ufp -> ufp.base == targetUnit.base).findFirst().orElse(null);
|
||||
if (unitFromPayload == null) {
|
||||
log.error("Unit mismatch: fromTopic={}, fromPayload=[{}]", targetUnit, inbound.getUnits().stream().map(Enum::name).collect(Collectors.joining(",")));
|
||||
return;
|
||||
}
|
||||
final Value value = new Value(inbound.value, unitFromPayload);
|
||||
entryService.receive(new SeriesInbound(name, inbound.date, value.as(targetUnit)));
|
||||
entryService.receive(name, inbound.date, value.as(targetUnit));
|
||||
}
|
||||
|
||||
private String propertyReplace(final String property) {
|
||||
@ -81,10 +81,10 @@ public class EspHomeHandler implements IMessageHandler {
|
||||
public final double value;
|
||||
|
||||
@NonNull
|
||||
@JsonDeserialize(using = Value.Unit.ListDeserializer.class)
|
||||
public final List<Value.Unit> units;
|
||||
@JsonDeserialize(using = Unit.ListDeserializer.class)
|
||||
public final List<Unit> units;
|
||||
|
||||
public Inbound(final long timestamp, final double value, @JsonProperty("unit") @NonNull final List<Value.Unit> units) {
|
||||
public Inbound(final long timestamp, final double value, @JsonProperty("unit") @NonNull final List<Unit> units) {
|
||||
this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
|
||||
this.value = value;
|
||||
this.units = units;
|
||||
|
||||
@ -2,9 +2,9 @@ package de.ph87.data.message.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import de.ph87.data.message.*;
|
||||
import de.ph87.data.series.*;
|
||||
import de.ph87.data.series.entry.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import de.ph87.data.value.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
import org.springframework.stereotype.*;
|
||||
@ -31,7 +31,7 @@ public class HeizungHandler implements IMessageHandler {
|
||||
}
|
||||
final String property = matcher.group("property");
|
||||
final Inbound inbound = objectMapper.readValue(message.payload, Inbound.class);
|
||||
entryService.receive(new SeriesInbound(property, inbound.date, inbound.value));
|
||||
entryService.receive(property, inbound.date, inbound.value);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ -45,7 +45,7 @@ public class HeizungHandler implements IMessageHandler {
|
||||
|
||||
public Inbound(final long timestamp, final double sum, final int count) {
|
||||
this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
|
||||
this.value = new Value(sum / count, Value.Unit.TEMPERATURE_C);
|
||||
this.value = new Value(sum / count, Unit.TEMPERATURE_C);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,9 +2,10 @@ package de.ph87.data.message.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import de.ph87.data.message.*;
|
||||
import de.ph87.data.series.*;
|
||||
import de.ph87.data.meter.*;
|
||||
import de.ph87.data.series.entry.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import de.ph87.data.value.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
import org.springframework.stereotype.*;
|
||||
@ -16,23 +17,27 @@ import java.time.*;
|
||||
@RequiredArgsConstructor
|
||||
public class OpenDTUHandler implements IMessageHandler {
|
||||
|
||||
private static final String METER_NUMBER = "114190515175";
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final EntryService entryService;
|
||||
|
||||
private final MeterService meterService;
|
||||
|
||||
@Override
|
||||
public void handle(final @NonNull Message message) throws Exception {
|
||||
if (!"openDTU/pv/patrix/json".equals(message.topic)) {
|
||||
return;
|
||||
}
|
||||
final Inbound inbound = objectMapper.readValue(message.payload, Inbound.class);
|
||||
entryService.receive(new SeriesInbound("electricity/energy/produced", inbound.date, inbound.energy.as(Value.Unit.ENERGY_KWH)));
|
||||
entryService.receive(new SeriesInbound("electricity/power/produced", inbound.date, inbound.power.as(Value.Unit.POWER_W)));
|
||||
entryService.receive("power/produced", inbound.date, inbound.power.as(Unit.POWER_W));
|
||||
meterService.receive("energy/produced", METER_NUMBER, inbound.date, inbound.energy);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
private static class Inbound {
|
||||
public static class Inbound {
|
||||
|
||||
@NonNull
|
||||
public final ZonedDateTime date;
|
||||
@ -43,8 +48,8 @@ public class OpenDTUHandler implements IMessageHandler {
|
||||
|
||||
public Inbound(final long timestamp, final double energyProducedKWh, final double powerW) {
|
||||
this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
|
||||
this.energy = new Value(energyProducedKWh, Value.Unit.ENERGY_KWH);
|
||||
this.power = new Value(powerW, Value.Unit.POWER_W);
|
||||
this.energy = new Value(energyProducedKWh, Unit.ENERGY_KWH);
|
||||
this.power = new Value(powerW, Unit.POWER_W);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,9 +2,9 @@ package de.ph87.data.message.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import de.ph87.data.message.*;
|
||||
import de.ph87.data.series.*;
|
||||
import de.ph87.data.series.entry.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import de.ph87.data.value.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
import org.springframework.stereotype.*;
|
||||
@ -26,7 +26,7 @@ public class SimpleJsonHandler implements IMessageHandler {
|
||||
return;
|
||||
}
|
||||
final Inbound inbound = objectMapper.readValue(message.payload, Inbound.class);
|
||||
entryService.receive(new SeriesInbound(inbound.name, inbound.date, inbound.value));
|
||||
entryService.receive(inbound.name, inbound.date, inbound.value);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ -41,7 +41,7 @@ public class SimpleJsonHandler implements IMessageHandler {
|
||||
|
||||
public final Value value;
|
||||
|
||||
public Inbound(@NonNull final String name, final long timestamp, final double value, @NonNull final Value.Unit unit) {
|
||||
public Inbound(@NonNull final String name, final long timestamp, final double value, @NonNull final Unit unit) {
|
||||
this.name = name;
|
||||
this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
|
||||
this.value = new Value(value, unit);
|
||||
|
||||
@ -2,9 +2,10 @@ package de.ph87.data.message.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import de.ph87.data.message.*;
|
||||
import de.ph87.data.series.*;
|
||||
import de.ph87.data.meter.*;
|
||||
import de.ph87.data.series.entry.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import de.ph87.data.value.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
import org.springframework.stereotype.*;
|
||||
@ -16,24 +17,28 @@ import java.time.*;
|
||||
@RequiredArgsConstructor
|
||||
public class SmartMeterHandler implements IMessageHandler {
|
||||
|
||||
private static final String METER_NUMBER = "1ZPA0020300305";
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final EntryService entryService;
|
||||
|
||||
private final MeterService meterService;
|
||||
|
||||
@Override
|
||||
public void handle(@NonNull final Message message) throws Exception {
|
||||
if (!"electricity/grid/json".equals(message.topic)) {
|
||||
return;
|
||||
}
|
||||
final Inbound inbound = objectMapper.readValue(message.payload, Inbound.class);
|
||||
entryService.receive(new SeriesInbound("electricity/energy/purchased", inbound.date, inbound.energyPurchased.as(Value.Unit.ENERGY_KWH)));
|
||||
entryService.receive(new SeriesInbound("electricity/energy/delivered", inbound.date, inbound.energyDelivered.as(Value.Unit.ENERGY_KWH)));
|
||||
entryService.receive(new SeriesInbound("electricity/power/difference", inbound.date, inbound.powerDifference.as(Value.Unit.POWER_W)));
|
||||
entryService.receive("power/balance", inbound.date, inbound.powerBalance.as(Unit.POWER_W));
|
||||
meterService.receive("energy/purchased", METER_NUMBER, inbound.date, inbound.energyPurchased);
|
||||
meterService.receive("energy/delivered", METER_NUMBER, inbound.date, inbound.energyDelivered);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
private static class Inbound {
|
||||
public static class Inbound {
|
||||
|
||||
@NonNull
|
||||
public final ZonedDateTime date;
|
||||
@ -42,13 +47,13 @@ public class SmartMeterHandler implements IMessageHandler {
|
||||
|
||||
public final Value energyDelivered;
|
||||
|
||||
public final Value powerDifference;
|
||||
public final Value powerBalance;
|
||||
|
||||
public Inbound(final long timestamp, final double purchaseWh, final double deliveryWh, final double powerW) {
|
||||
this.date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
|
||||
this.energyPurchased = new Value(purchaseWh, Value.Unit.ENERGY_WH);
|
||||
this.energyDelivered = new Value(deliveryWh, Value.Unit.ENERGY_WH);
|
||||
this.powerDifference = new Value(powerW, Value.Unit.POWER_W);
|
||||
this.energyPurchased = new Value(purchaseWh, Unit.ENERGY_WH).as(Unit.ENERGY_KWH);
|
||||
this.energyDelivered = new Value(deliveryWh, Unit.ENERGY_WH).as(Unit.ENERGY_KWH);
|
||||
this.powerBalance = new Value(powerW, Unit.POWER_W);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
40
src/main/java/de/ph87/data/meter/Meter.java
Normal file
40
src/main/java/de/ph87/data/meter/Meter.java
Normal file
@ -0,0 +1,40 @@
|
||||
package de.ph87.data.meter;
|
||||
|
||||
import de.ph87.data.series.*;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.*;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@Table(uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = {"series_id", "date"})
|
||||
})
|
||||
public class Meter {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private long id;
|
||||
|
||||
@NonNull
|
||||
@ManyToOne
|
||||
private Series series;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime date;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private String number;
|
||||
|
||||
public Meter(@NonNull final Series series, @NonNull final ZonedDateTime date, @NonNull final String number) {
|
||||
this.series = series;
|
||||
this.date = date;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
}
|
||||
12
src/main/java/de/ph87/data/meter/MeterRepository.java
Normal file
12
src/main/java/de/ph87/data/meter/MeterRepository.java
Normal file
@ -0,0 +1,12 @@
|
||||
package de.ph87.data.meter;
|
||||
|
||||
import de.ph87.data.series.*;
|
||||
import org.springframework.data.repository.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public interface MeterRepository extends ListCrudRepository<Meter, Long> {
|
||||
|
||||
Optional<Meter> findFirstBySeriesOrderByDateDesc(Series series);
|
||||
|
||||
}
|
||||
93
src/main/java/de/ph87/data/meter/MeterService.java
Normal file
93
src/main/java/de/ph87/data/meter/MeterService.java
Normal file
@ -0,0 +1,93 @@
|
||||
package de.ph87.data.meter;
|
||||
|
||||
import de.ph87.data.meter.day.*;
|
||||
import de.ph87.data.meter.five.*;
|
||||
import de.ph87.data.meter.month.*;
|
||||
import de.ph87.data.meter.week.*;
|
||||
import de.ph87.data.meter.year.*;
|
||||
import de.ph87.data.series.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
import org.springframework.stereotype.*;
|
||||
import org.springframework.transaction.annotation.*;
|
||||
|
||||
import java.time.*;
|
||||
import java.time.temporal.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class MeterService {
|
||||
|
||||
private final SeriesService seriesService;
|
||||
|
||||
private final MeterRepository meterRepository;
|
||||
|
||||
private final MeterDayRepository meterDayRepository;
|
||||
|
||||
private final MeterWeekRepository meterWeekRepository;
|
||||
|
||||
private final MeterMonthRepository meterMonthRepository;
|
||||
|
||||
private final MeterYearRepository meterYearRepository;
|
||||
|
||||
private final MeterFiveRepository meterFiveRepository;
|
||||
|
||||
public void receive(@NonNull final String seriesName, @NonNull final String meterNumber, @NonNull final ZonedDateTime date, @NonNull final Value value) {
|
||||
final Series series = seriesService.getOrCreate(seriesName, value.unit);
|
||||
final Meter meter = meterRepository.findFirstBySeriesOrderByDateDesc(series)
|
||||
.filter(m -> m.getNumber().equals(meterNumber))
|
||||
.orElseGet(() -> meterRepository.save(new Meter(series, date, meterNumber)));
|
||||
|
||||
final MeterFive.Id five = new MeterFive.Id(meter, date.truncatedTo(ChronoUnit.MINUTES).minusMinutes(date.getMinute() % 5));
|
||||
meterFiveRepository.findById(five).ifPresentOrElse(
|
||||
existing -> existing.update(value),
|
||||
() -> meterFiveRepository.save(new MeterFive(five, value))
|
||||
);
|
||||
|
||||
final MeterDay.Id day = new MeterDay.Id(meter, date.toLocalDate());
|
||||
meterDayRepository.findById(day).ifPresentOrElse(
|
||||
existing -> existing.update(value),
|
||||
() -> meterDayRepository.save(new MeterDay(day, value))
|
||||
);
|
||||
|
||||
final MeterWeek.Id week = new MeterWeek.Id(meter, toWeek(date));
|
||||
meterWeekRepository.findById(week).ifPresentOrElse(
|
||||
existing -> existing.update(value),
|
||||
() -> meterWeekRepository.save(new MeterWeek(week, value))
|
||||
);
|
||||
|
||||
final MeterMonth.Id month = new MeterMonth.Id(meter, toMonth(date));
|
||||
meterMonthRepository.findById(month).ifPresentOrElse(
|
||||
existing -> existing.update(value),
|
||||
() -> meterMonthRepository.save(new MeterMonth(month, value))
|
||||
);
|
||||
|
||||
final MeterYear.Id year = new MeterYear.Id(meter, toYear(date));
|
||||
meterYearRepository.findById(year).ifPresentOrElse(
|
||||
existing -> existing.update(value),
|
||||
() -> meterYearRepository.save(new MeterYear(year, value))
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static LocalDate toWeek(@NonNull final ZonedDateTime date) {
|
||||
final LocalDate t = date.toLocalDate();
|
||||
return t.minusDays(t.getDayOfWeek().getValue() - 1);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static LocalDate toMonth(@NonNull final ZonedDateTime date) {
|
||||
final LocalDate t = date.toLocalDate();
|
||||
return t.minusDays(t.getDayOfMonth() - 1);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static LocalDate toYear(@NonNull final ZonedDateTime date) {
|
||||
final LocalDate t = date.toLocalDate();
|
||||
return t.minusDays(t.getDayOfYear() - 1);
|
||||
}
|
||||
|
||||
}
|
||||
61
src/main/java/de/ph87/data/meter/day/MeterDay.java
Normal file
61
src/main/java/de/ph87/data/meter/day/MeterDay.java
Normal file
@ -0,0 +1,61 @@
|
||||
package de.ph87.data.meter.day;
|
||||
|
||||
import de.ph87.data.meter.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.*;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class MeterDay {
|
||||
|
||||
@EmbeddedId
|
||||
private Id id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double min;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double max;
|
||||
|
||||
public MeterDay(@NonNull final Id id, @NonNull final Value value) {
|
||||
this.id = id;
|
||||
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
this.max = this.min;
|
||||
}
|
||||
|
||||
public void update(@NonNull final Value value) {
|
||||
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
if (v < this.max) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
this.max = v;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@Embeddable
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
public static class Id {
|
||||
|
||||
@NonNull
|
||||
@ManyToOne
|
||||
private Meter meter;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private LocalDate date;
|
||||
|
||||
public Id(@NonNull final Meter meter, @NonNull final LocalDate date) {
|
||||
this.meter = meter;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package de.ph87.data.meter.day;
|
||||
|
||||
import org.springframework.data.repository.*;
|
||||
|
||||
public interface MeterDayRepository extends ListCrudRepository<MeterDay, MeterDay.Id> {
|
||||
|
||||
}
|
||||
61
src/main/java/de/ph87/data/meter/five/MeterFive.java
Normal file
61
src/main/java/de/ph87/data/meter/five/MeterFive.java
Normal file
@ -0,0 +1,61 @@
|
||||
package de.ph87.data.meter.five;
|
||||
|
||||
import de.ph87.data.meter.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.*;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class MeterFive {
|
||||
|
||||
@EmbeddedId
|
||||
private Id id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double min;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double max;
|
||||
|
||||
public MeterFive(@NonNull final Id id, @NonNull final Value value) {
|
||||
this.id = id;
|
||||
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
this.max = this.min;
|
||||
}
|
||||
|
||||
public void update(@NonNull final Value value) {
|
||||
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
if (v < this.max) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
this.max = v;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@Embeddable
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
public static class Id {
|
||||
|
||||
@NonNull
|
||||
@ManyToOne
|
||||
private Meter meter;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime date;
|
||||
|
||||
public Id(@NonNull final Meter meter, @NonNull final ZonedDateTime date) {
|
||||
this.meter = meter;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package de.ph87.data.meter.five;
|
||||
|
||||
import org.springframework.data.repository.*;
|
||||
|
||||
public interface MeterFiveRepository extends ListCrudRepository<MeterFive, MeterFive.Id> {
|
||||
|
||||
}
|
||||
61
src/main/java/de/ph87/data/meter/month/MeterMonth.java
Normal file
61
src/main/java/de/ph87/data/meter/month/MeterMonth.java
Normal file
@ -0,0 +1,61 @@
|
||||
package de.ph87.data.meter.month;
|
||||
|
||||
import de.ph87.data.meter.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.*;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class MeterMonth {
|
||||
|
||||
@EmbeddedId
|
||||
private Id id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double min;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double max;
|
||||
|
||||
public MeterMonth(@NonNull final Id id, @NonNull final Value value) {
|
||||
this.id = id;
|
||||
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
this.max = this.min;
|
||||
}
|
||||
|
||||
public void update(@NonNull final Value value) {
|
||||
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
if (v < this.max) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
this.max = v;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@Embeddable
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
public static class Id {
|
||||
|
||||
@NonNull
|
||||
@ManyToOne
|
||||
private Meter meter;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private LocalDate date;
|
||||
|
||||
public Id(@NonNull final Meter meter, @NonNull final LocalDate date) {
|
||||
this.meter = meter;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package de.ph87.data.meter.month;
|
||||
|
||||
import org.springframework.data.repository.*;
|
||||
|
||||
public interface MeterMonthRepository extends ListCrudRepository<MeterMonth, MeterMonth.Id> {
|
||||
|
||||
}
|
||||
61
src/main/java/de/ph87/data/meter/week/MeterWeek.java
Normal file
61
src/main/java/de/ph87/data/meter/week/MeterWeek.java
Normal file
@ -0,0 +1,61 @@
|
||||
package de.ph87.data.meter.week;
|
||||
|
||||
import de.ph87.data.meter.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.*;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class MeterWeek {
|
||||
|
||||
@EmbeddedId
|
||||
private Id id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double min;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double max;
|
||||
|
||||
public MeterWeek(@NonNull final Id id, @NonNull final Value value) {
|
||||
this.id = id;
|
||||
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
this.max = this.min;
|
||||
}
|
||||
|
||||
public void update(@NonNull final Value value) {
|
||||
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
if (v < this.max) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
this.max = v;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@Embeddable
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
public static class Id {
|
||||
|
||||
@NonNull
|
||||
@ManyToOne
|
||||
private Meter meter;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private LocalDate date;
|
||||
|
||||
public Id(@NonNull final Meter meter, @NonNull final LocalDate date) {
|
||||
this.meter = meter;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package de.ph87.data.meter.week;
|
||||
|
||||
import org.springframework.data.repository.*;
|
||||
|
||||
public interface MeterWeekRepository extends ListCrudRepository<MeterWeek, MeterWeek.Id> {
|
||||
|
||||
}
|
||||
61
src/main/java/de/ph87/data/meter/year/MeterYear.java
Normal file
61
src/main/java/de/ph87/data/meter/year/MeterYear.java
Normal file
@ -0,0 +1,61 @@
|
||||
package de.ph87.data.meter.year;
|
||||
|
||||
import de.ph87.data.meter.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.*;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class MeterYear {
|
||||
|
||||
@EmbeddedId
|
||||
private Id id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double min;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double max;
|
||||
|
||||
public MeterYear(@NonNull final Id id, @NonNull final Value value) {
|
||||
this.id = id;
|
||||
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
this.max = this.min;
|
||||
}
|
||||
|
||||
public void update(@NonNull final Value value) {
|
||||
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
|
||||
if (v < this.max) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
this.max = v;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@Embeddable
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
public static class Id {
|
||||
|
||||
@NonNull
|
||||
@ManyToOne
|
||||
private Meter meter;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private LocalDate date;
|
||||
|
||||
public Id(@NonNull final Meter meter, @NonNull final LocalDate date) {
|
||||
this.meter = meter;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package de.ph87.data.meter.year;
|
||||
|
||||
import org.springframework.data.repository.*;
|
||||
|
||||
public interface MeterYearRepository extends ListCrudRepository<MeterYear, MeterYear.Id> {
|
||||
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package de.ph87.data.series;
|
||||
|
||||
import de.ph87.data.value.Value;
|
||||
import de.ph87.data.value.*;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
@ -24,17 +24,16 @@ public class Series {
|
||||
@Column(nullable = false, unique = true)
|
||||
private String title;
|
||||
|
||||
@Setter
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Value.Unit unit;
|
||||
private Unit unit;
|
||||
|
||||
@Setter
|
||||
@Column(nullable = false)
|
||||
private int decimals = 1;
|
||||
|
||||
public Series(@NonNull final String name, @NonNull final Value.Unit unit) {
|
||||
public Series(@NonNull final String name, @NonNull final Unit unit) {
|
||||
this.name = name;
|
||||
this.title = name;
|
||||
this.unit = unit;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package de.ph87.data.series;
|
||||
|
||||
import de.ph87.data.value.Value;
|
||||
import de.ph87.data.value.*;
|
||||
import jakarta.annotation.*;
|
||||
import lombok.*;
|
||||
|
||||
@ -14,7 +14,7 @@ public class SeriesDto {
|
||||
|
||||
public final String title;
|
||||
|
||||
public final Value.Unit unit;
|
||||
public final Unit unit;
|
||||
|
||||
public final int decimals;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package de.ph87.data.series;
|
||||
|
||||
import de.ph87.data.*;
|
||||
import de.ph87.data.value.Value;
|
||||
import de.ph87.data.value.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
import org.springframework.stereotype.*;
|
||||
@ -49,7 +49,7 @@ public class SeriesService {
|
||||
return new SeriesDto(series);
|
||||
}
|
||||
|
||||
public Series getOrCreate(@NonNull final String name, @NonNull final Value.Unit unit) {
|
||||
public Series getOrCreate(@NonNull final String name, @NonNull final Unit unit) {
|
||||
return seriesRepository
|
||||
.findByName(name)
|
||||
.orElseGet(() -> {
|
||||
|
||||
@ -12,33 +12,39 @@ import java.time.temporal.*;
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@Table(
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = {"series_id", "date"})
|
||||
}
|
||||
)
|
||||
public class Entry {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private long id;
|
||||
@EmbeddedId
|
||||
private EntryId id;
|
||||
|
||||
@NonNull
|
||||
@ManyToOne
|
||||
private Series series;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime date;
|
||||
|
||||
@Setter
|
||||
@Column(nullable = false, name = "`value`")
|
||||
private double value;
|
||||
|
||||
public Entry(@NonNull final Series series, @NonNull final ZonedDateTime date, final Value value) throws Value.Unit.NotConvertible {
|
||||
this.series = series;
|
||||
this.date = date.truncatedTo(ChronoUnit.MINUTES);
|
||||
this.value = value.as(series.getUnit()).value;
|
||||
public Entry(@NonNull final EntryId id, @NonNull final Value value) {
|
||||
this.id = id;
|
||||
this.value = value.as(id.getSeries().getUnit()).value;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@Embeddable
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
public static class EntryId {
|
||||
|
||||
@NonNull
|
||||
@ManyToOne
|
||||
private Series series;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime date;
|
||||
|
||||
public EntryId(@NonNull final Series series, final @NonNull ZonedDateTime date) {
|
||||
this.series = series;
|
||||
this.date = date.truncatedTo(ChronoUnit.MINUTES).minusMinutes(date.getMinute() % 5);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
package de.ph87.data.series.entry;
|
||||
|
||||
import de.ph87.data.series.*;
|
||||
import lombok.*;
|
||||
import org.springframework.data.repository.*;
|
||||
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
|
||||
public interface EntryRepository extends ListCrudRepository<Entry, Long> {
|
||||
public interface EntryRepository extends ListCrudRepository<Entry, Entry.EntryId> {
|
||||
|
||||
Optional<Entry> findBySeriesAndDate(@NonNull Series series, @NonNull ZonedDateTime truncated);
|
||||
Optional<Entry> findById(@NonNull Entry.EntryId id);
|
||||
|
||||
List<Entry> findAllBySeriesIdAndDateGreaterThanEqualAndDateLessThanEqual(long id, @NonNull ZonedDateTime begin, @NonNull ZonedDateTime end);
|
||||
List<Entry> findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(long id, @NonNull ZonedDateTime begin, @NonNull ZonedDateTime end);
|
||||
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import org.springframework.stereotype.*;
|
||||
import org.springframework.transaction.annotation.*;
|
||||
|
||||
import java.time.*;
|
||||
import java.time.temporal.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@ -20,17 +19,17 @@ public class EntryService {
|
||||
|
||||
private final SeriesService seriesService;
|
||||
|
||||
public void write(@NonNull final Series series, @NonNull final SeriesInbound measure) throws Value.Unit.NotConvertible {
|
||||
final ZonedDateTime truncated = measure.date.truncatedTo(ChronoUnit.MINUTES).minusMinutes(measure.date.getMinute() % 5);
|
||||
if (entryRepository.findBySeriesAndDate(series, truncated).isEmpty()) {
|
||||
final Entry created = entryRepository.save(new Entry(series, truncated, measure.value));
|
||||
public void receive(final String name, final @NonNull ZonedDateTime date, final Value value) {
|
||||
final Series series = seriesService.getOrCreate(name, value.unit);
|
||||
write(series, date, value);
|
||||
}
|
||||
|
||||
public void write(@NonNull final Series series, final @NonNull ZonedDateTime date, final Value value) {
|
||||
final Entry.EntryId id = new Entry.EntryId(series, date);
|
||||
if (entryRepository.findById(id).isEmpty()) {
|
||||
final Entry created = entryRepository.save(new Entry(id, value));
|
||||
log.debug("Created: {}", created);
|
||||
}
|
||||
}
|
||||
|
||||
public void receive(@NonNull final SeriesInbound measure) throws Value.Unit.NotConvertible {
|
||||
final Series series = seriesService.getOrCreate(measure.name, measure.value.unit);
|
||||
write(series, measure);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -145,7 +145,7 @@ public class Graph {
|
||||
|
||||
private Function<Entry, Point> toPoint(final long minuteMin, final double minuteScale, final double valueMin, final double valueScale) {
|
||||
return entry -> {
|
||||
final long minuteEpoch = entry.getDate().toEpochSecond() / 60;
|
||||
final long minuteEpoch = entry.getId().getDate().toEpochSecond() / 60;
|
||||
final long minuteRelative = minuteEpoch - minuteMin;
|
||||
final double minuteScaled = minuteRelative * minuteScale;
|
||||
final int x = (int) Math.round(minuteScaled);
|
||||
|
||||
@ -22,7 +22,7 @@ public class GraphService {
|
||||
|
||||
public Graph getGraph(final long seriesId, @NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end, final int width, final int height, final int border) {
|
||||
final SeriesDto series = seriesService.getDtoById(seriesId);
|
||||
final List<Entry> entries = entryRepository.findAllBySeriesIdAndDateGreaterThanEqualAndDateLessThanEqual(seriesId, begin, end);
|
||||
final List<Entry> entries = entryRepository.findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(seriesId, begin, end);
|
||||
return new Graph(series, entries, begin, end, width, height, border);
|
||||
}
|
||||
|
||||
|
||||
65
src/main/java/de/ph87/data/value/Unit.java
Normal file
65
src/main/java/de/ph87/data/value/Unit.java
Normal file
@ -0,0 +1,65 @@
|
||||
package de.ph87.data.value;
|
||||
|
||||
import com.fasterxml.jackson.core.*;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public enum Unit {
|
||||
TEMPERATURE_C("°C"),
|
||||
PRESSURE_HPA("hPa"),
|
||||
HUMIDITY_RELATIVE_PERCENT("%"),
|
||||
HUMIDITY_ABSOLUTE_MGL("mg/L"),
|
||||
HUMIDITY_ABSOLUTE_GM3("g/m³", 1, HUMIDITY_ABSOLUTE_MGL),
|
||||
ILLUMINANCE_LUX("lux"),
|
||||
RESISTANCE_OHMS("Ω"),
|
||||
ALTITUDE_M("m"),
|
||||
POWER_W("W"),
|
||||
POWER_KW("kW", 1000, POWER_W),
|
||||
ENERGY_WH("W"),
|
||||
ENERGY_KWH("kWh", 1000, ENERGY_WH),
|
||||
IAQ("IAQ"),
|
||||
IAQ_CO2_EQUIVALENT("ppm"),
|
||||
IAQ_VOC_EQUIVALENT("ppm"),
|
||||
SUN_DC("Δ°C"),
|
||||
UNIT_PERCENT("%"),
|
||||
;
|
||||
|
||||
public final String unit;
|
||||
|
||||
public final double factor;
|
||||
|
||||
public final Unit base;
|
||||
|
||||
Unit(@NonNull final String unit) {
|
||||
this.unit = unit;
|
||||
this.factor = 1.0;
|
||||
this.base = this;
|
||||
}
|
||||
|
||||
Unit(@NonNull final String unit, final double factor, @NonNull final Unit base) {
|
||||
this.unit = unit;
|
||||
this.factor = factor;
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
public static class NotConvertible extends RuntimeException {
|
||||
|
||||
public NotConvertible(final @NonNull Unit source, final Unit target) {
|
||||
super("Cannot convert Units: source=%s, target=%s".formatted(source, target));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ListDeserializer extends JsonDeserializer<List<Unit>> {
|
||||
|
||||
@Override
|
||||
public List<Unit> deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
|
||||
final String name = jsonParser.getValueAsString();
|
||||
return Arrays.stream(values()).filter(unit -> unit.unit.equals(name)).toList();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,7 @@
|
||||
package de.ph87.data.value;
|
||||
|
||||
import com.fasterxml.jackson.core.*;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class Value {
|
||||
|
||||
public final double value;
|
||||
@ -18,7 +13,7 @@ public class Value {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public Value as(@NonNull final Unit target) throws Unit.NotConvertible {
|
||||
public Value as(@NonNull final Unit target) {
|
||||
if (this.unit == target) {
|
||||
return this;
|
||||
}
|
||||
@ -28,61 +23,4 @@ public class Value {
|
||||
return new Value(value * this.unit.factor / target.factor, target);
|
||||
}
|
||||
|
||||
public enum Unit {
|
||||
TEMPERATURE_C("°C"),
|
||||
PRESSURE_HPA("hPa"),
|
||||
HUMIDITY_RELATIVE_PERCENT("%"),
|
||||
HUMIDITY_ABSOLUTE_MGL("mg/L"),
|
||||
HUMIDITY_ABSOLUTE_GM3("g/m³", 1, HUMIDITY_ABSOLUTE_MGL),
|
||||
ILLUMINANCE_LUX("lux"),
|
||||
RESISTANCE_OHMS("Ω"),
|
||||
ALTITUDE_M("m"),
|
||||
POWER_W("W"),
|
||||
POWER_KW("kW", 1000, POWER_W),
|
||||
ENERGY_WH("W"),
|
||||
ENERGY_KWH("kWh", 1000, ENERGY_WH),
|
||||
IAQ("IAQ"),
|
||||
IAQ_CO2_EQUIVALENT("ppm"),
|
||||
IAQ_VOC_EQUIVALENT("ppm"),
|
||||
SUN_DC("Δ°C"),
|
||||
UNIT_PERCENT("%"),
|
||||
;
|
||||
|
||||
public final String unit;
|
||||
|
||||
public final double factor;
|
||||
|
||||
public final Unit base;
|
||||
|
||||
Unit(@NonNull final String unit) {
|
||||
this.unit = unit;
|
||||
this.factor = 1.0;
|
||||
this.base = this;
|
||||
}
|
||||
|
||||
Unit(@NonNull final String unit, final double factor, @NonNull final Value.Unit base) {
|
||||
this.unit = unit;
|
||||
this.factor = factor;
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
public static class NotConvertible extends Exception {
|
||||
|
||||
public NotConvertible(final @NonNull Value.Unit source, final Unit target) {
|
||||
super("Cannot convert Units: source=%s, target=%s".formatted(source, target));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ListDeserializer extends JsonDeserializer<List<Unit>> {
|
||||
|
||||
@Override
|
||||
public List<Unit> deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
|
||||
final String name = jsonParser.getValueAsString();
|
||||
return Arrays.stream(values()).filter(unit -> unit.unit.equals(name)).toList();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user