]> git.basschouten.com Git - openhab-addons.git/blob
b0cdae64983e94d4df6ab410452e99366aaaae8e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.network.internal.handler;
14
15 import static org.openhab.binding.network.internal.NetworkBindingConstants.*;
16 import static org.openhab.core.library.unit.Units.*;
17
18 import java.math.BigDecimal;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.network.internal.SpeedTestConfiguration;
25 import org.openhab.core.library.dimension.DataTransferRate;
26 import org.openhab.core.library.types.DateTimeType;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.library.types.QuantityType;
29 import org.openhab.core.library.unit.Units;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.binding.BaseThingHandler;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.State;
37 import org.openhab.core.types.UnDefType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import fr.bmartel.speedtest.SpeedTestReport;
42 import fr.bmartel.speedtest.SpeedTestSocket;
43 import fr.bmartel.speedtest.inter.ISpeedTestListener;
44 import fr.bmartel.speedtest.model.SpeedTestError;
45
46 /**
47  * The {@link SpeedTestHandler } is responsible for launching bandwidth
48  * measurements at a given interval and for given file / size
49  *
50  * @author GaĆ«l L'hopital - Initial contribution
51  */
52 @NonNullByDefault
53 public class SpeedTestHandler extends BaseThingHandler implements ISpeedTestListener {
54     private final Logger logger = LoggerFactory.getLogger(SpeedTestHandler.class);
55     private @Nullable SpeedTestSocket speedTestSocket;
56     private @NonNullByDefault({}) ScheduledFuture<?> refreshTask;
57     private @NonNullByDefault({}) SpeedTestConfiguration configuration;
58     private State bufferedProgress = UnDefType.UNDEF;
59     private int timeouts;
60
61     public SpeedTestHandler(Thing thing) {
62         super(thing);
63     }
64
65     @Override
66     public void initialize() {
67         configuration = getConfigAs(SpeedTestConfiguration.class);
68         startRefreshTask();
69     }
70
71     private synchronized void startSpeedTest() {
72         if (speedTestSocket == null) {
73             logger.debug("Network speedtest started");
74             final SpeedTestSocket socket = new SpeedTestSocket(1500);
75             speedTestSocket = socket;
76             socket.addSpeedTestListener(this);
77             updateState(CHANNEL_TEST_ISRUNNING, OnOffType.ON);
78             updateState(CHANNEL_TEST_START, new DateTimeType());
79             updateState(CHANNEL_TEST_END, UnDefType.NULL);
80             updateProgress(new QuantityType<>(0, Units.PERCENT));
81             socket.startDownload(configuration.getDownloadURL());
82         } else {
83             logger.info("A speedtest is already in progress, will retry on next refresh");
84         }
85     }
86
87     private synchronized void stopSpeedTest() {
88         updateState(CHANNEL_TEST_ISRUNNING, OnOffType.OFF);
89         updateProgress(UnDefType.NULL);
90         updateState(CHANNEL_TEST_END, new DateTimeType());
91         if (speedTestSocket != null) {
92             SpeedTestSocket socket = speedTestSocket;
93             socket.closeSocket();
94             socket.removeSpeedTestListener(this);
95             socket = null;
96             speedTestSocket = null;
97             logger.debug("Network speedtest finished");
98         }
99     }
100
101     @Override
102     public void onCompletion(final @Nullable SpeedTestReport testReport) {
103         timeouts = configuration.maxTimeout;
104         if (testReport != null) {
105             BigDecimal rate = testReport.getTransferRateBit();
106             QuantityType<DataTransferRate> quantity = new QuantityType<>(rate, BIT_PER_SECOND)
107                     .toUnit(MEGABIT_PER_SECOND);
108             if (quantity != null) {
109                 switch (testReport.getSpeedTestMode()) {
110                     case DOWNLOAD:
111                         updateState(CHANNEL_RATE_DOWN, quantity);
112                         if (speedTestSocket != null && configuration != null) {
113                             speedTestSocket.startUpload(configuration.getUploadURL(), configuration.uploadSize);
114                         }
115                         break;
116                     case UPLOAD:
117                         updateState(CHANNEL_RATE_UP, quantity);
118                         stopSpeedTest();
119                         break;
120                     default:
121                         break;
122                 }
123             }
124         }
125     }
126
127     @Override
128     public void onError(final @Nullable SpeedTestError testError, final @Nullable String errorMessage) {
129         if (SpeedTestError.UNSUPPORTED_PROTOCOL.equals(testError) || SpeedTestError.MALFORMED_URI.equals(testError)) {
130             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMessage);
131             freeRefreshTask();
132             return;
133         } else if (SpeedTestError.SOCKET_TIMEOUT.equals(testError)) {
134             timeouts--;
135             if (timeouts <= 0) {
136                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Max timeout count reached");
137                 freeRefreshTask();
138             } else {
139                 logger.warn("Speedtest timed out, {} attempts left. Message '{}'", timeouts, errorMessage);
140                 stopSpeedTest();
141             }
142             return;
143         } else if (SpeedTestError.SOCKET_ERROR.equals(testError)
144                 || SpeedTestError.INVALID_HTTP_RESPONSE.equals(testError)) {
145             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
146             freeRefreshTask();
147             return;
148         } else {
149             stopSpeedTest();
150             logger.warn("Speedtest failed: {}", errorMessage);
151         }
152     }
153
154     @Override
155     public void onProgress(float percent, @Nullable SpeedTestReport testReport) {
156         updateProgress(new QuantityType<>(Math.round(percent), Units.PERCENT));
157     }
158
159     private void updateProgress(State state) {
160         if (!state.toString().equals(bufferedProgress.toString())) {
161             bufferedProgress = state;
162             updateState(CHANNEL_TEST_PROGRESS, bufferedProgress);
163         }
164     }
165
166     @Override
167     public void handleCommand(ChannelUID channelUID, Command command) {
168         if (command == OnOffType.ON
169                 && ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR == getThing().getStatusInfo().getStatusDetail()) {
170             logger.debug("Speedtest was offline, restarting it upon command to do so");
171             startRefreshTask();
172         } else {
173             if (CHANNEL_TEST_ISRUNNING.equals(channelUID.getId())) {
174                 if (command == OnOffType.ON) {
175                     startSpeedTest();
176                 } else if (command == OnOffType.OFF) {
177                     stopSpeedTest();
178                 }
179             } else {
180                 logger.debug("Command {} is not supported for channel: {}.", command, channelUID.getId());
181             }
182         }
183     }
184
185     @Override
186     public void dispose() {
187         freeRefreshTask();
188     }
189
190     private void freeRefreshTask() {
191         stopSpeedTest();
192         if (refreshTask != null) {
193             refreshTask.cancel(true);
194             refreshTask = null;
195         }
196     }
197
198     private void startRefreshTask() {
199         logger.info("Speedtests starts in {} minutes, then refreshes every {} minutes", configuration.initialDelay,
200                 configuration.refreshInterval);
201         refreshTask = scheduler.scheduleWithFixedDelay(this::startSpeedTest, configuration.initialDelay,
202                 configuration.refreshInterval, TimeUnit.MINUTES);
203         timeouts = configuration.maxTimeout;
204         updateStatus(ThingStatus.ONLINE);
205     }
206 }