forked from openhab/openhab-addons
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[WlanThermo] Add support for new ESP32-powered devices [V3.x] (openha…
…b#9579) * Add support for ESP32 devices * Add Unit Tests Ensure Gson objects are NonNull Generify Handlers Generify Utils Signed-off-by: Christian Schlipp <[email protected]>
- Loading branch information
Showing
72 changed files
with
5,155 additions
and
1,403 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
...wlanthermo/src/main/java/org/openhab/binding/wlanthermo/internal/WlanThermoException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** | ||
* Copyright (c) 2010-2021 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.binding.wlanthermo.internal; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
|
||
/** | ||
* The {@link WlanThermoException} is thrown if an exception in WlanThermoBinding occurs. | ||
* | ||
* @author Christian Schlipp - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public class WlanThermoException extends Exception { | ||
|
||
static final long serialVersionUID = 1L; | ||
|
||
public WlanThermoException(String reason) { | ||
super(reason, null); | ||
} | ||
|
||
public WlanThermoException(String message, Throwable cause) { | ||
super(message, cause); | ||
} | ||
|
||
public WlanThermoException(Throwable cause) { | ||
super(cause); | ||
} | ||
|
||
public WlanThermoException() { | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
...rc/main/java/org/openhab/binding/wlanthermo/internal/WlanThermoExtendedConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/** | ||
* Copyright (c) 2010-2021 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.binding.wlanthermo.internal; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
|
||
/** | ||
* The {@link WlanThermoExtendedConfiguration} class contains fields mapping thing configuration parameters. | ||
* | ||
* @author Christian Schlipp - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public class WlanThermoExtendedConfiguration extends WlanThermoConfiguration { | ||
|
||
/** | ||
* Username of WlanThermo user. | ||
*/ | ||
private String username = ""; | ||
|
||
/** | ||
* Password of WlanThermo user. | ||
*/ | ||
|
||
private String password = ""; | ||
|
||
public String getUsername() { | ||
return username; | ||
} | ||
|
||
public void setUsername(String username) { | ||
this.username = username; | ||
} | ||
|
||
public String getPassword() { | ||
return password; | ||
} | ||
|
||
public void setPassword(String password) { | ||
this.password = password; | ||
} | ||
} |
189 changes: 189 additions & 0 deletions
189
...g.wlanthermo/src/main/java/org/openhab/binding/wlanthermo/internal/WlanThermoHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/** | ||
* Copyright (c) 2010-2021 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.binding.wlanthermo.internal; | ||
|
||
import static org.openhab.binding.wlanthermo.internal.WlanThermoUtil.requireNonNull; | ||
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.ScheduledFuture; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.TimeoutException; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.eclipse.jetty.client.HttpClient; | ||
import org.eclipse.jetty.client.api.Authentication; | ||
import org.eclipse.jetty.client.api.AuthenticationStore; | ||
import org.eclipse.jetty.client.util.DigestAuthentication; | ||
import org.eclipse.jetty.client.util.StringContentProvider; | ||
import org.openhab.core.thing.*; | ||
import org.openhab.core.thing.binding.BaseThingHandler; | ||
import org.openhab.core.types.Command; | ||
import org.openhab.core.types.RefreshType; | ||
import org.openhab.core.types.State; | ||
import org.openhab.core.types.UnDefType; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import com.google.gson.Gson; | ||
|
||
/** | ||
* The {@link WlanThermoHandler} is responsible for handling commands, which are | ||
* sent to one of the channels. | ||
* | ||
* @author Christian Schlipp - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public abstract class WlanThermoHandler extends BaseThingHandler { | ||
|
||
private final boolean extendedConfig; | ||
protected WlanThermoConfiguration config = new WlanThermoConfiguration(); | ||
protected final HttpClient httpClient; | ||
protected final Logger logger = LoggerFactory.getLogger(WlanThermoHandler.class); | ||
protected final Gson gson = new Gson(); | ||
protected @Nullable ScheduledFuture<?> pollingScheduler; | ||
|
||
public WlanThermoHandler(Thing thing, HttpClient httpClient, boolean extendedConfig) { | ||
super(thing); | ||
this.httpClient = httpClient; | ||
this.extendedConfig = extendedConfig; | ||
} | ||
|
||
@Override | ||
public void initialize() { | ||
updateStatus(ThingStatus.UNKNOWN); | ||
try { | ||
if (extendedConfig) { | ||
config = getConfigAs(WlanThermoExtendedConfiguration.class); | ||
WlanThermoExtendedConfiguration extendedConfig = (WlanThermoExtendedConfiguration) config; | ||
if (extendedConfig.getUsername().isEmpty() && !extendedConfig.getPassword().isEmpty()) { | ||
AuthenticationStore authStore = httpClient.getAuthenticationStore(); | ||
authStore.addAuthentication(new DigestAuthentication(config.getUri(), Authentication.ANY_REALM, | ||
extendedConfig.getUsername(), extendedConfig.getPassword())); | ||
} | ||
} else { | ||
config = getConfigAs(WlanThermoConfiguration.class); | ||
} | ||
pollingScheduler = scheduler.scheduleWithFixedDelay(this::checkConnectionAndUpdate, 0, | ||
config.getPollingInterval(), TimeUnit.SECONDS); | ||
} catch (URISyntaxException e) { | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, | ||
"Failed to initialize WlanThermo: " + e.getMessage()); | ||
} | ||
} | ||
|
||
@Override | ||
public void dispose() { | ||
ScheduledFuture<?> oldScheduler = pollingScheduler; | ||
if (oldScheduler != null) { | ||
boolean stopped = oldScheduler.cancel(true); | ||
logger.debug("Stopped polling: {}", stopped); | ||
} | ||
pollingScheduler = null; | ||
} | ||
|
||
protected void checkConnectionAndUpdate() { | ||
if (this.thing.getStatus() != ThingStatus.ONLINE) { | ||
try { | ||
if (httpClient.GET(config.getUri()).getStatus() == 200) { | ||
updateStatus(ThingStatus.ONLINE); | ||
// rerun immediately to update state | ||
checkConnectionAndUpdate(); | ||
} else { | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, | ||
"WlanThermo not found under given address."); | ||
} | ||
} catch (URISyntaxException | ExecutionException | TimeoutException e) { | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, | ||
"Could not connect to WlanThermo at " + config.getIpAddress() + ": " + e.getMessage()); | ||
} catch (InterruptedException e) { | ||
logger.debug("Connection check interrupted. {}", e.getMessage()); | ||
} | ||
} else { | ||
pull(); | ||
} | ||
} | ||
|
||
protected boolean doPost(String endpoint, String json) throws InterruptedException { | ||
try { | ||
URI uri = config.getUri(endpoint); | ||
int status = httpClient.POST(uri).content(new StringContentProvider(json), "application/json") | ||
.timeout(5, TimeUnit.SECONDS).send().getStatus(); | ||
if (status == 401) { | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, | ||
"No or wrong login credentials provided. Please configure username/password for write access to WlanThermo!"); | ||
return false; | ||
} else if (status != 200) { | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, | ||
"Failed to update channel on device, Statuscode " + status + " on URI " + uri.toString()); | ||
logger.debug("Payload sent: {}", json); | ||
// Still continue to try next channel | ||
return true; | ||
} else { | ||
updateStatus(ThingStatus.ONLINE); | ||
return true; | ||
} | ||
} catch (TimeoutException | ExecutionException | URISyntaxException e) { | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, | ||
"Failed to update channel on device: " + e.getMessage()); | ||
return false; | ||
} | ||
} | ||
|
||
protected <T> T doGet(String endpoint, Class<T> object) throws InterruptedException, WlanThermoException { | ||
try { | ||
String json = httpClient.GET(config.getUri(endpoint)).getContentAsString(); | ||
logger.debug("Received at {}: {}", endpoint, json); | ||
return requireNonNull(gson.fromJson(json, object)); | ||
} catch (URISyntaxException | ExecutionException | TimeoutException | WlanThermoException e) { | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, | ||
"Update failed: " + e.getMessage()); | ||
for (Channel channel : thing.getChannels()) { | ||
updateState(channel.getUID(), UnDefType.UNDEF); | ||
} | ||
throw new WlanThermoException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public void handleCommand(ChannelUID channelUID, Command command) { | ||
if (command instanceof RefreshType) { | ||
try { | ||
State s = getState(channelUID); | ||
updateState(channelUID, s); | ||
} catch (WlanThermoException e) { | ||
logger.debug("Could not handle command of type {} for channel {}!", | ||
command.getClass().toGenericString(), channelUID.getId()); | ||
} | ||
} else { | ||
if (setState(channelUID, command) && thing.getStatus() == ThingStatus.ONLINE) { | ||
logger.debug("Data updated, pushing changes"); | ||
scheduler.execute(this::push); | ||
} else { | ||
logger.debug("Could not handle command of type {} for channel {}!", | ||
command.getClass().toGenericString(), channelUID.getId()); | ||
} | ||
} | ||
} | ||
|
||
protected abstract void push(); | ||
|
||
protected abstract void pull(); | ||
|
||
protected abstract State getState(ChannelUID channelUID) | ||
throws WlanThermoInputException, WlanThermoUnknownChannelException; | ||
|
||
protected abstract boolean setState(ChannelUID channelUID, Command command); | ||
} |
Oops, something went wrong.