diff --git a/src/main/java/de/ph87/data/DemoService.java b/src/main/java/de/ph87/data/DemoService.java index 668c1fa..5e8d34e 100644 --- a/src/main/java/de/ph87/data/DemoService.java +++ b/src/main/java/de/ph87/data/DemoService.java @@ -6,9 +6,11 @@ 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; @@ -64,6 +66,8 @@ public class DemoService { 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") @@ -75,6 +79,8 @@ public class DemoService { 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", TopicQueryFunction.ONLY_POSITIVE), @@ -82,6 +88,21 @@ public class DemoService { new TopicQuery(electricityPowerDelivery, "$.powerW", TopicQueryFunction.ONLY_NEGATIVE_BUT_NEGATE) ); + 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", 1), + new TopicQuery(elternElectricityPowerPurchase, "$.meter.power_w", TopicQueryFunction.ONLY_POSITIVE), + new TopicQuery(elternElectricityEnergyDelivery, "$.meter.energy_delivered_kwh", 1), + new TopicQuery(elternElectricityPowerDelivery, "$.meter.power_w", TopicQueryFunction.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); @@ -132,13 +153,17 @@ public class DemoService { final Series cisternVolume = series("cistern/volume", "L", SeriesType.VARYING, 5); topic("cistern/volume/PatrixJson", "$.date", new TopicQuery(cisternVolume, "$.value")); + + plotRepository.deleteAll(); + zuhauseEnergie(); + zuhauseTemperatur(); + eltern(); } - private void plots() { - plotRepository.deleteAll(); - - final Plot plot = plotRepository.save(new Plot()); - plot.setName("Test"); + 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); @@ -148,13 +173,39 @@ public class DemoService { final Series electricityEnergyPurchase = seriesRepository.findByName("electricity/energy/purchase").orElseThrow(); final Graph electricityEnergyPurchaseGraph = graphRepository.save(new Graph(energy, electricityEnergyPurchase)); + electricityEnergyPurchaseGraph.setType(GraphType.BAR); + electricityEnergyPurchaseGraph.setStack("a"); electricityEnergyPurchaseGraph.setName("Bezug"); electricityEnergyPurchaseGraph.setColor("#FF8800"); energy.addGraph(electricityEnergyPurchaseGraph); + final Series electricityEnergyDelivery = seriesRepository.findByName("electricity/energy/delivery").orElseThrow(); + final Graph electricityEnergyDeliveryGraph = graphRepository.save(new Graph(energy, electricityEnergyDelivery)); + electricityEnergyDeliveryGraph.setType(GraphType.BAR); + electricityEnergyDeliveryGraph.setStack("a"); + electricityEnergyDeliveryGraph.setName("Ü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.setType(GraphType.BAR); + electricityEnergyProduceGraph.setStack("a"); + electricityEnergyProduceGraph.setName("Produktion"); + electricityEnergyProduceGraph.setColor("#0000FF"); + energy.addGraph(electricityEnergyProduceGraph); + } + + 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"); @@ -167,34 +218,51 @@ public class DemoService { bedroomTemperatureGraph.setMax(true); temperature.addGraph(bedroomTemperatureGraph); - final Axis status = axisRepository.save(new Axis(plot)); - plot.addAxis(status); - status.setVisible(false); - status.setRight(true); - status.setName("Status"); + 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 fallbackRelay0 = seriesRepository.findByName("fallback/relay0").orElseThrow(); - final Graph fallbackRelay0Graph = graphRepository.save(new Graph(status, fallbackRelay0)); - fallbackRelay0Graph.setName("FallbackRelay0"); - fallbackRelay0Graph.setColor("#00FF00"); - status.addGraph(fallbackRelay0Graph); + private void eltern() { + final Plot plot = plotRepository.save(new Plot(plotRepository.count())); + plot.setName("Eltern"); - final Series infrared = seriesRepository.findByName("infraredHeater/state").orElseThrow(); - final Graph infraredGraph = graphRepository.save(new Graph(status, infrared)); - infraredGraph.setName("Infrarotheizung"); - infraredGraph.setColor("#FF00FF"); - status.addGraph(infraredGraph); + final Axis energy = axisRepository.save(new Axis(plot)); + plot.addAxis(energy); + plot.setDashboard(true); + energy.setRight(true); + energy.setName("Energie"); + energy.setUnit("kWh"); - plotRepository.save(plot); + 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); - axisRepository.save(energy); - axisRepository.save(temperature); - axisRepository.save(status); + 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); - graphRepository.save(electricityEnergyPurchaseGraph); - graphRepository.save(bedroomTemperatureGraph); - graphRepository.save(fallbackRelay0Graph); - graphRepository.save(infraredGraph); +// final Series electricityEnergyProduce = seriesRepository.findByName("eltern/electricity/energy/produce").orElseThrow(); +// final Graph electricityEnergyProduceGraph = graphRepository.save(new Graph(energy, electricityEnergyProduce)); +// electricityEnergyProduceGraph.setName("Produktion"); +// electricityEnergyProduceGraph.setType(GraphType.BAR); +// electricityEnergyProduceGraph.setStack("a"); +// electricityEnergyProduceGraph.setColor("#0000FF"); +// energy.addGraph(electricityEnergyProduceGraph); } @NonNull @@ -222,10 +290,11 @@ public class DemoService { topic.getQueries().addAll(List.of(queries)); } - private void topicMeterNumber(@NonNull final String name, @NonNull final String meterNumberQuery, @NonNull final TopicQuery... 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.setTimestampQuery("$.timestamp"); + topic.setTimestampType(timestampType); + topic.setTimestampQuery(timestampQuery); topic.getQueries().clear(); topic.getQueries().addAll(List.of(queries)); } diff --git a/src/main/java/de/ph87/data/topic/TimestampType.java b/src/main/java/de/ph87/data/topic/TimestampType.java index bc74417..d2db4f7 100644 --- a/src/main/java/de/ph87/data/topic/TimestampType.java +++ b/src/main/java/de/ph87/data/topic/TimestampType.java @@ -1,5 +1,7 @@ package de.ph87.data.topic; public enum TimestampType { - EPOCH_MILLISECONDS, EPOCH_SECONDS + EPOCH_MILLISECONDS, + EPOCH_SECONDS, + ISO_LOCAL_DATE_TIME, } diff --git a/src/main/java/de/ph87/data/topic/TopicReceiver.java b/src/main/java/de/ph87/data/topic/TopicReceiver.java index b396779..ca890f1 100644 --- a/src/main/java/de/ph87/data/topic/TopicReceiver.java +++ b/src/main/java/de/ph87/data/topic/TopicReceiver.java @@ -18,6 +18,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Instant; +import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Optional; @@ -27,6 +28,8 @@ import java.util.Optional; @RequiredArgsConstructor public class TopicReceiver { + public static final String SML_METER_NUMBER_RAW = "sml_meter_number_raw:"; + private final TopicRepository topicRepository; private final BoolService boolService; @@ -111,27 +114,24 @@ public class TopicReceiver { } final Object valueRaw = json.read(query.getValueQuery()); - queryValue(valueRaw).ifPresentOrElse( - v -> { - final double value = query.getFunction().apply(v) * query.getFactor(); - series.update(date, value); - applicationEventPublisher.publishEvent(new SeriesDto(series, false)); - switch (series.getType()) { - case BOOL -> { - final ZonedDateTime begin = queryTimestamp(json, query.getBeginQuery(), topic.getTimestampType()); - final boolean terminated = queryBoolean(json, query.getTerminatedQuery()); - boolService.write(series, begin, date, value > 0, terminated); - } - case DELTA -> { - final String meterNumber = queryMeterNumber(topic, json); - topic.setMeterNumberLast(meterNumber); - deltaService.write(series, meterNumber, date, value); - } - case VARYING -> varyingService.write(series, date, value); + queryValue(valueRaw).ifPresentOrElse(v -> { + final double value = query.getFunction().apply(v) * query.getFactor(); + series.update(date, value); + applicationEventPublisher.publishEvent(new SeriesDto(series, false)); + switch (series.getType()) { + case BOOL -> { + final ZonedDateTime begin = queryTimestamp(json, query.getBeginQuery(), topic.getTimestampType()); + final boolean terminated = queryBoolean(json, query.getTerminatedQuery()); + boolService.write(series, begin, date, value > 0, terminated); } - }, - () -> topic.error(log, "Failed to parse value: %s".formatted(valueRaw)) - ); + case DELTA -> { + final String meterNumber = queryMeterNumber(topic, json); + topic.setMeterNumberLast(meterNumber); + deltaService.write(series, meterNumber, date, value); + } + case VARYING -> varyingService.write(series, date, value); + } + }, () -> topic.error(log, "Failed to parse value: %s".formatted(valueRaw))); } catch (Exception e) { topic.error(log, "Error executing TopicQuery: %s\n topic=%s\n query=%s\n inbound=%s".formatted(e.toString(), topic, query, inbound), e); } @@ -139,10 +139,28 @@ public class TopicReceiver { @NonNull private static String queryMeterNumber(@NonNull final Topic topic, @NonNull final DocumentContext json) { - if (topic.getMeterNumberQuery().startsWith("\"") && topic.getMeterNumberQuery().endsWith("\"")) { - return topic.getMeterNumberQuery().substring(1, topic.getMeterNumberQuery().length() - 1); + final String query = topic.getMeterNumberQuery(); + if (query.startsWith(SML_METER_NUMBER_RAW)) { + final String field = query.substring(SML_METER_NUMBER_RAW.length()); + final String raw = json.read(field, String.class); + if (raw.isEmpty()) { + throw new NumberFormatException("Cannot parse Meter number: No Hex-chars read."); + } + if (raw.length() % 2 != 0) { + throw new NumberFormatException("Cannot parse Meter number: Hex-char count must be multiple of 2."); + } + final int length = Integer.parseInt(raw.substring(0, 2), 16); + if (raw.length() != length * 2) { + throw new NumberFormatException("Cannot parse Meter number: Invalid length"); + } + final int type = Integer.parseInt(raw.substring(2, 4), 16); + final String name = "" + (char) Integer.parseInt(raw.substring(4, 6), 16) + (char) Integer.parseInt(raw.substring(6, 8), 16) + (char) Integer.parseInt(raw.substring(8, 10), 16); + final int number = Integer.parseInt(raw.substring(10), 16); + return "%d%s%s".formatted(type, name, number); + } else if (query.startsWith("\"") && query.endsWith("\"")) { + return query.substring(1, query.length() - 1); } - return json.read(topic.getMeterNumberQuery(), String.class); + return json.read(query, String.class); } private static boolean queryBoolean(@NonNull final DocumentContext json, @NonNull final String terminatedQuery) { @@ -181,6 +199,7 @@ public class TopicReceiver { return switch (type) { case TimestampType.EPOCH_SECONDS -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(json.read(query, Long.class)), ZoneId.systemDefault()); case TimestampType.EPOCH_MILLISECONDS -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(json.read(query, Long.class)), ZoneId.systemDefault()); + case TimestampType.ISO_LOCAL_DATE_TIME -> ZonedDateTime.of(LocalDateTime.parse(json.read(query, String.class)), ZoneId.systemDefault()); }; }