]> git.basschouten.com Git - openhab-addons.git/blob
af1b600e89b97cf4f457cc7cea1bd28ae1570eb9
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.proteusecometer.internal.ecometers.handler;
14
15 import static org.openhab.binding.proteusecometer.internal.ProteusEcoMeterBindingConstants.*;
16
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;
23
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;
44
45 import com.fazecast.jSerialComm.SerialPort;
46
47 /**
48  * The {@link ProteusEcoMeterSHandler} updates thing channels when receiving data
49  *
50  * @author Matthias Herrmann - Initial contribution
51  */
52 @NonNullByDefault
53 public class ProteusEcoMeterSHandler extends BaseThingHandler {
54
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() {
60         @NonNullByDefault
61         public InputStream getInputStream(String portId, int baudRate, int numDataBits, int numStopBits, int parity) {
62             try {
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");
67                 }
68                 localSerialPort.closePort();
69
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");
79                 }
80                 return inputStream;
81             } catch (final Exception e) {
82                 closeSerialPort();
83                 throw new WrappedException(e);
84             }
85         }
86     };
87
88     public ProteusEcoMeterSHandler(final Thing thing) {
89         super(thing);
90     }
91
92     @Override
93     public void initialize() {
94         config = getConfigAs(ProteusEcoMeterConfiguration.class);
95         updateStatus(ThingStatus.UNKNOWN);
96         job = scheduler.schedule(() -> handleDeviceReplies(), 0, TimeUnit.SECONDS);
97     }
98
99     @Override
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
103     }
104
105     @Override
106     public void dispose() {
107         super.dispose();
108         closeSerialPort();
109         final ScheduledFuture<?> localJob = job;
110         if (localJob != null) {
111             localJob.cancel(true);
112             job = null;
113         }
114     }
115
116     private void handleDeviceReplies() {
117         final Duration retryInitDelay = Duration.ofSeconds(10);
118         try {
119             final ProteusEcoMeterSService ecoMeterSService = new ProteusEcoMeterSService();
120             final Stream<ProteusEcoMeterSReply> replyStream = ecoMeterSService.read(config.usbPort, serialPortService);
121             updateStatus(ThingStatus.ONLINE);
122
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));
130             });
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());
136         } finally {
137             closeSerialPort();
138             job = scheduler.schedule(this::handleDeviceReplies, retryInitDelay.getSeconds(), TimeUnit.SECONDS);
139         }
140     }
141
142     private void closeSerialPort() {
143         if (serialPort != null) {
144             final boolean closed = serialPort.closePort();
145             logger.debug("serialPort.closePort() returned {}", closed);
146             serialPort = null;
147         }
148     }
149 }