]> git.basschouten.com Git - openhab-addons.git/blob
28122005e3be2fca55647f9d347582f006706d33
[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.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         String url = configuration.getDownloadURL();
73         if (speedTestSocket == null && url != null) {
74             logger.debug("Network speedtest started");
75             final SpeedTestSocket socket = new SpeedTestSocket(1500);
76             speedTestSocket = socket;
77             socket.addSpeedTestListener(this);
78             updateState(CHANNEL_TEST_ISRUNNING, OnOffType.ON);
79             updateState(CHANNEL_TEST_START, new DateTimeType());
80             updateState(CHANNEL_TEST_END, UnDefType.NULL);
81             updateProgress(new QuantityType<>(0, Units.PERCENT));
82             socket.startDownload(url);
83         } else {
84             logger.info("A speedtest is already in progress, will retry on next refresh");
85         }
86     }
87
88     private synchronized void stopSpeedTest() {
89         updateState(CHANNEL_TEST_ISRUNNING, OnOffType.OFF);
90         updateProgress(UnDefType.NULL);
91         updateState(CHANNEL_TEST_END, new DateTimeType());
92         if (speedTestSocket != null) {
93             SpeedTestSocket socket = speedTestSocket;
94             socket.closeSocket();
95             socket.removeSpeedTestListener(this);
96             socket = null;
97             speedTestSocket = null;
98             logger.debug("Network speedtest finished");
99         }
100     }
101
102     @Override
103     public void onCompletion(final @Nullable SpeedTestReport testReport) {
104         timeouts = configuration.maxTimeout;
105         if (testReport != null) {
106             BigDecimal rate = testReport.getTransferRateBit();
107             QuantityType<DataTransferRate> quantity = new QuantityType<>(rate, BIT_PER_SECOND)
108                     .toUnit(MEGABIT_PER_SECOND);
109             if (quantity != null) {
110                 switch (testReport.getSpeedTestMode()) {
111                     case DOWNLOAD:
112                         updateState(CHANNEL_RATE_DOWN, quantity);
113                         String url = configuration.getUploadURL();
114                         if (speedTestSocket != null && url != null) {
115                             speedTestSocket.startUpload(configuration.getUploadURL(), configuration.uploadSize);
116                         }
117                         break;
118                     case UPLOAD:
119                         updateState(CHANNEL_RATE_UP, quantity);
120                         stopSpeedTest();
121                         break;
122                     default:
123                         break;
124                 }
125             }
126         }
127     }
128
129     @Override
130     public void onError(final @Nullable SpeedTestError testError, final @Nullable String errorMessage) {
131         if (SpeedTestError.UNSUPPORTED_PROTOCOL.equals(testError) || SpeedTestError.MALFORMED_URI.equals(testError)) {
132             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMessage);
133             freeRefreshTask();
134             return;
135         } else if (SpeedTestError.SOCKET_TIMEOUT.equals(testError)) {
136             timeouts--;
137             if (timeouts <= 0) {
138                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Max timeout count reached");
139                 freeRefreshTask();
140             } else {
141                 logger.warn("Speedtest timed out, {} attempts left. Message '{}'", timeouts, errorMessage);
142                 stopSpeedTest();
143             }
144             return;
145         } else if (SpeedTestError.SOCKET_ERROR.equals(testError)
146                 || SpeedTestError.INVALID_HTTP_RESPONSE.equals(testError)) {
147             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
148             freeRefreshTask();
149             return;
150         } else {
151             stopSpeedTest();
152             logger.warn("Speedtest failed: {}", errorMessage);
153         }
154     }
155
156     @Override
157     public void onProgress(float percent, @Nullable SpeedTestReport testReport) {
158         updateProgress(new QuantityType<>(Math.round(percent), Units.PERCENT));
159     }
160
161     private void updateProgress(State state) {
162         if (!state.toString().equals(bufferedProgress.toString())) {
163             bufferedProgress = state;
164             updateState(CHANNEL_TEST_PROGRESS, bufferedProgress);
165         }
166     }
167
168     @Override
169     public void handleCommand(ChannelUID channelUID, Command command) {
170         if (command == OnOffType.ON
171                 && ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR == getThing().getStatusInfo().getStatusDetail()) {
172             logger.debug("Speedtest was offline, restarting it upon command to do so");
173             startRefreshTask();
174         } else {
175             if (CHANNEL_TEST_ISRUNNING.equals(channelUID.getId())) {
176                 if (command == OnOffType.ON) {
177                     startSpeedTest();
178                 } else if (command == OnOffType.OFF) {
179                     stopSpeedTest();
180                 }
181             } else {
182                 logger.debug("Command {} is not supported for channel: {}.", command, channelUID.getId());
183             }
184         }
185     }
186
187     @Override
188     public void dispose() {
189         freeRefreshTask();
190     }
191
192     private void freeRefreshTask() {
193         stopSpeedTest();
194         if (refreshTask != null) {
195             refreshTask.cancel(true);
196             refreshTask = null;
197         }
198     }
199
200     private void startRefreshTask() {
201         logger.info("Speedtests starts in {} minutes, then refreshes every {} minutes", configuration.initialDelay,
202                 configuration.refreshInterval);
203         refreshTask = scheduler.scheduleWithFixedDelay(this::startSpeedTest, configuration.initialDelay,
204                 configuration.refreshInterval, TimeUnit.MINUTES);
205         timeouts = configuration.maxTimeout;
206         updateStatus(ThingStatus.ONLINE);
207     }
208 }