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,6 +256,7 @@ 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);
return report.firstLevel.consumptions;
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 @@ -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,23 +91,48 @@ 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) {
logData(consumption.aggregats.days, "Day", false, DateTimeFormatter.ISO_LOCAL_DATE, false);
logData(consumption.aggregats.weeks, "Week", true, DateTimeFormatter.ISO_LOCAL_DATE_TIME, false);
consumption = getConsumptionAfterChecks(consumption);
}
return consumption;
});

this.cachedPowerData = new ExpiringDayCache<>("power cache", REFRESH_FIRST_HOUR_OF_DAY, () -> {
LocalDate to = LocalDate.now().plusDays(1);
LocalDate from = to.minusDays(2);
return getPowerData(from, to);
Consumption consumption = getPowerData(from, to);
if (consumption != null) {
try {
checkData(consumption);
} catch (LinkyException e) {
logger.debug("Power data: {}", e.getMessage());
return null;
}
}
return consumption;
});

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) {
logData(consumption.aggregats.months, "Month", true, DateTimeFormatter.ISO_LOCAL_DATE_TIME, false);
consumption = getConsumptionAfterChecks(consumption);
}
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) {
logData(consumption.aggregats.years, "Year", true, DateTimeFormatter.ISO_LOCAL_DATE_TIME, false);
consumption = getConsumptionAfterChecks(consumption);
}
return consumption;
});
}

Expand Down Expand Up @@ -178,12 +203,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 +213,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 +225,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 +250,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 +267,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,10 +307,12 @@ 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++) {
int size = (days.datas == null || days.periodes == null) ? 0
: (days.datas.size() <= days.periodes.size() ? days.datas.size() : days.periodes.size());
for (int i = 0; i < size; i++) {
double consumption = days.datas.get(i);
String line = days.periodes.get(i).dateDebut.format(DateTimeFormatter.ISO_LOCAL_DATE) + separator;
if (consumption >= 0) {
Expand Down Expand Up @@ -354,7 +348,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 +364,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 +408,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 All @@ -438,4 +436,80 @@ public synchronized void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("The Linky binding is read-only and can not handle command {}", command);
}
}

private @Nullable Consumption getConsumptionAfterChecks(Consumption consumption) {
try {
checkData(consumption);
} catch (LinkyException e) {
logger.debug("Consumption data: {}", e.getMessage());
return null;
}
if (!isDataLastDayAvailable(consumption)) {
logger.debug("Data including yesterday are not yet available");
return null;
}
return consumption;
}

public void checkData(Consumption consumption) throws LinkyException {
if (consumption.aggregats.days.periodes.size() == 0) {
throw new LinkyException("invalid consumptions data: no day period");
}
if (consumption.aggregats.days.periodes.size() != consumption.aggregats.days.datas.size()) {
throw new LinkyException("invalid consumptions data: not one data for each day period");
}
if (consumption.aggregats.weeks.periodes.size() == 0) {
throw new LinkyException("invalid consumptions data: no week period");
}
if (consumption.aggregats.weeks.periodes.size() != consumption.aggregats.weeks.datas.size()) {
throw new LinkyException("invalid consumptions data: not one data for each week period");
}
if (consumption.aggregats.months.periodes.size() == 0) {
throw new LinkyException("invalid consumptions data: no month period");
}
if (consumption.aggregats.months.periodes.size() != consumption.aggregats.months.datas.size()) {
throw new LinkyException("invalid consumptions data: not one data for each month period");
}
if (consumption.aggregats.years.periodes.size() == 0) {
throw new LinkyException("invalid consumptions data: no year period");
}
if (consumption.aggregats.years.periodes.size() != consumption.aggregats.years.datas.size()) {
throw new LinkyException("invalid consumptions data: not one data for each year period");
}
}

private boolean isDataLastDayAvailable(Consumption consumption) {
Aggregate days = consumption.aggregats.days;
logData(days, "Last day", false, DateTimeFormatter.ISO_LOCAL_DATE, true);
return days.datas != null && days.datas.size() > 0 && !days.datas.get(days.datas.size() - 1).isNaN();
}

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

private void logData(Aggregate aggregate, int index, String title, boolean withDateFin,
DateTimeFormatter dateTimeFormatter) {
if (withDateFin) {
logger.debug("{} {} {} value {}", title, aggregate.periodes.get(index).dateDebut.format(dateTimeFormatter),
aggregate.periodes.get(index).dateFin.format(dateTimeFormatter), aggregate.datas.get(index));
} else {
logger.debug("{} {} value {}", title, aggregate.periodes.get(index).dateDebut.format(dateTimeFormatter),
aggregate.datas.get(index));
}
}
}