[linky] Fix data refresh (login / logout) (#9358)

Fix #9313

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo
2020-12-15 22:38:52 +01:00
committed by GitHub
parent 30485c6a5b
commit 4e54b5b9b5
2 changed files with 144 additions and 47 deletions

View File

@@ -45,6 +45,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/** /**
* {@link EnedisHttpApi} wraps the Enedis Webservice. * {@link EnedisHttpApi} wraps the Enedis Webservice.
@@ -149,7 +150,7 @@ public class EnedisHttpApi {
throw new LinkyException("Connection failed step 5"); throw new LinkyException("Connection failed step 5");
} }
connected = true; connected = true;
} catch (InterruptedException | TimeoutException | ExecutionException e) { } catch (InterruptedException | TimeoutException | ExecutionException | JsonSyntaxException e) {
throw new LinkyException("Error opening connection with Enedis webservice", e); throw new LinkyException("Error opening connection with Enedis webservice", e);
} }
} }
@@ -160,6 +161,7 @@ public class EnedisHttpApi {
public void disconnect() throws LinkyException { public void disconnect() throws LinkyException {
if (connected) { if (connected) {
logger.debug("Logout process");
try { // Three times in a row to get disconnected try { // Three times in a row to get disconnected
String location = getLocation(httpClient.GET(URL_APPS_LINCS + "/logout")); String location = getLocation(httpClient.GET(URL_APPS_LINCS + "/logout"));
location = getLocation(httpClient.GET(location)); location = getLocation(httpClient.GET(location));
@@ -173,6 +175,10 @@ public class EnedisHttpApi {
} }
} }
public boolean isConnected() {
return connected;
}
public void dispose() throws LinkyException { public void dispose() throws LinkyException {
disconnect(); disconnect();
} }
@@ -204,16 +210,40 @@ public class EnedisHttpApi {
} }
public PrmInfo getPrmInfo() throws LinkyException { public PrmInfo getPrmInfo() throws LinkyException {
if (!connected) {
initialize();
}
final String prm_info_url = URL_APPS_LINCS + "/mes-mesures/api/private/v1/personnes/null/prms"; final String prm_info_url = URL_APPS_LINCS + "/mes-mesures/api/private/v1/personnes/null/prms";
String data = getData(prm_info_url); String data = getData(prm_info_url);
if (data.isEmpty()) {
throw new LinkyException(String.format("Requesting '%s' returned an empty response", prm_info_url));
}
try {
PrmInfo[] prms = gson.fromJson(data, PrmInfo[].class); PrmInfo[] prms = gson.fromJson(data, PrmInfo[].class);
return prms[0]; return prms[0];
} catch (JsonSyntaxException e) {
logger.debug("invalid JSON response not matching PrmInfo[].class: {}", data);
throw new LinkyException(String.format("Requesting '%s' returned an invalid JSON response : %s",
prm_info_url, e.getMessage()), e);
}
} }
public UserInfo getUserInfo() throws LinkyException { public UserInfo getUserInfo() throws LinkyException {
if (!connected) {
initialize();
}
final String user_info_url = URL_APPS_LINCS + "/userinfos"; final String user_info_url = URL_APPS_LINCS + "/userinfos";
String data = getData(user_info_url); String data = getData(user_info_url);
if (data.isEmpty()) {
throw new LinkyException(String.format("Requesting '%s' returned an empty response", user_info_url));
}
try {
return Objects.requireNonNull(gson.fromJson(data, UserInfo.class)); return Objects.requireNonNull(gson.fromJson(data, UserInfo.class));
} catch (JsonSyntaxException e) {
logger.debug("invalid JSON response not matching UserInfo.class: {}", data);
throw new LinkyException(String.format("Requesting '%s' returned an invalid JSON response : %s",
user_info_url, e.getMessage()), e);
}
} }
private Consumption getMeasures(String userId, String prmId, LocalDate from, LocalDate to, String request) private Consumption getMeasures(String userId, String prmId, LocalDate from, LocalDate to, String request)
@@ -223,15 +253,30 @@ public class EnedisHttpApi {
String url = String.format(measure_url, userId, prmId, request, from.format(API_DATE_FORMAT), String url = String.format(measure_url, userId, prmId, request, from.format(API_DATE_FORMAT),
to.format(API_DATE_FORMAT)); to.format(API_DATE_FORMAT));
String data = getData(url); String data = getData(url);
if (data.isEmpty()) {
throw new LinkyException(String.format("Requesting '%s' returned an empty response", url));
}
try {
ConsumptionReport report = gson.fromJson(data, ConsumptionReport.class); ConsumptionReport report = gson.fromJson(data, ConsumptionReport.class);
return report.firstLevel.consumptions; return report.firstLevel.consumptions;
} catch (JsonSyntaxException e) {
logger.debug("invalid JSON response not matching ConsumptionReport.class: {}", data);
throw new LinkyException(
String.format("Requesting '%s' returned an invalid JSON response : %s", url, e.getMessage()), e);
}
} }
public Consumption getEnergyData(String userId, String prmId, LocalDate from, LocalDate to) throws LinkyException { public Consumption getEnergyData(String userId, String prmId, LocalDate from, LocalDate to) throws LinkyException {
if (!connected) {
initialize();
}
return getMeasures(userId, prmId, from, to, "energie"); return getMeasures(userId, prmId, from, to, "energie");
} }
public Consumption getPowerData(String userId, String prmId, LocalDate from, LocalDate to) throws LinkyException { public Consumption getPowerData(String userId, String prmId, LocalDate from, LocalDate to) throws LinkyException {
if (!connected) {
initialize();
}
return getMeasures(userId, prmId, from, to, "pmax"); return getMeasures(userId, prmId, from, to, "pmax");
} }
} }

View File

@@ -127,19 +127,26 @@ public class LinkyHandler extends BaseThingHandler {
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
if (thing.getProperties().isEmpty()) { if (thing.getProperties().isEmpty()) {
Map<String, String> properties = discoverAttributes(); Map<String, String> properties = new HashMap<>();
PrmInfo prmInfo = api.getPrmInfo();
UserInfo userInfo = api.getUserInfo();
properties.put(USER_ID, userInfo.userProperties.internId);
properties.put(PUISSANCE, prmInfo.puissanceSouscrite + " kVA");
properties.put(PRM_ID, prmInfo.prmId);
updateProperties(properties); updateProperties(properties);
} }
prmId = thing.getProperties().get(PRM_ID); prmId = thing.getProperties().get(PRM_ID);
userId = thing.getProperties().get(USER_ID); userId = thing.getProperties().get(USER_ID);
updateData();
disconnect();
final LocalDateTime now = LocalDateTime.now(); final LocalDateTime now = LocalDateTime.now();
final LocalDateTime nextDayFirstTimeUpdate = now.plusDays(1).withHour(REFRESH_FIRST_HOUR_OF_DAY) final LocalDateTime nextDayFirstTimeUpdate = now.plusDays(1).withHour(REFRESH_FIRST_HOUR_OF_DAY)
.truncatedTo(ChronoUnit.HOURS); .truncatedTo(ChronoUnit.HOURS);
updateData();
refreshJob = scheduler.scheduleWithFixedDelay(this::updateData, refreshJob = scheduler.scheduleWithFixedDelay(this::updateData,
ChronoUnit.MINUTES.between(now, nextDayFirstTimeUpdate) % REFRESH_INTERVAL_IN_MIN + 1, ChronoUnit.MINUTES.between(now, nextDayFirstTimeUpdate) % REFRESH_INTERVAL_IN_MIN + 1,
REFRESH_INTERVAL_IN_MIN, TimeUnit.MINUTES); REFRESH_INTERVAL_IN_MIN, TimeUnit.MINUTES);
@@ -152,36 +159,31 @@ public class LinkyHandler extends BaseThingHandler {
}); });
} }
private Map<String, String> discoverAttributes() throws LinkyException {
Map<String, String> properties = new HashMap<>();
EnedisHttpApi api = this.enedisApi;
if (api != null) {
PrmInfo prmInfo = api.getPrmInfo();
UserInfo userInfo = api.getUserInfo();
properties.put(USER_ID, userInfo.userProperties.internId);
properties.put(PUISSANCE, prmInfo.puissanceSouscrite + " kVA");
properties.put(PRM_ID, prmInfo.prmId);
}
return properties;
}
/** /**
* Request new data and updates channels * Request new data and updates channels
*/ */
private void updateData() { private synchronized void updateData() {
boolean connectedBefore = isConnected();
updatePowerData(); updatePowerData();
updateDailyData(); updateDailyData();
updateWeeklyData(); updateWeeklyData();
updateMonthlyData(); updateMonthlyData();
updateYearlyData(); updateYearlyData();
if (!connectedBefore && isConnected()) {
disconnect();
}
} }
private synchronized void updatePowerData() { private synchronized void updatePowerData() {
if (isLinked(PEAK_POWER) || isLinked(PEAK_TIMESTAMP)) { if (isLinked(PEAK_POWER) || isLinked(PEAK_TIMESTAMP)) {
cachedPowerData.getValue().ifPresent(values -> { cachedPowerData.getValue().ifPresent(values -> {
updateVAChannel(PEAK_POWER, values.aggregats.days.datas.get(0)); Aggregate days = values.aggregats.days;
updateState(PEAK_TIMESTAMP, new DateTimeType(values.aggregats.days.periodes.get(0).dateDebut)); 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));
}
}); });
} }
} }
@@ -193,6 +195,10 @@ public class LinkyHandler extends BaseThingHandler {
if (isLinked(YESTERDAY) || isLinked(THIS_WEEK)) { if (isLinked(YESTERDAY) || isLinked(THIS_WEEK)) {
cachedDailyData.getValue().ifPresent(values -> { cachedDailyData.getValue().ifPresent(values -> {
Aggregate days = values.aggregats.days; 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 maxValue = days.periodes.size() - 1;
int thisWeekNumber = days.periodes.get(maxValue).dateDebut.get(weekFields.weekOfWeekBasedYear()); int thisWeekNumber = days.periodes.get(maxValue).dateDebut.get(weekFields.weekOfWeekBasedYear());
double yesterday = days.datas.get(maxValue); double yesterday = days.datas.get(maxValue);
@@ -223,6 +229,9 @@ public class LinkyHandler extends BaseThingHandler {
Aggregate weeks = values.aggregats.weeks; Aggregate weeks = values.aggregats.weeks;
if (weeks.datas.size() > 1) { if (weeks.datas.size() > 1) {
updateKwhChannel(LAST_WEEK, weeks.datas.get(1)); updateKwhChannel(LAST_WEEK, weeks.datas.get(1));
} else {
logger.debug("Weekly data are without last week data");
updateKwhChannel(LAST_WEEK, Double.NaN);
} }
}); });
} }
@@ -235,12 +244,19 @@ public class LinkyHandler extends BaseThingHandler {
if (isLinked(LAST_MONTH) || isLinked(THIS_MONTH)) { if (isLinked(LAST_MONTH) || isLinked(THIS_MONTH)) {
cachedMonthlyData.getValue().ifPresent(values -> { cachedMonthlyData.getValue().ifPresent(values -> {
Aggregate months = values.aggregats.months; 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);
} else {
updateKwhChannel(LAST_MONTH, months.datas.get(0)); updateKwhChannel(LAST_MONTH, months.datas.get(0));
if (months.datas.size() > 1) { if (months.datas.size() > 1) {
updateKwhChannel(THIS_MONTH, months.datas.get(1)); updateKwhChannel(THIS_MONTH, months.datas.get(1));
} else { } else {
logger.debug("Monthly data are without current month data");
updateKwhChannel(THIS_MONTH, Double.NaN); updateKwhChannel(THIS_MONTH, Double.NaN);
} }
}
}); });
} }
} }
@@ -252,12 +268,19 @@ public class LinkyHandler extends BaseThingHandler {
if (isLinked(LAST_YEAR) || isLinked(THIS_YEAR)) { if (isLinked(LAST_YEAR) || isLinked(THIS_YEAR)) {
cachedYearlyData.getValue().ifPresent(values -> { cachedYearlyData.getValue().ifPresent(values -> {
Aggregate years = values.aggregats.years; 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);
} else {
updateKwhChannel(LAST_YEAR, years.datas.get(0)); updateKwhChannel(LAST_YEAR, years.datas.get(0));
if (years.datas.size() > 1) { if (years.datas.size() > 1) {
updateKwhChannel(THIS_YEAR, years.datas.get(1)); updateKwhChannel(THIS_YEAR, years.datas.get(1));
} else { } else {
logger.debug("Yearly data are without current year data");
updateKwhChannel(THIS_YEAR, Double.NaN); updateKwhChannel(THIS_YEAR, Double.NaN);
} }
}
}); });
} }
} }
@@ -280,9 +303,15 @@ public class LinkyHandler extends BaseThingHandler {
* @param endDay the end day of the report * @param endDay the end day of the report
* @param separator the separator to be used betwwen the date and the value * @param separator the separator to be used betwwen the date and the value
* *
* @return the report as a string * @return the report as a list of string
*/ */
public List<String> reportValues(LocalDate startDay, LocalDate endDay, @Nullable String separator) { public synchronized List<String> reportValues(LocalDate startDay, LocalDate endDay, @Nullable String separator) {
List<String> report = buildReport(startDay, endDay, separator);
disconnect();
return report;
}
private List<String> buildReport(LocalDate startDay, LocalDate endDay, @Nullable String separator) {
List<String> report = new ArrayList<>(); List<String> report = new ArrayList<>();
if (startDay.getYear() == endDay.getYear() && startDay.getMonthValue() == endDay.getMonthValue()) { if (startDay.getYear() == endDay.getYear() && startDay.getMonthValue() == endDay.getMonthValue()) {
// All values in the same month // All values in the same month
@@ -312,7 +341,7 @@ public class LinkyHandler extends BaseThingHandler {
if (last.isAfter(endDay)) { if (last.isAfter(endDay)) {
last = endDay; last = endDay;
} }
report.addAll(reportValues(first, last, separator)); report.addAll(buildReport(first, last, separator));
first = last.plusDays(1); first = last.plusDays(1);
} while (!first.isAfter(endDay)); } while (!first.isAfter(endDay));
} }
@@ -320,6 +349,8 @@ public class LinkyHandler extends BaseThingHandler {
} }
private @Nullable Consumption getConsumptionData(LocalDate from, LocalDate to) { private @Nullable Consumption getConsumptionData(LocalDate from, LocalDate to) {
logger.debug("getConsumptionData from {} to {}", from.format(DateTimeFormatter.ISO_LOCAL_DATE),
to.format(DateTimeFormatter.ISO_LOCAL_DATE));
EnedisHttpApi api = this.enedisApi; EnedisHttpApi api = this.enedisApi;
if (api != null) { if (api != null) {
try { try {
@@ -332,6 +363,8 @@ public class LinkyHandler extends BaseThingHandler {
} }
private @Nullable Consumption getPowerData(LocalDate from, LocalDate to) { private @Nullable Consumption getPowerData(LocalDate from, LocalDate to) {
logger.debug("getPowerData from {} to {}", from.format(DateTimeFormatter.ISO_LOCAL_DATE),
to.format(DateTimeFormatter.ISO_LOCAL_DATE));
EnedisHttpApi api = this.enedisApi; EnedisHttpApi api = this.enedisApi;
if (api != null) { if (api != null) {
try { try {
@@ -343,6 +376,21 @@ public class LinkyHandler extends BaseThingHandler {
return null; return null;
} }
private boolean isConnected() {
EnedisHttpApi api = this.enedisApi;
return api == null ? false : api.isConnected();
}
private void disconnect() {
EnedisHttpApi api = this.enedisApi;
if (api != null) {
try {
api.dispose();
} catch (LinkyException ignore) {
}
}
}
@Override @Override
public void dispose() { public void dispose() {
logger.debug("Disposing the Linky handler."); logger.debug("Disposing the Linky handler.");
@@ -351,26 +399,23 @@ public class LinkyHandler extends BaseThingHandler {
job.cancel(true); job.cancel(true);
refreshJob = null; refreshJob = null;
} }
EnedisHttpApi api = this.enedisApi; disconnect();
if (api != null) {
try {
api.dispose();
enedisApi = null; enedisApi = null;
} catch (LinkyException ignore) {
}
}
} }
@Override @Override
public void handleCommand(ChannelUID channelUID, Command command) { public synchronized void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) { if (command instanceof RefreshType) {
logger.debug("Refreshing channel {}", channelUID.getId()); logger.debug("Refreshing channel {}", channelUID.getId());
boolean connectedBefore = isConnected();
switch (channelUID.getId()) { switch (channelUID.getId()) {
case YESTERDAY: case YESTERDAY:
case LAST_WEEK:
case THIS_WEEK: case THIS_WEEK:
updateDailyData(); updateDailyData();
break; break;
case LAST_WEEK:
updateWeeklyData();
break;
case LAST_MONTH: case LAST_MONTH:
case THIS_MONTH: case THIS_MONTH:
updateMonthlyData(); updateMonthlyData();
@@ -379,9 +424,16 @@ public class LinkyHandler extends BaseThingHandler {
case THIS_YEAR: case THIS_YEAR:
updateYearlyData(); updateYearlyData();
break; break;
case PEAK_POWER:
case PEAK_TIMESTAMP:
updatePowerData();
break;
default: default:
break; break;
} }
if (!connectedBefore && isConnected()) {
disconnect();
}
} else { } else {
logger.debug("The Linky binding is read-only and can not handle command {}", command); logger.debug("The Linky binding is read-only and can not handle command {}", command);
} }