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

[WlanThermo] Add support for new Nano V3, Mini V3, Link V1, Mini V1/V2(ESP32) devices [V3.x] #9579

Merged
merged 40 commits into from
Jan 18, 2021
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8a18317
Add support for ESP32 devices
CSchlipp Nov 19, 2020
31a5d04
Fix Compiler warnings in V3 branch
CSchlipp Dec 29, 2020
24680ba
moved auto-generated classed to dto package
CSchlipp Dec 31, 2020
7f85970
fix typo
CSchlipp Dec 31, 2020
5662306
fix imports
CSchlipp Dec 31, 2020
dd494b9
reduce logs
CSchlipp Dec 31, 2020
3021e06
fix formatting
CSchlipp Dec 31, 2020
6356ac2
removed nullable config
CSchlipp Dec 31, 2020
5855a56
Only log payload if response code != 200
CSchlipp Jan 1, 2021
c76ecfa
fix compiler warnings
CSchlipp Jan 1, 2021
db1da51
fix channel naming
CSchlipp Jan 1, 2021
ebe4011
fixed checkstyle error
CSchlipp Jan 1, 2021
c675da7
Add license headers
CSchlipp Jan 1, 2021
65628c9
Rebase & Update License Headers
CSchlipp Jan 3, 2021
d365c23
Fix scheduled task not cancelled on dispose
CSchlipp Jan 3, 2021
aff5cb5
Retrigger Jenkins build
CSchlipp Jan 4, 2021
609d213
fix constants
CSchlipp Jan 9, 2021
6f1f5e9
make Util classes NonNullByDefault
CSchlipp Jan 9, 2021
81fc86a
Rework CommandHandler Returns
CSchlipp Jan 9, 2021
1823458
Set Thing status to OFFLINE on Communication Error
CSchlipp Jan 9, 2021
00908e7
Don't try to update other channels if one fails due to wrong credentials
CSchlipp Jan 9, 2021
d108957
Exit immediately if interrupted
CSchlipp Jan 9, 2021
7a32b38
merge checkConnection and update method
CSchlipp Jan 9, 2021
b2f971b
change rssi channel to category Number
CSchlipp Jan 9, 2021
a7de6f6
Improve Exception handling
CSchlipp Jan 10, 2021
c3dab0e
Make Pitmaster values writeable for ESP32 and NanoV1
CSchlipp Jan 12, 2021
c1c9ac7
Only push changes if thing is ONLINE
CSchlipp Jan 13, 2021
cf34d9c
Improve Triggers
CSchlipp Jan 14, 2021
eb1c960
reduce updates to properties
CSchlipp Jan 14, 2021
0aaff63
return early on InterruptedException
CSchlipp Jan 14, 2021
e342374
fix pitmaster getState
CSchlipp Jan 14, 2021
2b7f277
Changed QuantityType to DecimalType for channels without unit
CSchlipp Jan 14, 2021
89a2424
extract POST request to new method
CSchlipp Jan 14, 2021
26aef3f
spotless:apply
CSchlipp Jan 15, 2021
a273604
Add Unit Tests
CSchlipp Jan 17, 2021
d7c3d69
Check if pitmaster is enabled for ESP32 devices
CSchlipp Jan 17, 2021
483da47
Revert RSSI conversion for NanoV1 devices
CSchlipp Jan 17, 2021
1270e69
fix Fallthrough bug introduced by change of return handling
CSchlipp Jan 18, 2021
00c450f
remove unused throws
CSchlipp Jan 18, 2021
7b15ff9
remove unused import
CSchlipp Jan 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.binding.wlanthermo.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.thing.ThingTypeUID;

/**
Expand All @@ -27,11 +28,17 @@ public class WlanThermoBindingConstants {
private static final String BINDING_ID = "wlanthermo";

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_WLANTHERMO_NANO = new ThingTypeUID(BINDING_ID, "nano");
public static final ThingTypeUID THING_TYPE_WLANTHERMO_NANO_V1 = new ThingTypeUID(BINDING_ID, "nano");
public static final ThingTypeUID THING_TYPE_WLANTHERMO_MINI = new ThingTypeUID(BINDING_ID, "mini");
public static final ThingTypeUID THING_TYPE_WLANTHERMO_ESP32 = new ThingTypeUID(BINDING_ID, "esp32");

// ThreadPool
public static final String WLANTHERMO_THREAD_POOL = "wlanthermo";
// Properties
public static final String PROPERTY_MODEL = "model";
public static final String PROPERTY_SERIAL = "serial";
public static final String PROPERTY_ESP32_BT_ENABLED = "esp32_bt_enabled";
public static final String PROPERTY_ESP32_PM_ENABLED = "esp32_pm_enabled";
public static final String PROPERTY_ESP32_TEMP_CHANNELS = "esp32_temp_channels";
public static final String PROPERTY_ESP32_PM_CHANNELS = "esp32_pm_channels";

// List of all Channel ids
// System Channels
Expand All @@ -43,16 +50,7 @@ public class WlanThermoBindingConstants {
public static final String SYSTEM_CPU_LOAD = "cpu_load";
public static final String SYSTEM_CPU_TEMP = "cpu_temp";

public static final String CHANNEL0 = "channel0";
public static final String CHANNEL1 = "channel1";
public static final String CHANNEL2 = "channel2";
public static final String CHANNEL3 = "channel3";
public static final String CHANNEL4 = "channel4";
public static final String CHANNEL5 = "channel5";
public static final String CHANNEL6 = "channel6";
public static final String CHANNEL7 = "channel7";
public static final String CHANNEL8 = "channel8";
public static final String CHANNEL9 = "channel9";
public static final String CHANNEL_PREFIX = "channel";

public static final String CHANNEL_NAME = "name";
public static final String CHANNEL_TYP = "typ";
Expand All @@ -67,6 +65,9 @@ public class WlanThermoBindingConstants {
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_COLOR_NAME = "color_name";

public static final String CHANNEL_PITMASTER_PREFIX = "pit";
public static final String CHANNEL_PITMASTER_1 = "pit1";
public static final String CHANNEL_PITMASTER_2 = "pit2";
public static final String CHANNEL_PITMASTER_ENABLED = "enabled"; // Mini
public static final String CHANNEL_PITMASTER_CURRENT = "current"; // Mini
public static final String CHANNEL_PITMASTER_SETPOINT = "setpoint"; // Mini+Nano
Expand All @@ -76,7 +77,12 @@ public class WlanThermoBindingConstants {
public static final String CHANNEL_PITMASTER_STATE = "state"; // Nano
public static final String CHANNEL_PITMASTER_PIDPROFILE = "pid_id"; // Nano

public static final String TRIGGER_ALARM_OFF = "OFF";
public static final String TRIGGER_NONE = "";
public static final String TRIGGER_ALARM_MIN = "MIN";
public static final String TRIGGER_ALARM_MAX = "MAX";

public static final DecimalType SIGNAL_STRENGTH_4 = new DecimalType(4);
public static final DecimalType SIGNAL_STRENGTH_3 = new DecimalType(3);
public static final DecimalType SIGNAL_STRENGTH_2 = new DecimalType(2);
public static final DecimalType SIGNAL_STRENGTH_1 = new DecimalType(1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* The {@link WlanThermoMiniConfiguration} class contains fields mapping thing configuration parameters.
* The {@link WlanThermoConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Christian Schlipp - Initial contribution
*/
@NonNullByDefault
public class WlanThermoMiniConfiguration {
public class WlanThermoConfiguration {

/**
* IP Address of WlanThermo.
Expand Down
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() {
}
}
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;
}
}
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);
}
Loading