Skip to content

Commit f44930f

Browse files
authored
[sunsynk] Token refresh method changed to account sign in (#20431)
* Token refresh method changed to account sign in Signed-off-by: LeeC77 <lee.charlton00@gmail.com>
1 parent 71ad2a4 commit f44930f

6 files changed

Lines changed: 145 additions & 140 deletions

File tree

bundles/org.openhab.binding.sunsynk/src/main/java/org/openhab/binding/sunsynk/internal/api/AccountController.java

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import org.openhab.binding.sunsynk.internal.api.dto.Inverter;
4545
import org.openhab.binding.sunsynk.internal.api.dto.SunSynkLogin;
4646
import org.openhab.binding.sunsynk.internal.api.dto.SunSynkPublicKey;
47-
import org.openhab.binding.sunsynk.internal.api.dto.TokenRefresh;
4847
import org.openhab.binding.sunsynk.internal.api.exception.SunSynkAuthenticateException;
4948
import org.openhab.binding.sunsynk.internal.api.exception.SunSynkClientAuthenticateException;
5049
import org.openhab.binding.sunsynk.internal.api.exception.SunSynkInverterDiscoveryException;
@@ -68,7 +67,6 @@ public class AccountController {
6867
private static final int TIMEOUT_IN_MS = 4000;
6968
private final Logger logger = LoggerFactory.getLogger(AccountController.class);
7069
private static final String BEARER_TYPE = "Bearer ";
71-
private static final long EXPIRYSECONDS = 100L; // 100 seconds before expiry
7270
private static final String SECRET_KEY = "POWER_VIEW"; // Is the SunSynk Connect App secret key
7371
private static final String SOURCE = "sunsynk"; // Is the SunSynk Connect App source identifier
7472
private Client sunAccount = new Client();
@@ -86,15 +84,15 @@ public AccountController() {
8684
* @throws SunSynkAuthenticateException
8785
* @throws SunSynkTokenException
8886
*/
89-
public void clientAuthenticate(String username, String userPassword)
87+
public void clientAuthenticate(String userName, String userPassword)
9088
throws SunSynkClientAuthenticateException, SunSynkAuthenticateException {
9189
long nonce = Instant.now().toEpochMilli();
9290
String signSource = "nonce=" + nonce + "&source=" + SOURCE + SECRET_KEY;
9391
try {
9492
String authEndpoint = "nonce=" + nonce + "&source=" + SOURCE + "&sign=" + getSign(signSource);
9593
httpGetPublicKey(authEndpoint);
9694
String encryptedPassword = getEncryptPassword(userPassword, this.publicKey.getPublicKey());
97-
userAuthenticate(username, encryptedPassword);
95+
userAuthenticate(userName, encryptedPassword);
9896
} catch (NoSuchAlgorithmException e) {
9997
throw new SunSynkClientAuthenticateException("Error attempting to authenticate client:" + e.getMessage());
10098
} catch (NoSuchPaddingException e) {
@@ -142,25 +140,15 @@ public void userAuthenticate(String username, String saltedPassword)
142140
}
143141

144142
/**
145-
* Checks if a Sunsynk Connect account token is expired and gets a new one if required.
143+
* calculates the time until the Sunsynk Connect account token is expired.
146144
*
147-
* @param username
145+
* @return long, seconds remaining on bearer token.
148146
* @throws SunSynkAuthenticateException
149-
* @throws SunSynkTokenException
150147
*/
151-
public void refreshAccount(String username) throws SunSynkAuthenticateException, SunSynkTokenException {
152-
Long expiresIn = this.sunAccount.getExpiresIn();
153-
Long issuedAt = this.sunAccount.getIssuedAt();
154-
if ((issuedAt + expiresIn) - Instant.now().getEpochSecond() > EXPIRYSECONDS) {
155-
logger.debug("Account configuration token not expired.");
156-
return;
157-
}
158-
if (this.sunAccount.getRefreshTokenString().isEmpty()) {
159-
throw new SunSynkTokenException("No refresh token available, re-authentication required.");
160-
}
161-
logger.debug("Account configuration token expired : {}", this.sunAccount.getData().toString());
162-
String payload = makeRefreshBody(username, this.sunAccount.getRefreshTokenString());
163-
httpTokenPost(payload);
148+
public long checkExpireTime() throws SunSynkAuthenticateException {
149+
long expiresIn = this.sunAccount.getExpiresIn();
150+
long issuedAt = this.sunAccount.getIssuedAt();
151+
return (issuedAt + expiresIn) - Instant.now().getEpochSecond();
164152
}
165153

166154
private void httpGetPublicKey(String endpoint) throws SunSynkClientAuthenticateException, JsonSyntaxException {
@@ -308,12 +296,6 @@ private static String makeLoginBody(String username, String password, String sig
308296
return gson.toJson(login);
309297
}
310298

311-
private static String makeRefreshBody(String username, String refreshToken) {
312-
Gson gson = new Gson();
313-
TokenRefresh refresh = new TokenRefresh(username, refreshToken);
314-
return gson.toJson(refresh);
315-
}
316-
317299
private void getToken() throws SunSynkAuthenticateException {
318300
APIdata data = this.sunAccount.getData();
319301
APIdata.staticAccessToken = data.getAccessToken();

bundles/org.openhab.binding.sunsynk/src/main/java/org/openhab/binding/sunsynk/internal/api/DeviceController.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.openhab.binding.sunsynk.internal.api.exception.SunSynkGetStatusException;
4141
import org.openhab.binding.sunsynk.internal.api.exception.SunSynkSendCommandException;
4242
import org.openhab.binding.sunsynk.internal.config.SunSynkInverterConfig;
43+
import org.openhab.core.i18n.TimeZoneProvider;
4344
import org.openhab.core.io.net.http.HttpUtil;
4445
import org.slf4j.Logger;
4546
import org.slf4j.LoggerFactory;
@@ -71,6 +72,7 @@ public class DeviceController {
7172
private Grid grid = new Grid();
7273
private Daytemps inverterDayTemperatures = new Daytemps();
7374
private RealTimeInData realTimeDataIn = new RealTimeInData();
75+
private final TimeZoneProvider timeZoneProvider;
7476
public static final int COMMONSETTINGS = 1 << 0;
7577
public static final int GRIDREALTIME = 1 << 1;
7678
public static final int BATTERYREALTIME = 1 << 2;
@@ -89,20 +91,19 @@ public class DeviceController {
8991
public Settings tempInverterChargeSettings = new Settings(); // Holds modified battery settings.
9092
public PlantSummary plantSummary = new PlantSummary();
9193

92-
public DeviceController() {
93-
}
94-
9594
/**
9695
* Sets the identity of the device (inverter) according to the configuration parameters;
97-
* serial number and alias.
96+
* serial number and alias. Connects TimeZoneProvider service form the openHAB framework.
9897
*
9998
* @param config
99+
* @param TimeZoneProvider service is injected by the openHAB framework
100100
*/
101-
public DeviceController(SunSynkInverterConfig config) {
101+
public DeviceController(SunSynkInverterConfig config, TimeZoneProvider timeZoneProvider) {
102102
this.sn = config.getSerialnumber();
103103
this.alias = config.getAlias();
104104
this.plantId = config.getPlantId();
105105
this.plantName = config.getPlantName();
106+
this.timeZoneProvider = timeZoneProvider;
106107
}
107108

108109
/**
@@ -363,6 +364,6 @@ private String makeURL(String endPoint, String queryParameters) {
363364
}
364365

365366
private String getAPIFormatDate() {
366-
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
367+
return LocalDate.now(timeZoneProvider.getTimeZone()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
367368
}
368369
}

bundles/org.openhab.binding.sunsynk/src/main/java/org/openhab/binding/sunsynk/internal/api/dto/TokenRefresh.java

Lines changed: 0 additions & 43 deletions
This file was deleted.

bundles/org.openhab.binding.sunsynk/src/main/java/org/openhab/binding/sunsynk/internal/handler/SunSynkAccountHandler.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
package org.openhab.binding.sunsynk.internal.handler;
1414

15+
import java.time.Duration;
1516
import java.util.ArrayList;
1617
import java.util.List;
1718
import java.util.concurrent.ScheduledFuture;
@@ -45,6 +46,7 @@
4546
@NonNullByDefault
4647
public class SunSynkAccountHandler extends BaseBridgeHandler {
4748
private final Logger logger = LoggerFactory.getLogger(SunSynkAccountHandler.class);
49+
private static final long EXPIRY_SECONDS = 100L; // 100 seconds before expiry
4850
private AccountController sunAccount = new AccountController();
4951
private @Nullable ScheduledFuture<?> discoverApiKeyJob;
5052
private @Nullable SunSynkAccountConfig accountConfig;
@@ -96,16 +98,16 @@ public void dispose() {
9698
}
9799
}
98100

99-
public void configAccount() {
101+
private boolean configAccount() {
100102
SunSynkAccountConfig accountConfig = this.accountConfig;
101103
if (accountConfig == null) {
102104
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No account config provided.");
103-
return;
105+
return false;
104106
}
105107
if (accountConfig.getEmail().isBlank() | accountConfig.getPassword().isBlank()) {
106108
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
107109
"E-mail address or Password missing in account configuration");
108-
return;
110+
return false;
109111
}
110112
try {
111113
this.sunAccount.clientAuthenticate(accountConfig.getEmail(), accountConfig.getPassword());
@@ -115,30 +117,47 @@ public void configAccount() {
115117
}
116118
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
117119
"Error attempting to authenticate binding with SunSynk");
118-
return;
120+
return false;
119121
} catch (SunSynkAuthenticateException e) {
120122
if (logger.isDebugEnabled()) {
121123
logger.debug("Error attempting to authenticate user: {}.", e.getMessage());
122124
}
123125
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
124126
"Error attempting to authenticate user credentials");
125-
return;
127+
return false;
126128
}
127129
updateStatus(ThingStatus.ONLINE);
130+
return true;
128131
}
129132

130-
public boolean refreshAccount() throws SunSynkAuthenticateException {
133+
/**
134+
* Checks if the bearer token is near expiry and if it is refreshes by logging in.
135+
*
136+
* @throws SunSynkAuthenticateException
137+
*/
138+
public synchronized void refreshAccount() throws SunSynkAuthenticateException {
131139
try {
132140
SunSynkAccountConfig accountConfig = this.accountConfig;
133141
if (accountConfig == null) {
134142
throw new SunSynkTokenException("No account config");
135143
}
136-
this.sunAccount.refreshAccount(accountConfig.getEmail());
144+
long expiresFromNow = this.sunAccount.checkExpireTime();
145+
if (expiresFromNow < EXPIRY_SECONDS) {
146+
logger.debug("Account configuration token about to expire - logging in.");
147+
if (configAccount() != true) {
148+
throw new SunSynkTokenException("failed to config account");
149+
}
150+
} else {
151+
Duration d = Duration.ofSeconds(expiresFromNow);
152+
logger.debug(
153+
"Account configuration token not expired, valid for: {} days, {} hours, {} minutes, {} seconds",
154+
d.toDays(), d.toHoursPart(), d.toMinutesPart(), d.toSecondsPart());
155+
updateStatus(ThingStatus.ONLINE);
156+
}
137157
} catch (SunSynkTokenException e) {
138158
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
139-
"Error attempting to refresh token: " + e.getMessage());
140-
return false;
159+
"Error attempting to refresh account: " + e.getMessage());
160+
throw new SunSynkAuthenticateException("" + e.getMessage());
141161
}
142-
return true;
143162
}
144163
}

bundles/org.openhab.binding.sunsynk/src/main/java/org/openhab/binding/sunsynk/internal/handler/SunSynkHandlerFactory.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
*/
1313
package org.openhab.binding.sunsynk.internal.handler;
1414

15-
import static org.openhab.binding.sunsynk.internal.SunSynkBindingConstants.*;
15+
import static org.openhab.binding.sunsynk.internal.SunSynkBindingConstants.BRIDGE_TYPE_ACCOUNT;
16+
import static org.openhab.binding.sunsynk.internal.SunSynkBindingConstants.THING_TYPE_INVERTER;
1617

1718
import java.util.Collections;
1819
import java.util.HashMap;
@@ -25,6 +26,7 @@
2526
import org.eclipse.jdt.annotation.Nullable;
2627
import org.openhab.binding.sunsynk.internal.discovery.SunSynkAccountDiscoveryService;
2728
import org.openhab.core.config.discovery.DiscoveryService;
29+
import org.openhab.core.i18n.TimeZoneProvider;
2830
import org.openhab.core.thing.Bridge;
2931
import org.openhab.core.thing.Thing;
3032
import org.openhab.core.thing.ThingTypeUID;
@@ -34,6 +36,7 @@
3436
import org.openhab.core.thing.binding.ThingHandlerFactory;
3537
import org.osgi.framework.ServiceRegistration;
3638
import org.osgi.service.component.annotations.Component;
39+
import org.osgi.service.component.annotations.Reference;
3740
import org.slf4j.Logger;
3841
import org.slf4j.LoggerFactory;
3942

@@ -57,6 +60,29 @@ public class SunSynkHandlerFactory extends BaseThingHandlerFactory {
5760
public static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPE_UIDS = Set.of(THING_TYPE_INVERTER);
5861

5962
private Map<ThingUID, ServiceRegistration<DiscoveryService>> discoveryServiceRegistrations = new HashMap<>();
63+
/**
64+
* The TimeZoneProvider service is injected by the openHAB framework (OSGi).
65+
* We use this instead of the system default to ensure the binding respects
66+
* the "Regional Settings" (Time Zone) configured by the user in the MainUI.
67+
*/
68+
private @Nullable TimeZoneProvider timeZoneProvider;
69+
70+
/**
71+
* This method is called by the framework when the TimeZoneProvider service
72+
* becomes available. It "plugs in" the service to our factory.
73+
*/
74+
@Reference
75+
protected void setTimeZoneProvider(TimeZoneProvider timeZoneProvider) {
76+
this.timeZoneProvider = timeZoneProvider;
77+
}
78+
79+
/**
80+
* This method is called if the TimeZoneProvider service is stopped or changed,
81+
* ensuring we don't keep a "stale" reference to a service that no longer exists.
82+
*/
83+
protected void unsetTimeZoneProvider(TimeZoneProvider timeZoneProvider) {
84+
this.timeZoneProvider = null;
85+
}
6086

6187
@Override
6288
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@@ -67,9 +93,15 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
6793
protected @Nullable ThingHandler createHandler(Thing thing) {
6894
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
6995

96+
// local non-null variable for the TimeZoneProvider
97+
TimeZoneProvider tzProvider = this.timeZoneProvider;
7098
if (thingTypeUID.equals(THING_TYPE_INVERTER)) {
99+
if (tzProvider == null) { // If null binding isn't ready yet.
100+
logger.debug("Could not create handler for {}: TimeZoneProvider is not available.", thing.getUID());
101+
return null;
102+
}
71103
logger.debug("SunSynkHandlerFactory created Inverter Handler");
72-
return new SunSynkInverterHandler(thing);
104+
return new SunSynkInverterHandler(thing, tzProvider);
73105
} else if (thingTypeUID.equals(BRIDGE_TYPE_ACCOUNT)) {
74106
SunSynkAccountHandler handler = new SunSynkAccountHandler((Bridge) thing);
75107
registerAccountDiscoveryService(handler);

0 commit comments

Comments
 (0)