2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.plugwise.internal.protocol.field;
15 import java.time.Duration;
16 import java.time.LocalDateTime;
17 import java.time.ZoneId;
18 import java.time.ZonedDateTime;
19 import java.time.temporal.ChronoUnit;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
25 * A simple class to represent energy usage, converting between Plugwise data representations.
27 * @author Wouter Born, Karel Goderis - Initial contribution
32 private static final int WATTS_PER_KILOWATT = 1000;
33 private static final double PULSES_PER_KW_SECOND = 468.9385193;
34 private static final double PULSES_PER_W_SECOND = PULSES_PER_KW_SECOND / WATTS_PER_KILOWATT;
36 private @Nullable ZonedDateTime utcStart; // using UTC resolves wrong local start/end timestamps when DST changes
38 private ZonedDateTime utcEnd;
40 private @Nullable Duration interval;
42 public Energy(ZonedDateTime utcEnd, long pulses) {
47 public Energy(ZonedDateTime utcEnd, long pulses, Duration interval) {
50 this.interval = interval;
51 updateStart(interval);
54 private double correctPulses(double pulses, PowerCalibration calibration) {
55 double gainA = calibration.getGainA();
56 double gainB = calibration.getGainB();
57 double offsetNoise = calibration.getOffsetNoise();
58 double offsetTotal = calibration.getOffsetTotal();
60 double correctedPulses = Math.pow(pulses + offsetNoise, 2) * gainB + (pulses + offsetNoise) * gainA
62 if ((pulses > 0 && correctedPulses < 0) || (pulses < 0 && correctedPulses > 0)) {
65 return correctedPulses;
68 public LocalDateTime getEnd() {
69 return utcEnd.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
72 public @Nullable Duration getInterval() {
76 public long getPulses() {
80 public @Nullable LocalDateTime getStart() {
81 ZonedDateTime localUtcStart = utcStart;
82 if (localUtcStart == null) {
85 return localUtcStart.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
88 private double intervalSeconds() {
89 Duration localInterval = interval;
90 if (localInterval == null) {
91 throw new IllegalStateException("Failed to calculate seconds because interval is null");
94 double seconds = localInterval.getSeconds();
95 seconds += (double) localInterval.getNano() / ChronoUnit.SECONDS.getDuration().toNanos();
99 public void setInterval(Duration interval) {
100 this.interval = interval;
101 updateStart(interval);
104 public double tokWh(PowerCalibration calibration) {
105 return toWatt(calibration) * intervalSeconds()
106 / (ChronoUnit.HOURS.getDuration().getSeconds() * WATTS_PER_KILOWATT);
110 public String toString() {
111 return "Energy [utcStart=" + utcStart + ", utcEnd=" + utcEnd + ", pulses=" + pulses + ", interval=" + interval
115 public double toWatt(PowerCalibration calibration) {
116 double averagePulses = pulses / intervalSeconds();
117 return correctPulses(averagePulses, calibration) / PULSES_PER_W_SECOND;
120 private void updateStart(Duration interval) {
121 utcStart = utcEnd.minus(interval);