2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.proteusecometer.internal.ecometers.handler;
15 import static org.openhab.binding.proteusecometer.internal.ProteusEcoMeterBindingConstants.*;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.time.Duration;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.stream.Stream;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.proteusecometer.internal.ProteusEcoMeterConfiguration;
27 import org.openhab.binding.proteusecometer.internal.WrappedException;
28 import org.openhab.binding.proteusecometer.internal.ecometers.ProteusEcoMeterSReply;
29 import org.openhab.binding.proteusecometer.internal.ecometers.ProteusEcoMeterSService;
30 import org.openhab.binding.proteusecometer.internal.serialport.SerialPortService;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.unit.ImperialUnits;
33 import org.openhab.core.library.unit.MetricPrefix;
34 import org.openhab.core.library.unit.SIUnits;
35 import org.openhab.core.library.unit.Units;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.types.Command;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
45 import com.fazecast.jSerialComm.SerialPort;
48 * The {@link ProteusEcoMeterSHandler} updates thing channels when receiving data
50 * @author Matthias Herrmann - Initial contribution
53 public class ProteusEcoMeterSHandler extends BaseThingHandler {
55 private final Logger logger = LoggerFactory.getLogger(ProteusEcoMeterSHandler.class);
56 private @Nullable SerialPort serialPort;
57 private ProteusEcoMeterConfiguration config = new ProteusEcoMeterConfiguration();
58 private @Nullable ScheduledFuture<?> job;
59 private SerialPortService serialPortService = new SerialPortService() {
61 public InputStream getInputStream(String portId, int baudRate, int numDataBits, int numStopBits, int parity) {
63 ProteusEcoMeterSHandler.this.serialPort = SerialPort.getCommPort(portId);
64 final SerialPort localSerialPort = ProteusEcoMeterSHandler.this.serialPort;
65 if (localSerialPort == null) {
66 throw new IOException("SerialPort.getCommPort(" + portId + ") returned null");
68 localSerialPort.closePort();
70 localSerialPort.setBaudRate(baudRate);
71 localSerialPort.setNumDataBits(numDataBits);
72 localSerialPort.setNumStopBits(numStopBits);
73 localSerialPort.setParity(parity);
74 localSerialPort.openPort();
75 localSerialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0);
76 final InputStream inputStream = localSerialPort.getInputStream();
77 if (inputStream == null) {
78 throw new IOException("serialPort.getInputStream() returned null");
81 } catch (final Exception e) {
83 throw new WrappedException(e);
88 public ProteusEcoMeterSHandler(final Thing thing) {
93 public void initialize() {
94 config = getConfigAs(ProteusEcoMeterConfiguration.class);
95 updateStatus(ThingStatus.UNKNOWN);
96 job = scheduler.schedule(() -> handleDeviceReplies(), 0, TimeUnit.SECONDS);
100 public void handleCommand(ChannelUID channelUID, Command command) {
101 // at the moment there are no commands supported. The Eco Meter S would support configuration
102 // commands, but this is not implemented yet
106 public void dispose() {
109 final ScheduledFuture<?> localJob = job;
110 if (localJob != null) {
111 localJob.cancel(true);
116 private void handleDeviceReplies() {
117 final Duration retryInitDelay = Duration.ofSeconds(10);
119 final ProteusEcoMeterSService ecoMeterSService = new ProteusEcoMeterSService();
120 final Stream<ProteusEcoMeterSReply> replyStream = ecoMeterSService.read(config.usbPort, serialPortService);
121 updateStatus(ThingStatus.ONLINE);
123 replyStream.forEach(reply -> {
124 updateState(SENSOR_LEVEL, new QuantityType<>(reply.sensorLevelInCm, MetricPrefix.CENTI(SIUnits.METRE)));
125 updateState(USABLE_LEVEL, new QuantityType<>(reply.usableLevelInLiter, Units.LITRE));
126 updateState(USABLE_LEVEL_IN_PERCENT, new QuantityType<>(
127 100d / reply.totalCapacityInLiter * reply.usableLevelInLiter, Units.PERCENT));
128 updateState(TEMPERATURE, new QuantityType<>(reply.tempInFahrenheit, ImperialUnits.FAHRENHEIT));
129 updateState(TOTAL_CAPACITY, new QuantityType<>(reply.totalCapacityInLiter, Units.LITRE));
131 logger.debug("The reply stream ended unexpectedly. Retrying in {}", retryInitDelay);
132 } catch (final Exception e) {
133 logger.debug("Error communicating with eco meter s. Retrying in {}", retryInitDelay, e);
134 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
135 "Error reading from Port: " + e.getMessage());
138 job = scheduler.schedule(this::handleDeviceReplies, retryInitDelay.getSeconds(), TimeUnit.SECONDS);
142 private void closeSerialPort() {
143 if (serialPort != null) {
144 final boolean closed = serialPort.closePort();
145 logger.debug("serialPort.closePort() returned {}", closed);