Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[linky] Handle case when data from yesterday is still NaN #9423

Merged
merged 13 commits into from
Dec 29, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,15 @@ private Consumption getMeasures(String userId, String prmId, LocalDate from, Loc
if (data.isEmpty()) {
throw new LinkyException(String.format("Requesting '%s' returned an empty response", url));
}
logger.trace("getData returned {}", data);
try {
ConsumptionReport report = gson.fromJson(data, ConsumptionReport.class);
try {
report.checkData();
} catch (LinkyException e) {
throw new LinkyException(
String.format("Requesting '%s' returned an invalid response: %s", url, e.getMessage()));
}
return report.firstLevel.consumptions;
} catch (JsonSyntaxException e) {
logger.debug("invalid JSON response not matching ConsumptionReport.class: {}", data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ public void execute(String[] args, Console console) {
console.println(String.format("'%s' is not a Linky thing id", args[0]));
printUsage(console);
} else if (REPORT.equals(args[1])) {
LocalDate now = LocalDate.now();
LocalDate start = now.minusDays(7);
LocalDate end = now.minusDays(1);
LocalDate yesterday = LocalDate.now().minusDays(1);
LocalDate start = yesterday.minusDays(6);
LocalDate end = yesterday;
String separator = " ";
if (args.length >= 3) {
try {
Expand All @@ -104,13 +104,13 @@ public void execute(String[] args, Console console) {
return;
}
}
if (!start.isBefore(now) || start.isAfter(end)) {
if (start.isAfter(yesterday) || start.isAfter(end)) {
console.println("Start day must be in the past and before the end day");
printUsage(console);
return;
}
if (end.isAfter(now.minusDays(1))) {
end = now.minusDays(1);
if (end.isAfter(yesterday)) {
end = yesterday;
}
if (args.length >= 5) {
separator = args[4];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@
package org.openhab.binding.linky.internal.dto;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

import org.openhab.binding.linky.internal.LinkyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.annotations.SerializedName;

/**
Expand All @@ -24,6 +29,8 @@
* @author Gaël L'hopital - Initial contribution
*/
public class ConsumptionReport {
private static final Logger LOGGER = LoggerFactory.getLogger(ConsumptionReport.class);

public class Period {
public String grandeurPhysiqueEnum;
public ZonedDateTime dateDebut;
Expand All @@ -34,6 +41,32 @@ public class Aggregate {
public List<String> labels;
public List<Period> periodes;
public List<Double> datas;

public void log(String title, boolean withDateFin, DateTimeFormatter dateTimeFormatter, boolean onlyLast) {
if (LOGGER.isDebugEnabled()) {
int size = (datas == null || periodes == null) ? 0
: (datas.size() <= periodes.size() ? datas.size() : periodes.size());
if (onlyLast) {
if (size > 0) {
log(size - 1, title, withDateFin, dateTimeFormatter);
}
} else {
for (int i = 0; i < size; i++) {
log(i, title, withDateFin, dateTimeFormatter);
}
}
}
}

private void log(int index, String title, boolean withDateFin, DateTimeFormatter dateTimeFormatter) {
if (withDateFin) {
LOGGER.debug("{} {} {} value {}", title, periodes.get(index).dateDebut.format(dateTimeFormatter),
periodes.get(index).dateFin.format(dateTimeFormatter), datas.get(index));
} else {
LOGGER.debug("{} {} value {}", title, periodes.get(index).dateDebut.format(dateTimeFormatter),
datas.get(index));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this logic into LinkyHandler instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will but I don't understand why...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I suggested moving the logic is because you want to minimize the amount of logic you are doing in DTO classes since the null checker doesn't check dto classes.

}

public class ChronoData {
Expand Down Expand Up @@ -61,4 +94,32 @@ public class FirstLevel {

@SerializedName("1")
public FirstLevel firstLevel;

public void checkData() throws LinkyException {
Consumption cons = firstLevel.consumptions;
if (cons.aggregats.days.periodes.size() == 0) {
throw new LinkyException("invalid consumptions data: no day period");
}
if (cons.aggregats.days.periodes.size() != cons.aggregats.days.datas.size()) {
throw new LinkyException("invalid consumptions data: not one data for each day period");
}
if (cons.aggregats.weeks.periodes.size() == 0) {
throw new LinkyException("invalid consumptions data: no week period");
}
if (cons.aggregats.weeks.periodes.size() != cons.aggregats.weeks.datas.size()) {
throw new LinkyException("invalid consumptions data: not one data for each week period");
}
if (cons.aggregats.months.periodes.size() == 0) {
throw new LinkyException("invalid consumptions data: no month period");
}
if (cons.aggregats.months.periodes.size() != cons.aggregats.months.datas.size()) {
throw new LinkyException("invalid consumptions data: not one data for each month period");
}
if (cons.aggregats.years.periodes.size() == 0) {
throw new LinkyException("invalid consumptions data: no year period");
}
if (cons.aggregats.years.periodes.size() != cons.aggregats.years.datas.size()) {
throw new LinkyException("invalid consumptions data: not one data for each year period");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@
public class LinkyHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(LinkyHandler.class);

private static final int REFRESH_FIRST_HOUR_OF_DAY = 5;
private static final int REFRESH_INTERVAL_IN_MIN = 360;
private static final int REFRESH_FIRST_HOUR_OF_DAY = 1;
private static final int REFRESH_INTERVAL_IN_MIN = 120;

private final HttpClient httpClient;
private final Gson gson;
Expand All @@ -91,7 +91,16 @@ public LinkyHandler(Thing thing, LocaleProvider localeProvider, Gson gson, HttpC

this.cachedDailyData = new ExpiringDayCache<>("daily cache", REFRESH_FIRST_HOUR_OF_DAY, () -> {
LocalDate today = LocalDate.now();
return getConsumptionData(today.minusDays(15), today);
Consumption consumption = getConsumptionData(today.minusDays(15), today);
if (consumption != null) {
consumption.aggregats.days.log("Day", false, DateTimeFormatter.ISO_LOCAL_DATE, false);
consumption.aggregats.weeks.log("Week", true, DateTimeFormatter.ISO_LOCAL_DATE_TIME, false);
if (!isDataLastDayAvailable(consumption)) {
logger.debug("Dayly data including yesterday are not yet available");
consumption = null;
}
}
return consumption;
});

this.cachedPowerData = new ExpiringDayCache<>("power cache", REFRESH_FIRST_HOUR_OF_DAY, () -> {
Expand All @@ -102,15 +111,38 @@ public LinkyHandler(Thing thing, LocaleProvider localeProvider, Gson gson, HttpC

this.cachedMonthlyData = new ExpiringDayCache<>("monthly cache", REFRESH_FIRST_HOUR_OF_DAY, () -> {
LocalDate today = LocalDate.now();
return getConsumptionData(today.withDayOfMonth(1).minusMonths(1), today);
Consumption consumption = getConsumptionData(today.withDayOfMonth(1).minusMonths(1), today);
if (consumption != null) {
consumption.aggregats.days.log("Day", false, DateTimeFormatter.ISO_LOCAL_DATE, true);
consumption.aggregats.months.log("Month", true, DateTimeFormatter.ISO_LOCAL_DATE_TIME, false);
if (!isDataLastDayAvailable(consumption)) {
logger.debug("Monthly data including yesterday are not yet available");
consumption = null;
}
}
return consumption;
});

this.cachedYearlyData = new ExpiringDayCache<>("yearly cache", REFRESH_FIRST_HOUR_OF_DAY, () -> {
LocalDate today = LocalDate.now();
return getConsumptionData(LocalDate.of(today.getYear() - 1, 1, 1), today);
Consumption consumption = getConsumptionData(LocalDate.of(today.getYear() - 1, 1, 1), today);
if (consumption != null) {
consumption.aggregats.days.log("Day", false, DateTimeFormatter.ISO_LOCAL_DATE, true);
consumption.aggregats.years.log("Year", true, DateTimeFormatter.ISO_LOCAL_DATE_TIME, false);
if (!isDataLastDayAvailable(consumption)) {
logger.debug("Yearly data including yesterday are not yet available");
consumption = null;
}
}
return consumption;
});
}

private boolean isDataLastDayAvailable(Consumption consumption) {
Aggregate days = consumption.aggregats.days;
return days.datas != null && days.datas.size() > 0 && !days.datas.get(days.datas.size() - 1).isNaN();
}

@Override
public void initialize() {
logger.debug("Initializing Linky handler.");
Expand Down Expand Up @@ -178,12 +210,8 @@ private synchronized void updatePowerData() {
if (isLinked(PEAK_POWER) || isLinked(PEAK_TIMESTAMP)) {
cachedPowerData.getValue().ifPresent(values -> {
Aggregate days = values.aggregats.days;
if (days.datas.size() == 0 || days.periodes.size() == 0) {
logger.debug("Daily power data are without any period/data");
} else {
updateVAChannel(PEAK_POWER, days.datas.get(0));
updateState(PEAK_TIMESTAMP, new DateTimeType(days.periodes.get(0).dateDebut));
}
updateVAChannel(PEAK_POWER, days.datas.get(0));
updateState(PEAK_TIMESTAMP, new DateTimeType(days.periodes.get(0).dateDebut));
});
}
}
Expand All @@ -192,30 +220,10 @@ private synchronized void updatePowerData() {
* Request new dayly/weekly data and updates channels
*/
private synchronized void updateDailyData() {
if (isLinked(YESTERDAY) || isLinked(THIS_WEEK)) {
if (isLinked(YESTERDAY)) {
cachedDailyData.getValue().ifPresent(values -> {
Aggregate days = values.aggregats.days;
if (days.periodes.size() > days.datas.size()) {
logger.debug("Daily data are invalid: not a data for each period");
return;
}
int maxValue = days.periodes.size() - 1;
int thisWeekNumber = days.periodes.get(maxValue).dateDebut.get(weekFields.weekOfWeekBasedYear());
double yesterday = days.datas.get(maxValue);
double thisWeek = 0.00;

for (int i = maxValue; i >= 0; i--) {
int weekNumber = days.periodes.get(i).dateDebut.get(weekFields.weekOfWeekBasedYear());
if (weekNumber == thisWeekNumber) {
Double value = days.datas.get(i);
thisWeek += !value.isNaN() ? value : 0;
} else {
break;
}
}

updateKwhChannel(YESTERDAY, yesterday);
updateKwhChannel(THIS_WEEK, thisWeek);
updateKwhChannel(YESTERDAY, days.datas.get(days.datas.size() - 1));
});
}
}
Expand All @@ -224,14 +232,19 @@ private synchronized void updateDailyData() {
* Request new weekly data and updates channels
*/
private synchronized void updateWeeklyData() {
if (isLinked(LAST_WEEK)) {
if (isLinked(LAST_WEEK) || isLinked(THIS_WEEK)) {
cachedDailyData.getValue().ifPresent(values -> {
Aggregate days = values.aggregats.days;
int idxLast = days.periodes.get(days.periodes.size() - 1).dateDebut.get(weekFields.dayOfWeek()) == 7 ? 2
: 1;
Aggregate weeks = values.aggregats.weeks;
if (weeks.datas.size() > 1) {
updateKwhChannel(LAST_WEEK, weeks.datas.get(1));
if (weeks.datas.size() > idxLast) {
updateKwhChannel(LAST_WEEK, weeks.datas.get(idxLast));
}
if (weeks.datas.size() > (idxLast + 1)) {
updateKwhChannel(THIS_WEEK, weeks.datas.get(idxLast + 1));
} else {
logger.debug("Weekly data are without last week data");
updateKwhChannel(LAST_WEEK, Double.NaN);
updateKwhChannel(THIS_WEEK, 0.0);
}
});
}
Expand All @@ -244,18 +257,11 @@ private synchronized void updateMonthlyData() {
if (isLinked(LAST_MONTH) || isLinked(THIS_MONTH)) {
cachedMonthlyData.getValue().ifPresent(values -> {
Aggregate months = values.aggregats.months;
if (months.datas.size() == 0) {
logger.debug("Monthly data are without any data");
updateKwhChannel(LAST_MONTH, Double.NaN);
updateKwhChannel(THIS_MONTH, Double.NaN);
updateKwhChannel(LAST_MONTH, months.datas.get(0));
if (months.datas.size() > 1) {
updateKwhChannel(THIS_MONTH, months.datas.get(1));
} else {
updateKwhChannel(LAST_MONTH, months.datas.get(0));
if (months.datas.size() > 1) {
updateKwhChannel(THIS_MONTH, months.datas.get(1));
} else {
logger.debug("Monthly data are without current month data");
updateKwhChannel(THIS_MONTH, Double.NaN);
}
updateKwhChannel(THIS_MONTH, 0.0);
}
});
}
Expand All @@ -268,18 +274,11 @@ private synchronized void updateYearlyData() {
if (isLinked(LAST_YEAR) || isLinked(THIS_YEAR)) {
cachedYearlyData.getValue().ifPresent(values -> {
Aggregate years = values.aggregats.years;
if (years.datas.size() == 0) {
logger.debug("Yearly data are without any data");
updateKwhChannel(LAST_YEAR, Double.NaN);
updateKwhChannel(THIS_YEAR, Double.NaN);
updateKwhChannel(LAST_YEAR, years.datas.get(0));
if (years.datas.size() > 1) {
updateKwhChannel(THIS_YEAR, years.datas.get(1));
} else {
updateKwhChannel(LAST_YEAR, years.datas.get(0));
if (years.datas.size() > 1) {
updateKwhChannel(THIS_YEAR, years.datas.get(1));
} else {
logger.debug("Yearly data are without current year data");
updateKwhChannel(THIS_YEAR, Double.NaN);
}
updateKwhChannel(THIS_YEAR, 0.0);
}
});
}
Expand Down Expand Up @@ -315,7 +314,7 @@ private List<String> buildReport(LocalDate startDay, LocalDate endDay, @Nullable
List<String> report = new ArrayList<>();
if (startDay.getYear() == endDay.getYear() && startDay.getMonthValue() == endDay.getMonthValue()) {
// All values in the same month
Consumption result = getConsumptionData(startDay, endDay);
Consumption result = getConsumptionData(startDay, endDay.plusDays(1));
if (result != null) {
Aggregate days = result.aggregats.days;
for (int i = 0; i < days.datas.size(); i++) {
Expand Down Expand Up @@ -354,7 +353,9 @@ private List<String> buildReport(LocalDate startDay, LocalDate endDay, @Nullable
EnedisHttpApi api = this.enedisApi;
if (api != null) {
try {
return api.getEnergyData(userId, prmId, from, to);
Consumption consumption = api.getEnergyData(userId, prmId, from, to);
updateStatus(ThingStatus.ONLINE);
return consumption;
} catch (LinkyException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
}
Expand All @@ -368,7 +369,9 @@ private List<String> buildReport(LocalDate startDay, LocalDate endDay, @Nullable
EnedisHttpApi api = this.enedisApi;
if (api != null) {
try {
return api.getPowerData(userId, prmId, from, to);
Consumption consumption = api.getPowerData(userId, prmId, from, to);
updateStatus(ThingStatus.ONLINE);
return consumption;
} catch (LinkyException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
}
Expand Down Expand Up @@ -410,10 +413,10 @@ public synchronized void handleCommand(ChannelUID channelUID, Command command) {
boolean connectedBefore = isConnected();
switch (channelUID.getId()) {
case YESTERDAY:
case THIS_WEEK:
updateDailyData();
break;
case LAST_WEEK:
case THIS_WEEK:
updateWeeklyData();
break;
case LAST_MONTH:
Expand Down