[haassohnpelletstove] Initial contribution (#10595)

Signed-off-by: Christian Feininger <tron81@gmx.de>
This commit is contained in:
chingon007 2021-04-29 20:59:37 +02:00 committed by GitHub
parent e74cb82be7
commit 7fb7c65306
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1173 additions and 0 deletions

View File

@ -97,6 +97,7 @@
/bundles/org.openhab.binding.gpstracker/ @gbicskei
/bundles/org.openhab.binding.gree/ @markus7017
/bundles/org.openhab.binding.groheondus/ @FlorianSW
/bundles/org.openhab.binding.haassohnpelletstove/ @chingon007
/bundles/org.openhab.binding.harmonyhub/ @digitaldan
/bundles/org.openhab.binding.haywardomnilogic/ @matchews
/bundles/org.openhab.binding.hdanywhere/ @kgoderis

View File

@ -471,6 +471,11 @@
<artifactId>org.openhab.binding.groheondus</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.haassohnpelletstove</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.harmonyhub</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,69 @@
# Haas Sohn Pellet Stove Binding
The binding for Haassohnpelletstove communicates with a Haas and Sohn Pelletstove through the optional
WIFI module. More information about the WIFI module can be found here: https://www.haassohn.com/de/ihr-plus/WLAN-Funktion
## Supported Things
| Things | Description | Thing Type |
|--------|--------------|------------|
| haassohnpelletstove | Control of a Haas & Sohn Pellet Stove| oven|
## Thing Configuration
In general two parameters are required. The IP-Address of the WIFI-Modul of the Stove in the local Network and the Access PIN of the Stove.
The PIN can be found directly at the stove under the Menue/Network/WLAN-PIN
```
Thing haassohnpelletstove:oven:myOven "Pelletstove" [ hostIP="192.168.0.23", hostPIN="1234"]
```
## Channels
The following channels are yet supported:
| Channel | Type | Access| Description|
|---------|-------|-------|------------|
| power| Switch | read/write|Turn the stove on/off|
|channelIsTemp|Number:Temperature|read|Receives the actual temperature of the stove|
|channelSpTemp|Number:Temperature|read/write|Receives and sets the target temperature of the stove|
|channelMode|String|read|Receives the actual mode the stove is in like heating, cooling, error, ....|
|channelEcoMode|Switch|read/write|Turn the eco mode of the stove on/off|
|channelIngitions|Number|read|Amount of ignitions of the stove|
|channelMaintenanceIn|Number:Mass|read|States the next maintenance in kg|
|channelCleaningIn|String|read|States the next cleaning window in hours:minutes as string|
|channelConsumption|Number:Mass|read|Total consumption of the stove|
|channelOnTime|Number|read|Operation hours of the stove|
## Full Example
demo.items:
```
Number:Temperature isTemp { channel="oven:channelIsTemp" }
Number:Temperature spTemp { channel="oven:channelSpTemp" }
String mode { channel="oven:channelMode" }
Switch power { channel="oven:power" }
```
## Google Assistant configuration
See also: https://www.openhab.org/docs/ecosystem/google-assistant/
googleassistantdemo.items
```
Group g_FeuerThermostat "FeuerThermostat" {ga="Thermostat" }
Number StatusFeuer "Status Feuer" (g_FeuerThermostat) { ga="thermostatMode" }
Number ZieltemperaturFeuer "ZieltemperaturFeuer" (g_FeuerThermostat) {ga="thermostatTemperatureSetpoint"}
Number TemperaturFeuer "TemperaturFeuer" (g_FeuerThermostat) {ga="thermostatTemperatureAmbient"}
```
## Tested Hardware
The binding was successfully tested with the following ovens:
- HSP 7 DIANA
- HSP6 434.08

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.haassohnpelletstove</artifactId>
<name>openHAB Add-ons :: Bundles :: Haas + Sohn Pelletstove Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.haassohnpelletstove-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-haassohnpelletstove" description="Haas + Sohn Pelletstove Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.haassohnpelletstove/${project.version}</bundle>
</feature>
</features>

View File

@ -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.haassohnpelletstove.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link HaasSohnpelletstoveBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Christian Feininger - Initial contribution
*/
@NonNullByDefault
public class HaasSohnpelletstoveBindingConstants {
private static final String BINDING_ID = "haassohnpelletstove";
public static final ThingTypeUID THING_TYPE_OVEN = new ThingTypeUID(BINDING_ID, "oven");
public static final String CHANNELISTEMP = "channelIsTemp";
public static final String CHANNELMODE = "channelMode";
public static final String CHANNELSPTEMP = "channelSpTemp";
public static final String CHANNELPOWER = "power";
public static final String CHANNELECOMODE = "channelEcoMode";
public static final String CHANNELIGNITIONS = "channelIgnitions";
public static final String CHANNELMAINTENANCEIN = "channelMaintenanceIn";
public static final String CHANNELCLEANINGIN = "channelCleaningIn";
public static final String CHANNELCONSUMPTION = "channelConsumption";
public static final String CHANNELONTIME = "channelOnTime";
}

View File

@ -0,0 +1,29 @@
/**
* 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.haassohnpelletstove.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link HaasSohnpelletstoveConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Christian Feininger - Initial contribution
*/
@NonNullByDefault
public class HaasSohnpelletstoveConfiguration {
public @Nullable String hostIP = null;
public @Nullable String hostPIN = null;
public int refreshRate = 30;
}

View File

@ -0,0 +1,318 @@
/**
* 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.haassohnpelletstove.internal;
import static org.openhab.binding.haassohnpelletstove.internal.HaasSohnpelletstoveBindingConstants.*;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.Unit;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link HaasSohnpelletstoveHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Christian Feininger - Initial contribution
*/
@NonNullByDefault
public class HaasSohnpelletstoveHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(HaasSohnpelletstoveHandler.class);
private @Nullable ScheduledFuture<?> refreshJob;
private HaasSohnpelletstoveConfiguration config = new HaasSohnpelletstoveConfiguration();
boolean resultOk = false;
private HaasSohnpelletstoveJSONCommunication serviceCommunication;
private boolean automaticRefreshing = false;
private Map<String, Boolean> linkedChannels = new HashMap<String, Boolean>();
public HaasSohnpelletstoveHandler(Thing thing) {
super(thing);
serviceCommunication = new HaasSohnpelletstoveJSONCommunication();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (channelUID.getId().equals(CHANNELPOWER)) {
String postData = null;
if (command.equals(OnOffType.ON)) {
postData = "{\"prg\":true}";
} else if (command.equals(OnOffType.OFF)) {
postData = "{\"prg\":false}";
}
if (postData != null) {
logger.debug("Executing {} command", CHANNELPOWER);
updateOvenData(postData);
}
} else if (channelUID.getId().equals(CHANNELSPTEMP)) {
if (command instanceof QuantityType<?>) {
QuantityType<?> value = (QuantityType<?>) command;
Unit<Temperature> unit = SIUnits.CELSIUS;
value = value.toUnit(unit);
if (value != null) {
double a = value.doubleValue();
String postdata = "{\"sp_temp\":" + a + "}";
logger.debug("Executing {} command", CHANNELSPTEMP);
updateOvenData(postdata);
}
} else {
logger.debug("Error. Command is the wrong type: {}", command.toString());
}
} else if (channelUID.getId().equals(CHANNELECOMODE)) {
String postData = null;
if (command.equals(OnOffType.ON)) {
postData = "{\"eco_mode\":true}";
} else if (command.equals(OnOffType.OFF)) {
postData = "{\"eco_mode\":false}";
}
if (postData != null) {
logger.debug("Executing {} command", CHANNELECOMODE);
updateOvenData(postData);
}
}
}
/**
* Calls the service to update the oven data
*
* @param postdata
*/
private boolean updateOvenData(@Nullable String postdata) {
Helper message = new Helper();
if (serviceCommunication.updateOvenData(postdata, message, this.getThing().getUID().toString())) {
updateStatus(ThingStatus.ONLINE);
return true;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
message.getStatusDesription());
return false;
}
}
@Override
public void initialize() {
logger.debug("Initializing haassohnpelletstove handler for thing {}", getThing().getUID());
config = getConfigAs(HaasSohnpelletstoveConfiguration.class);
boolean validConfig = true;
String errors = "";
String statusDescr = null;
if (config.refreshRate < 0 && config.refreshRate > 999) {
errors += " Parameter 'refresh Rate' greater then 0 and less then 1000.";
statusDescr = "Parameter 'refresh Rate' greater then 0 and less then 1000.";
validConfig = false;
}
if (config.hostIP == null) {
errors += " Parameter 'hostIP' must be configured.";
statusDescr = "IP Address must be configured!";
validConfig = false;
}
if (config.hostPIN == null) {
errors += " Parameter 'hostPin' must be configured.";
statusDescr = "PIN must be configured!";
validConfig = false;
}
errors = errors.trim();
Helper message = new Helper();
message.setStatusDescription(statusDescr);
if (validConfig) {
serviceCommunication.setConfig(config);
if (serviceCommunication.refreshOvenConnection(message, this.getThing().getUID().toString())) {
if (updateOvenData(null)) {
updateStatus(ThingStatus.ONLINE);
updateLinkedChannels();
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message.getStatusDesription());
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message.getStatusDesription());
}
}
private void updateLinkedChannels() {
verifyLinkedChannel(CHANNELISTEMP);
verifyLinkedChannel(CHANNELMODE);
verifyLinkedChannel(CHANNELPOWER);
verifyLinkedChannel(CHANNELSPTEMP);
verifyLinkedChannel(CHANNELECOMODE);
verifyLinkedChannel(CHANNELIGNITIONS);
verifyLinkedChannel(CHANNELMAINTENANCEIN);
verifyLinkedChannel(CHANNELCLEANINGIN);
verifyLinkedChannel(CHANNELCONSUMPTION);
verifyLinkedChannel(CHANNELONTIME);
if (!linkedChannels.isEmpty()) {
updateOvenData(null);
for (Channel channel : getThing().getChannels()) {
updateChannel(channel.getUID().getId());
}
startAutomaticRefresh();
automaticRefreshing = true;
}
}
private void verifyLinkedChannel(String channelID) {
if (isLinked(channelID) && !linkedChannels.containsKey(channelID)) {
linkedChannels.put(channelID, true);
}
}
@Override
public void dispose() {
stopScheduler();
}
private void stopScheduler() {
ScheduledFuture<?> job = refreshJob;
if (job != null) {
job.cancel(true);
}
refreshJob = null;
}
/**
* Start the job refreshing the oven status
*/
private void startAutomaticRefresh() {
ScheduledFuture<?> job = refreshJob;
if (job == null || job.isCancelled()) {
int period = config.refreshRate;
refreshJob = scheduler.scheduleWithFixedDelay(this::run, 0, period, TimeUnit.SECONDS);
}
}
private void run() {
updateOvenData(null);
for (Channel channel : getThing().getChannels()) {
updateChannel(channel.getUID().getId());
}
}
@Override
public void channelLinked(ChannelUID channelUID) {
if (!automaticRefreshing) {
logger.debug("Start automatic refreshing");
startAutomaticRefresh();
automaticRefreshing = true;
}
verifyLinkedChannel(channelUID.getId());
updateChannel(channelUID.getId());
}
@Override
public void channelUnlinked(ChannelUID channelUID) {
linkedChannels.remove(channelUID.getId());
if (linkedChannels.isEmpty()) {
automaticRefreshing = false;
stopScheduler();
logger.debug("Stop automatic refreshing");
}
}
private void updateChannel(String channelId) {
if (isLinked(channelId)) {
State state = null;
HaasSohnpelletstoveJsonDataDTO data = serviceCommunication.getOvenData();
if (data != null) {
switch (channelId) {
case CHANNELISTEMP:
state = new QuantityType<Temperature>(Double.valueOf(data.getisTemp()), SIUnits.CELSIUS);
update(state, channelId);
break;
case CHANNELMODE:
state = new StringType(data.getMode());
update(state, channelId);
break;
case CHANNELPOWER:
update(OnOffType.from(data.getPrg()), channelId);
break;
case CHANNELECOMODE:
update(OnOffType.from(data.getEcoMode()), channelId);
break;
case CHANNELSPTEMP:
state = new QuantityType<Temperature>(Double.valueOf(data.getspTemp()), SIUnits.CELSIUS);
update(state, channelId);
break;
case CHANNELCLEANINGIN:
String cleaning = data.getCleaningIn();
double time = Double.parseDouble(cleaning);
time = time / 60;
DecimalFormat df = new DecimalFormat("0.00");
state = new StringType(df.format(time));
update(state, channelId);
break;
case CHANNELCONSUMPTION:
state = new StringType(data.getConsumption());
update(state, channelId);
break;
case CHANNELIGNITIONS:
state = new StringType(data.getIgnitions());
update(state, channelId);
break;
case CHANNELMAINTENANCEIN:
state = new StringType(data.getMaintenanceIn());
update(state, channelId);
break;
case CHANNELONTIME:
state = new StringType(data.getOnTime());
update(state, channelId);
break;
}
}
}
}
/**
* Updates the State of the given channel
*
* @param state
* @param channelId
*/
private void update(@Nullable State state, String channelId) {
logger.debug("Update channel {} with state {}", channelId, (state == null) ? "null" : state.toString());
if (state != null) {
updateState(channelId, state);
} else {
updateState(channelId, UnDefType.NULL);
}
}
}

View File

@ -0,0 +1,54 @@
/**
* 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.haassohnpelletstove.internal;
import static org.openhab.binding.haassohnpelletstove.internal.HaasSohnpelletstoveBindingConstants.THING_TYPE_OVEN;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
/**
* The {@link HaasSohnpelletstoveHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Christian Feininger - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.haassohnpelletstove", service = ThingHandlerFactory.class)
public class HaasSohnpelletstoveHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_OVEN);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_OVEN.equals(thingTypeUID)) {
return new HaasSohnpelletstoveHandler(thing);
}
return null;
}
}

View File

@ -0,0 +1,223 @@
/**
* 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.haassohnpelletstove.internal;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* This class handles the JSON communication with the Wifi Modul of the Stove
*
* @author Christian Feininger - Initial contribution
*
*/
@NonNullByDefault
public class HaasSohnpelletstoveJSONCommunication {
private final Logger logger = LoggerFactory.getLogger(HaasSohnpelletstoveJSONCommunication.class);
private HaasSohnpelletstoveConfiguration config;
private Gson gson;
private @Nullable String xhspin;
private @Nullable HaasSohnpelletstoveJsonDataDTO ovenData;
public HaasSohnpelletstoveJSONCommunication() {
gson = new Gson();
ovenData = new HaasSohnpelletstoveJsonDataDTO();
xhspin = "";
config = new HaasSohnpelletstoveConfiguration();
}
/**
* Refreshes the oven Connection with the internal oven token.
*
* @param message Message object to pass errors to the calling method.
* @param thingUID Thing UID for logging purposes
* @return true if no error occurred, false otherwise.
*/
public boolean refreshOvenConnection(Helper message, String thingUID) {
if (config.hostIP == null || config.hostPIN == null) {
message.setStatusDescription("Error in configuration. Please recreate Thing.");
return false;
}
HaasSohnpelletstoveJsonDataDTO result = null;
boolean resultOk = false;
String error = "", errorDetail = "", statusDescr = "";
String urlStr = "http://" + config.hostIP + "/status.cgi";
String response = null;
try {
response = HttpUtil.executeUrl("GET", urlStr, 10000);
logger.debug("OvenData = {}", response);
result = gson.fromJson(response, HaasSohnpelletstoveJsonDataDTO.class);
resultOk = true;
} catch (IOException e) {
logger.debug("Error processiong Get request {}", urlStr);
statusDescr = "Timeout error with" + config.hostIP
+ ". Cannot find service on give IP. Please verify the IP-Address!";
errorDetail = e.getMessage();
resultOk = false;
} catch (Exception e) {
logger.debug("Unknwon Error: {}", e.getMessage());
errorDetail = e.getMessage();
resultOk = false;
}
if (resultOk) {
ovenData = result;
xhspin = getValidXHSPIN(ovenData);
} else {
logger.debug("Setting thing '{}' to OFFLINE: Error '{}': {}", thingUID, error, errorDetail);
ovenData = new HaasSohnpelletstoveJsonDataDTO();
}
message.setStatusDescription(statusDescr);
return resultOk;
}
/**
* Gets the status of the oven
*
* @return true if success or false in case of error
*/
public boolean updateOvenData(@Nullable String postData, Helper helper, String thingUID) {
String statusDescr = "";
boolean resultOk = false;
String error = "", errorDetail = "";
if (config.hostIP == null || config.hostPIN == null) {
return false;
}
String urlStr = "http://" + config.hostIP + "/status.cgi";
// Run the HTTP POST request and get the JSON response from Oven
String response = null;
Properties httpHeader = new Properties();
if (postData != null) {
try {
InputStream targetStream = new ByteArrayInputStream(postData.getBytes("UTF-8"));
refreshOvenConnection(helper, thingUID);
httpHeader = createHeader(postData);
response = HttpUtil.executeUrl("POST", urlStr, httpHeader, targetStream, "application/json", 10000);
resultOk = true;
logger.debug("Execute POST request with content to {} with header: {}", urlStr, httpHeader.toString());
} catch (UnsupportedEncodingException e1) {
logger.debug("Wrong encoding found. Only UTF-8 is supported.");
statusDescr = "Encoding of oven is not supported. Only UTF-8 is supported.";
resultOk = false;
} catch (IOException e) {
logger.debug("Error processiong POST request {}", urlStr);
statusDescr = "Cannot execute command on Stove. Please verify connection and Thing Status";
resultOk = false;
}
} else {
try {
refreshOvenConnection(helper, thingUID);
httpHeader = createHeader(null);
response = HttpUtil.executeUrl("POST", urlStr, httpHeader, null, "", 10000);
resultOk = true;
logger.debug("Execute POST request to {} with header: {}", urlStr, httpHeader.toString());
} catch (IOException e) {
logger.debug("Error processiong POST request {}", e.getMessage());
String message = e.getMessage();
if (message != null && message.contains("Authentication challenge without WWW-Authenticate ")) {
statusDescr = "Cannot connect to stove. Given PIN: " + config.hostPIN + " is incorrect!";
}
resultOk = false;
}
}
if (resultOk) {
logger.debug("OvenData = {}", response);
ovenData = gson.fromJson(response, HaasSohnpelletstoveJsonDataDTO.class);
} else {
logger.debug("Setting thing '{}' to OFFLINE: Error '{}': {}", thingUID, error, errorDetail);
ovenData = new HaasSohnpelletstoveJsonDataDTO();
}
helper.setStatusDescription(statusDescr);
return resultOk;
}
/**
* Creates the header for the Post Request
*
* @return The created Header Properties
* @throws UnsupportedEncodingException
*/
private Properties createHeader(@Nullable String postData) throws UnsupportedEncodingException {
Properties httpHeader = new Properties();
httpHeader.setProperty("Host", config.hostIP);
httpHeader.setProperty("Accept", "*/*");
httpHeader.setProperty("Proxy-Connection", "keep-alive");
httpHeader.setProperty("X-BACKEND-IP", "https://app.haassohn.com");
httpHeader.setProperty("Accept-Language", "de-DE;q=1.0, en-DE;q=0.9");
httpHeader.setProperty("Accept-Encoding", "gzip;q=1.0, compress;q=0.5");
httpHeader.setProperty("token", "32 bytes");
httpHeader.setProperty("Content-Type", "application/json");
if (postData != null) {
int a = postData.getBytes("UTF-8").length;
httpHeader.setProperty(xhspin, Integer.toString(a));
}
httpHeader.setProperty("User-Agent", "ios");
httpHeader.setProperty("Connection", "keep-alive");
httpHeader.setProperty("X-HS-PIN", xhspin);
return httpHeader;
}
/**
* Generate the valid encrypted string to communicate with the oven.
*
* @param ovenData
* @return
*/
private @Nullable String getValidXHSPIN(@Nullable HaasSohnpelletstoveJsonDataDTO ovenData) {
if (ovenData != null && config.hostPIN != null) {
String nonce = ovenData.getNonce();
String hostPIN = config.hostPIN;
String ePin = MD5Utils.getMD5String(hostPIN);
return MD5Utils.getMD5String(nonce + ePin);
} else {
return null;
}
}
/**
* Set the config for service to communicate
*
* @param config2
*/
public void setConfig(@Nullable HaasSohnpelletstoveConfiguration config2) {
if (config2 != null) {
this.config = config2;
}
}
/**
* Returns the actual stored Oven Data
*
* @return
*/
@Nullable
public HaasSohnpelletstoveJsonDataDTO getOvenData() {
return this.ovenData;
}
}

View File

@ -0,0 +1,152 @@
/**
* 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.haassohnpelletstove.internal;
import com.google.gson.annotations.SerializedName;
/**
* The {@link HaasSohnpelletstoveJsonDataDTO} is the Java class used to map the JSON
* response to a Oven request.
*
* @author Christian Feininger - Initial contribution
*/
public class HaasSohnpelletstoveJsonDataDTO {
metadata meta = new metadata();
boolean prg;
boolean wprg;
String mode = "";
@SerializedName("sp_temp")
String spTemp = "";
@SerializedName("is_temp")
String isTemp = "";
@SerializedName("ht_char")
String htChar = "";
@SerializedName("weekprogram")
private wprogram[] weekprogram;
@SerializedName("error")
private err[] error;
@SerializedName("eco_mode")
boolean ecoMode;
boolean pgi;
String ignitions = "";
@SerializedName("on_time")
String onTime = "";
String consumption = "";
@SerializedName("maintenance_in")
String maintenanceIn = "";
@SerializedName("cleaning_in")
String cleaningIn = "";
/***
* Get the nonce
*
* @return nonce
*/
public String getNonce() {
return this.meta.getNonce();
}
/**
* Returns the is Temperature of the Oven
*
* @return
*/
public String getisTemp() {
return isTemp;
}
public boolean getEcoMode() {
return ecoMode;
}
public String getIgnitions() {
return ignitions;
}
public String getOnTime() {
return onTime;
}
public String getConsumption() {
return consumption;
}
public String getMaintenanceIn() {
return maintenanceIn;
}
public String getCleaningIn() {
return cleaningIn;
}
/***
* JSON response
*
* @return JSON response as object
*/
public HaasSohnpelletstoveJsonDataDTO getResponse() {
return this;
}
public class metadata {
@SerializedName("sw_version")
String swVersion = "";
@SerializedName("hw_version")
String hwVersion = "";
@SerializedName("bootl_version")
String bootlVersion = "";
@SerializedName("wifi_sw_version")
String wifiSWVersion = "";
@SerializedName("wifi_bootl_version")
String wifiBootlVersion = "";
String sn = "";
String typ = "";
String language = "";
String nonce = "";
@SerializedName("eco_editable")
String ecoEditable = "";
String ts = "";
String ean = "";
boolean rau;
@SerializedName("wlan_features")
private String[] wlan_features;
public String getNonce() {
return nonce;
}
}
public class err {
String time = "";
String nr = "";
}
public class wprogram {
String day = "";
String begin = "";
String end = "";
String temp = "";
}
public String getMode() {
return mode;
}
public String getspTemp() {
return spTemp;
}
public boolean getPrg() {
return prg;
}
}

View File

@ -0,0 +1,48 @@
/**
* 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.haassohnpelletstove.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link Helper} is a Helper class to overcome Call by value for a Status Description.
*
*
* @author Christian Feininger - Initial contribution
*/
@NonNullByDefault
public class Helper {
private String statusDescription = "";
/***
* Gets the Status Description
*
* @return
*/
public String getStatusDesription() {
return statusDescription;
}
/***
* Sets the Status Description
*
* @param status
*/
public void setStatusDescription(@Nullable String status) {
if (status != null) {
statusDescription = statusDescription + "\n" + status;
}
}
}

View File

@ -0,0 +1,66 @@
/**
* 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.haassohnpelletstove.internal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link MD5Utils} is responsible for generating the MD5 hash
*
*
* @author Christian Feininger - Initial contribution
*/
@NonNullByDefault
public class MD5Utils {
private static final Charset UTF_8 = StandardCharsets.UTF_8;
private static byte[] digest(byte[] input) {
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
byte[] result = md.digest(input);
return result;
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/***
* Returns an encrypted MD5 string
*
* @param input nonce as input
* @return Encrypted String
*/
public static String getMD5String(@Nullable String input) {
if (input != null) {
byte[] md5InBytes = MD5Utils.digest(input.getBytes(UTF_8));
return bytesToHex(md5InBytes);
}
return "";
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="haassohnpelletstove" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Haas and Sohn Pelletstove Binding</name>
<description>This binding communicates with Haas and Sohn Pelletstoves through the optional WIFI module. It allows to
power the stove on and off and receives different operation information.</description>
</binding:binding>

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="haassohnpelletstove"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Haas Sohn Thing Type -->
<thing-type id="oven">
<label>Haas+Sohn Oven</label>
<description>The binding for Haas and Sohn Pelletstove communicates with a Haas and Sohn Pelletstove through the
optional
WLAN-Modul. More information can be found here: https://www.haassohn.com/de/ihr-plus/WLAN-Funktion. It allows
to power on/off the stove as well as receiving different operation information about the stove.</description>
<channels>
<channel id="channelIsTemp" typeId="isTemp"/>
<channel id="channelMode" typeId="mode"/>
<channel id="channelSpTemp" typeId="spTemp"/>
<channel id="power" typeId="power"/>
<channel id="channelEcoMode" typeId="ecoMode"/>
<channel id="channelIgnitions" typeId="ignitions"/>
<channel id="channelMaintenanceIn" typeId="maintenanceIn"/>
<channel id="channelCleaningIn" typeId="cleaningIn"/>
<channel id="channelConsumption" typeId="consumption"/>
<channel id="channelOnTime" typeId="onTime"/>
</channels>
<config-description>
<parameter name="hostIP" type="text" required="true">
<label>IP Address</label>
<description>Please add the IP Address of the WIFI Module of the Haas and Sohn oven here</description>
<context>network-address</context>
</parameter>
<parameter name="hostPIN" type="text" required="true" pattern="[0-9]{4}">
<label>PIN</label>
<description>Please add the PIN of your oven here. You can find it in the Menu directly in your oven.</description>
</parameter>
<parameter name="refreshRate" type="integer" unit="s" min="0" max="1000">
<label>Refresh Rate</label>
<description>How often the Pellet Stove should schedule a refresh after a channel is linked to an item. Temperature
data will be refreshed according this set time in seconds. Valid input is 0 - 999.
</description>
<advanced>true</advanced>
<default>30</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="isTemp">
<item-type>Number:Temperature</item-type>
<label>Is Temperature Stove</label>
<description>Receives the is temperature of the stove as number:temperature</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="mode">
<item-type>String</item-type>
<label>Mode Stove</label>
<description>Receives the actual mode of the stove as string</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="spTemp">
<item-type>Number:Temperature</item-type>
<label>Set Temperature Stove</label>
<description>Set the target temperature of the stove as number:temperature</description>
</channel-type>
<channel-type id="power">
<item-type>Switch</item-type>
<label>On/Off Stove</label>
<description>To turn the stove on/off as switch</description>
</channel-type>
<channel-type id="ecoMode">
<item-type>Switch</item-type>
<label>On/Off Eco Mode</label>
<description>To turn the Eco Mode on/off for the stove as switch</description>
</channel-type>
<channel-type id="ignitions">
<item-type>Number</item-type>
<label>Ignitions Stove</label>
<description>Receives the total amount of ignitions of the stove as string</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="maintenanceIn">
<item-type>Number:Mass</item-type>
<label>Next Maintenance</label>
<description>Provides a pellet forecast when the stove need to be maintained next in kilogram as number:mass</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="cleaningIn">
<item-type>String</item-type>
<label>Next Cleaning Window</label>
<description>Provides a time forecast in hours:minutes when the stove need to be cleaned next as String</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="consumption">
<item-type>Number:Mass</item-type>
<label>Total Consumption Stove</label>
<description>Provides the information about the total consumption of pellets of the stove as number:mass</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="onTime">
<item-type>Number</item-type>
<label>Operation Hours Stove</label>
<description>Provides the information of the operating hours of stove as number</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -129,6 +129,7 @@
<module>org.openhab.binding.gpstracker</module>
<module>org.openhab.binding.gree</module>
<module>org.openhab.binding.groheondus</module>
<module>org.openhab.binding.haassohnpelletstove</module>
<module>org.openhab.binding.harmonyhub</module>
<module>org.openhab.binding.haywardomnilogic</module>
<module>org.openhab.binding.hdanywhere</module>