mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[proteusecometer] Proteus Eco Meter Binding - Initial contribution (#11333)
* Proteus Eco Meter Binding Signed-off-by: Matthias Herrmann <matthias.mh.herrmann@gmail.com> * Fulfil some conventions and choose better tradeoffs Signed-off-by: Matthias Herrmann <matthias.mh.herrmann@gmail.com> * Patch shell script in another PR Signed-off-by: Matthias Herrmann <matthias.mh.herrmann@gmail.com> * Move 4 lines into another PR Signed-off-by: Matthias Herrmann <matthias.mh.herrmann@gmail.com> * Improvements Signed-off-by: Matthias Herrmann <matthias.mh.herrmann@gmail.com> * File based doc Signed-off-by: Matthias Herrmann <matthias.mh.herrmann@gmail.com> * Rename identifiers Signed-off-by: Matthias Herrmann <matthias.mh.herrmann@gmail.com> * Changed identifier Signed-off-by: Matthias Herrmann <matthias.mh.herrmann@gmail.com> * Uniformed unit pattern Signed-off-by: Matthias Herrmann <matthias.mh.herrmann@gmail.com>
This commit is contained in:
parent
8337f8b92d
commit
d2e6780140
@ -240,6 +240,7 @@
|
||||
/bundles/org.openhab.binding.plugwise/ @wborn
|
||||
/bundles/org.openhab.binding.plugwiseha/ @lsiepel
|
||||
/bundles/org.openhab.binding.powermax/ @lolodomo
|
||||
/bundles/org.openhab.binding.proteusecometer/ @2chilled
|
||||
/bundles/org.openhab.binding.pulseaudio/ @peuter
|
||||
/bundles/org.openhab.binding.pushbullet/ @hakan42
|
||||
/bundles/org.openhab.binding.pushover/ @cweitkamp
|
||||
|
@ -1181,6 +1181,11 @@
|
||||
<artifactId>org.openhab.binding.powermax</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.proteusecometer</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.pulseaudio</artifactId>
|
||||
|
20
bundles/org.openhab.binding.proteusecometer/NOTICE
Normal file
20
bundles/org.openhab.binding.proteusecometer/NOTICE
Normal file
@ -0,0 +1,20 @@
|
||||
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
|
||||
|
||||
== Third-party Content
|
||||
|
||||
jSerialComm
|
||||
* License: Apache 2.0 License
|
||||
* Project: https://github.com/Fazecast/jSerialComm
|
||||
* Source: https://github.com/Fazecast/jSerialComm
|
52
bundles/org.openhab.binding.proteusecometer/README.md
Normal file
52
bundles/org.openhab.binding.proteusecometer/README.md
Normal file
@ -0,0 +1,52 @@
|
||||
# ProteusEcoMeter Binding
|
||||
|
||||
This is the binding for the Proteus EcoMeter S, which is able to report the level of a cistern or tank.
|
||||
|
||||
Note that this binding currently supports no write channels.
|
||||
This means you have to configure your sensor by considering the manual of the product (using wireless display).
|
||||
After doing that the binding comes into play and helps you to get your measured values into openHAB.
|
||||
Please be patient while waiting for the first received data.
|
||||
The sensor reports at an interval of approx. 1h, except when the water level changes relatively fast.
|
||||
|
||||
## Supported Things
|
||||
|
||||
Proteus EcoMeter S.
|
||||
The binding has been tested with this EcoMeter sensor only.
|
||||
|
||||
## Discovery
|
||||
|
||||
No auto discovery implemented yet.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
Plug the wireless display into an USB port.
|
||||
Note [openHAB Serial Port documentation](https://www.openhab.org/docs/administration/serial.html) for general serial port configuration.
|
||||
After that you can add the device as thing and configure the usbPort your OS generated for the display.
|
||||
|
||||
```
|
||||
UID: proteusecometer:EcoMeterS:e90705eaa4
|
||||
label: Proteus EcoMeter S
|
||||
thingTypeUID: proteusecometer:EcoMeterS
|
||||
configuration:
|
||||
usbPort: /dev/ttyUSB0
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
| channel | type | description |
|
||||
|-----------------------|----------------------|------------------------------------------------------|
|
||||
| temperature | Number:Temperature | Temperature measured by the sensor |
|
||||
| sensorLevel | Number:Length | Distance between sensor and water surface |
|
||||
| usableLevel | Number:Volume | How much liquid is usable |
|
||||
| usableLevelInPercent | Number:Dimensionless | How much liquid is usable relative to total capacity |
|
||||
| totalCapacity | Number:Volume | Total capacity of measured cistern/tank |
|
||||
|
||||
## Full Example
|
||||
|
||||
Thing proteusecometer:EcoMeterS:e90705eaa4 "Proteus EcoMeter S" [ usbPort="/dev/ttyUSB0" ]
|
||||
|
||||
Number:Temperature Temperature "Measured temperature [%.1f °C]" { channel="proteusecometer:EcoMeterS:e90705eaa4:temperature" }
|
||||
Number:Length SensorLevelCm "Sensor Level" { channel="proteusecometer:EcoMeterS:e90705eaa4:sensorLevel" }
|
||||
Number:Volume UsableLevel "Usable Level" { channel="proteusecometer:EcoMeterS:e90705eaa4:usableLevel" }
|
||||
Number:Dimensionless UsableLevelinpercent "Usable Level" { channel="proteusecometer:EcoMeterS:e90705eaa4:usableLevelInPercent" }
|
||||
Number:Volume TotalCapacityinliter "Total Capacity" { channel="proteusecometer:EcoMeterS:e90705eaa4:totalCapacity" }
|
28
bundles/org.openhab.binding.proteusecometer/pom.xml
Normal file
28
bundles/org.openhab.binding.proteusecometer/pom.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?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.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.proteusecometer</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: ProteusEcoMeter Binding</name>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fazecast</groupId>
|
||||
<artifactId>jSerialComm</artifactId>
|
||||
<version>2.7.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.proteusecometer-${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-proteusecometer" description="ProteusEcoMeter Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.proteusecometer/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -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.proteusecometer.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ProteusEcoMeterBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Matthias Herrmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ProteusEcoMeterBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "proteusecometer";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_ECO_METER_S = new ThingTypeUID(BINDING_ID, "EcoMeterS");
|
||||
|
||||
public static final String TEMPERATURE = "temperature";
|
||||
|
||||
public static final String SENSOR_LEVEL = "sensorLevel";
|
||||
|
||||
public static final String USABLE_LEVEL = "usableLevel";
|
||||
|
||||
public static final String USABLE_LEVEL_IN_PERCENT = "usableLevelInPercent";
|
||||
|
||||
public static final String TOTAL_CAPACITY = "totalCapacity";
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.proteusecometer.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ProteusEcoMeterConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Matthias Herrmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ProteusEcoMeterConfiguration {
|
||||
public String usbPort = "";
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.proteusecometer.internal;
|
||||
|
||||
import static org.openhab.binding.proteusecometer.internal.ProteusEcoMeterBindingConstants.THING_TYPE_ECO_METER_S;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.proteusecometer.internal.ecometers.handler.ProteusEcoMeterSHandler;
|
||||
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.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ProteusEcoMeterHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Matthias Herrmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.proteusecometer", service = ThingHandlerFactory.class)
|
||||
public class ProteusEcoMeterHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ECO_METER_S);
|
||||
private final Logger logger = LoggerFactory.getLogger(ProteusEcoMeterHandlerFactory.class);
|
||||
|
||||
@Activate
|
||||
public ProteusEcoMeterHandlerFactory() {
|
||||
}
|
||||
|
||||
@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_ECO_METER_S.equals(thingTypeUID)) {
|
||||
logger.trace("Creating ProteusEcoMeterSHandler");
|
||||
return new ProteusEcoMeterSHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.proteusecometer.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Allows you to transform an {@link Exception} to {@link RuntimeException} to circumvent checked exception
|
||||
* issues.
|
||||
*
|
||||
* @author Matthias Herrmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WrappedException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public WrappedException(final Exception wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.proteusecometer.internal.ecometers;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Parse the bytes from the device
|
||||
*
|
||||
* @author Matthias Herrmann - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class ProteusEcoMeterSParser {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ProteusEcoMeterSParser.class);
|
||||
|
||||
/**
|
||||
* @param bytes Raw bytes send from the device
|
||||
* @return A structured version of the bytes, if possible
|
||||
*/
|
||||
public Optional<ProteusEcoMeterSReply> parseFromBytes(final byte[] bytes) {
|
||||
return Optional.ofNullable(bytes).flatMap(b -> {
|
||||
final String hexString = HexUtils.bytesToHex(b);
|
||||
logger.trace("Received hex string: {}", hexString);
|
||||
|
||||
if (hexString.length() < 4) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
final String marker = hexString.substring(0, 4);
|
||||
if (!"5349".equals(marker)) {
|
||||
logger.trace("Marker is not {} but {}", "5349", marker);
|
||||
return Optional.empty();
|
||||
} else if (hexString.length() < 40) {
|
||||
logger.trace("hexString is of length {}, expected >= 40", hexString.length());
|
||||
return Optional.empty();
|
||||
} else {
|
||||
try {
|
||||
return Optional
|
||||
.of(new ProteusEcoMeterSReply(parseInt(hexString.substring(26, 28), "tempInFahrenheit"),
|
||||
parseInt(hexString.substring(28, 32), "sensorLevelInCm"),
|
||||
parseInt(hexString.substring(32, 36), "usableLevelInLiter"),
|
||||
parseInt(hexString.substring(36, 40), "totalCapacityInLiter")));
|
||||
} catch (final NumberFormatException e) {
|
||||
logger.debug("Error while parsing numbers", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Integer parseInt(final String toParse, final String fieldName) throws NumberFormatException {
|
||||
try {
|
||||
return Integer.parseInt(toParse, 16);
|
||||
} catch (final NumberFormatException e) {
|
||||
logger.trace("Unable to parse field {}", fieldName, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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.proteusecometer.internal.ecometers;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The reply of Proteus EcoMeter S
|
||||
*
|
||||
* @author Matthias Herrmann - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ProteusEcoMeterSReply {
|
||||
public final double tempInFahrenheit;
|
||||
public final int sensorLevelInCm;
|
||||
public final int usableLevelInLiter;
|
||||
public final int totalCapacityInLiter;
|
||||
|
||||
public ProteusEcoMeterSReply(final double tempInFahrenheit, final int sensorLevelInCm, final int usableLevelInLiter,
|
||||
final int totalCapacityInLiter) {
|
||||
this.tempInFahrenheit = tempInFahrenheit;
|
||||
this.sensorLevelInCm = sensorLevelInCm;
|
||||
this.usableLevelInLiter = usableLevelInLiter;
|
||||
this.totalCapacityInLiter = totalCapacityInLiter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProteusEcoMeterSReply [sensorLevelInCm=" + sensorLevelInCm + ", tempInFahrenheit=" + tempInFahrenheit
|
||||
+ ", totalCapacityInLiter=" + totalCapacityInLiter + ", usableLevelInLiter=" + usableLevelInLiter + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.proteusecometer.internal.ecometers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.proteusecometer.internal.WrappedException;
|
||||
import org.openhab.binding.proteusecometer.internal.serialport.SerialPortService;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Read from Proteus EcoMeter S
|
||||
*
|
||||
* @author Matthias Herrmann - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ProteusEcoMeterSService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ProteusEcoMeterSService.class);
|
||||
|
||||
/**
|
||||
* Initialize the communication with the device, i.e. open the serial port etc.
|
||||
*
|
||||
* @return {@code true} if we can communicate with the device
|
||||
* @throws IOException
|
||||
*/
|
||||
public Stream<ProteusEcoMeterSReply> read(final String portId, final SerialPortService serialPort)
|
||||
throws IOException {
|
||||
logger.trace("communicate");
|
||||
|
||||
final InputStream inputStream = serialPort.getInputStream(portId, 115200, 8, 1, 0);
|
||||
final Supplier<Optional<ProteusEcoMeterSReply>> supplier = () -> {
|
||||
logger.trace("Input stream opened for the port");
|
||||
|
||||
try {
|
||||
final byte[] deviceBytes = new byte[22];
|
||||
inputStream.read(deviceBytes, 0, 22);
|
||||
final String hexString = HexUtils.bytesToHex(deviceBytes);
|
||||
logger.trace("Received hex string: {}", hexString);
|
||||
final ProteusEcoMeterSParser parser = new ProteusEcoMeterSParser();
|
||||
final Optional<ProteusEcoMeterSReply> dataOpt = parser.parseFromBytes(deviceBytes);
|
||||
|
||||
if (dataOpt.isEmpty()) {
|
||||
logger.warn("Received bytes I don't understand: {}", hexString);
|
||||
}
|
||||
return dataOpt;
|
||||
} catch (final IOException e) {
|
||||
throw new WrappedException(e);
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (final IOException e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Stream.generate(supplier).takeWhile(reply -> !Thread.interrupted()).filter(Optional::isPresent)
|
||||
.map(Optional::get);
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* 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.proteusecometer.internal.ecometers.handler;
|
||||
|
||||
import static org.openhab.binding.proteusecometer.internal.ProteusEcoMeterBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.proteusecometer.internal.ProteusEcoMeterConfiguration;
|
||||
import org.openhab.binding.proteusecometer.internal.WrappedException;
|
||||
import org.openhab.binding.proteusecometer.internal.ecometers.ProteusEcoMeterSReply;
|
||||
import org.openhab.binding.proteusecometer.internal.ecometers.ProteusEcoMeterSService;
|
||||
import org.openhab.binding.proteusecometer.internal.serialport.SerialPortService;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.fazecast.jSerialComm.SerialPort;
|
||||
|
||||
/**
|
||||
* The {@link ProteusEcoMeterSHandler} updates thing channels when receiving data
|
||||
*
|
||||
* @author Matthias Herrmann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ProteusEcoMeterSHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ProteusEcoMeterSHandler.class);
|
||||
private @Nullable SerialPort serialPort;
|
||||
private ProteusEcoMeterConfiguration config = new ProteusEcoMeterConfiguration();
|
||||
private @Nullable ScheduledFuture<?> job;
|
||||
private SerialPortService serialPortService = new SerialPortService() {
|
||||
@NonNullByDefault
|
||||
public InputStream getInputStream(String portId, int baudRate, int numDataBits, int numStopBits, int parity) {
|
||||
try {
|
||||
ProteusEcoMeterSHandler.this.serialPort = SerialPort.getCommPort(portId);
|
||||
final SerialPort localSerialPort = ProteusEcoMeterSHandler.this.serialPort;
|
||||
if (localSerialPort == null) {
|
||||
throw new IOException("SerialPort.getCommPort(" + portId + ") returned null");
|
||||
}
|
||||
localSerialPort.closePort();
|
||||
|
||||
localSerialPort.setBaudRate(baudRate);
|
||||
localSerialPort.setNumDataBits(numDataBits);
|
||||
localSerialPort.setNumStopBits(numStopBits);
|
||||
localSerialPort.setParity(parity);
|
||||
localSerialPort.openPort();
|
||||
localSerialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0);
|
||||
final InputStream inputStream = localSerialPort.getInputStream();
|
||||
if (inputStream == null) {
|
||||
throw new IOException("serialPort.getInputStream() returned null");
|
||||
}
|
||||
return inputStream;
|
||||
} catch (final Exception e) {
|
||||
closeSerialPort();
|
||||
throw new WrappedException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ProteusEcoMeterSHandler(final Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(ProteusEcoMeterConfiguration.class);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
job = scheduler.schedule(() -> handleDeviceReplies(), 0, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// at the moment there are no commands supported. The Eco Meter S would support configuration
|
||||
// commands, but this is not implemented yet
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
closeSerialPort();
|
||||
final ScheduledFuture<?> localJob = job;
|
||||
if (localJob != null) {
|
||||
localJob.cancel(true);
|
||||
job = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeviceReplies() {
|
||||
final Duration retryInitDelay = Duration.ofSeconds(10);
|
||||
try {
|
||||
final ProteusEcoMeterSService ecoMeterSService = new ProteusEcoMeterSService();
|
||||
final Stream<ProteusEcoMeterSReply> replyStream = ecoMeterSService.read(config.usbPort, serialPortService);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
replyStream.forEach(reply -> {
|
||||
updateState(SENSOR_LEVEL, new QuantityType<>(reply.sensorLevelInCm, MetricPrefix.CENTI(SIUnits.METRE)));
|
||||
updateState(USABLE_LEVEL, new QuantityType<>(reply.usableLevelInLiter, Units.LITRE));
|
||||
updateState(USABLE_LEVEL_IN_PERCENT, new QuantityType<>(
|
||||
100d / reply.totalCapacityInLiter * reply.usableLevelInLiter, Units.PERCENT));
|
||||
updateState(TEMPERATURE, new QuantityType<>(reply.tempInFahrenheit, ImperialUnits.FAHRENHEIT));
|
||||
updateState(TOTAL_CAPACITY, new QuantityType<>(reply.totalCapacityInLiter, Units.LITRE));
|
||||
});
|
||||
logger.debug("The reply stream ended unexpectedly. Retrying in {}", retryInitDelay);
|
||||
} catch (final Exception e) {
|
||||
logger.debug("Error communicating with eco meter s. Retrying in {}", retryInitDelay, e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Error reading from Port: " + e.getMessage());
|
||||
} finally {
|
||||
closeSerialPort();
|
||||
job = scheduler.schedule(this::handleDeviceReplies, retryInitDelay.getSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSerialPort() {
|
||||
if (serialPort != null) {
|
||||
final boolean closed = serialPort.closePort();
|
||||
logger.debug("serialPort.closePort() returned {}", closed);
|
||||
serialPort = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.proteusecometer.internal.serialport;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Abstract over serial port implementations
|
||||
*
|
||||
* @author Matthias Herrmann - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SerialPortService {
|
||||
public InputStream getInputStream(String portId, int baudRate, int numDataBits, int numStopBits, int parity);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="proteusecometer" 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>Proteus EcoMeter</name>
|
||||
<description>Puts your EcoMeter data into openHAB</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,24 @@
|
||||
# binding
|
||||
binding.proteusecometer.name = Proteus EcoMeter
|
||||
binding.proteusecometer.description = Puts your EcoMeter data into openHAB
|
||||
# thing types
|
||||
thing-type.proteusecometer.EcoMeterS.label = Proteus EcoMeter S
|
||||
thing-type.proteusecometer.EcoMeterS.description = Sensor for measuring water level of a cistern. Connected via USB
|
||||
|
||||
thing-type.config.proteusecometer.EcoMeterS.usbPort.label = USB Port
|
||||
thing-type.config.proteusecometer.EcoMeterS.usbPort.description = USB port the device is connected to i.e. /dev/ttyUSB0
|
||||
# channel types
|
||||
channel-type.proteusecometer.Temperature.label = Temperature
|
||||
channel-type.proteusecometer.Temperature.description = Temperature measured by the sensor
|
||||
|
||||
channel-type.proteusecometer.SensorLevel.label = Sensor Level
|
||||
channel-type.proteusecometer.SensorLevel.description = The distance between the sensor and the water surface
|
||||
|
||||
channel-type.proteusecometer.UsableLevel.label = Usable Level in litre
|
||||
channel-type.proteusecometer.UsableLevel.description = The usable level in litre
|
||||
|
||||
channel-type.proteusecometer.UsableLevelInPercent.label = Usable Level in percent
|
||||
channel-type.proteusecometer.UsableLevelInPercent.description = The usable level in percent
|
||||
|
||||
channel-type.proteusecometer.TotalCapacity.label = Total Capacity
|
||||
channel-type.proteusecometer.TotalCapacity.description = The total capacity of your cistern/tank
|
@ -0,0 +1,24 @@
|
||||
# binding
|
||||
binding.proteusecometer.name = Proteus EcoMeter
|
||||
binding.proteusecometer.description = EcoMeter Sensordaten in openHAB
|
||||
# thing types
|
||||
thing-type.proteusecometer.EcoMeterS.label = Proteus EcoMeter S
|
||||
thing-type.proteusecometer.EcoMeterS.description = Füllstandsanzeige für Zisterne, Wassertanks, Erdtanks
|
||||
|
||||
thing-type.config.proteusecometer.EcoMeterS.usbPort.label = USB Port
|
||||
thing-type.config.proteusecometer.EcoMeterS.usbPort.description = USB Port des Geräts, z.B. /dev/ttyUSB0
|
||||
# channel types
|
||||
channel-type.proteusecometer.Temperature.label = Temperatur
|
||||
channel-type.proteusecometer.Temperature.description = Umgebungstemperatur des Sensors
|
||||
|
||||
channel-type.proteusecometer.SensorLevel.label = Sensorhöhe
|
||||
channel-type.proteusecometer.SensorLevel.description = Sensorhöhe über Flüssigkeitsoberfläche
|
||||
|
||||
channel-type.proteusecometer.UsableLevel.label = Füllmenge in Liter
|
||||
channel-type.proteusecometer.UsableLevel.description = Füllmenge in Liter
|
||||
|
||||
channel-type.proteusecometer.UsableLevelInPercent.label = Füllmenge in Prozent
|
||||
channel-type.proteusecometer.UsableLevelInPercent.description = Füllmenge in Prozent
|
||||
|
||||
channel-type.proteusecometer.TotalCapacity.label = Gesamtkapazität
|
||||
channel-type.proteusecometer.TotalCapacity.description = Gesamtkapazität des Messobjekts
|
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="proteusecometer"
|
||||
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">
|
||||
|
||||
<thing-type id="EcoMeterS">
|
||||
<label>Proteus EcoMeter S</label>
|
||||
<description>Sensor for measuring water level of a cistern. Connected via USB</description>
|
||||
|
||||
<channels>
|
||||
<channel id="temperature" typeId="Temperature"/>
|
||||
<channel id="sensorLevel" typeId="SensorLevel"/>
|
||||
<channel id="usableLevel" typeId="UsableLevel"/>
|
||||
<channel id="usableLevelInPercent" typeId="UsableLevelInPercent"/>
|
||||
<channel id="totalCapacity" typeId="TotalCapacity"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="usbPort" type="text" required="true">
|
||||
<context>serial-port</context>
|
||||
<label>USB Port</label>
|
||||
<description>USB port the device is connected to i.e. /dev/ttyUSB0</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="Temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Temperature measured by the sensor</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="SensorLevel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Sensor Level</label>
|
||||
<description>The distance between the sensor and the water surface</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="UsableLevel">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Usable Level in litre</label>
|
||||
<description>The usable level in litre</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="UsableLevelInPercent">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Usable Level in percent</label>
|
||||
<description>The usable level in percent</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="TotalCapacity">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Total Capacity</label>
|
||||
<description>The total capacity of your cistern/tank</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -272,6 +272,7 @@
|
||||
<module>org.openhab.binding.plugwise</module>
|
||||
<module>org.openhab.binding.plugwiseha</module>
|
||||
<module>org.openhab.binding.powermax</module>
|
||||
<module>org.openhab.binding.proteusecometer</module>
|
||||
<module>org.openhab.binding.pulseaudio</module>
|
||||
<module>org.openhab.binding.pushbullet</module>
|
||||
<module>org.openhab.binding.pushover</module>
|
||||
|
Loading…
Reference in New Issue
Block a user