]> git.basschouten.com Git - openhab-addons.git/blob
d83fb8fb1649de0da28da2b24a26f65b4ae4ae33
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.mikrotik.internal.handler;
14
15 import static org.openhab.core.thing.ThingStatus.OFFLINE;
16 import static org.openhab.core.thing.ThingStatus.ONLINE;
17 import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
18 import static org.openhab.core.types.RefreshType.REFRESH;
19
20 import java.lang.reflect.ParameterizedType;
21 import java.time.Duration;
22 import java.util.HashMap;
23 import java.util.Map;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.mikrotik.internal.config.ConfigValidation;
30 import org.openhab.binding.mikrotik.internal.config.RouterosThingConfig;
31 import org.openhab.binding.mikrotik.internal.model.RouterosDevice;
32 import org.openhab.core.cache.ExpiringCache;
33 import org.openhab.core.thing.Bridge;
34 import org.openhab.core.thing.Channel;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.ThingStatusInfo;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.State;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * The {@link MikrotikBaseThingHandler} is a base class for all other RouterOS things of map-value nature.
49  * It is responsible for handling commands, which are sent to one of the channels and emit channel updates
50  * whenever required.
51  *
52  * @author Oleg Vivtash - Initial contribution
53  *
54  *
55  * @param <C> config - the config class used by this base thing handler
56  *
57  */
58 @NonNullByDefault
59 public abstract class MikrotikBaseThingHandler<C extends ConfigValidation> extends BaseThingHandler {
60     private final Logger logger = LoggerFactory.getLogger(MikrotikBaseThingHandler.class);
61     protected @Nullable C config;
62     private @Nullable ScheduledFuture<?> refreshJob;
63     protected ExpiringCache<Boolean> refreshCache = new ExpiringCache<>(Duration.ofDays(1), () -> false);
64     protected Map<String, State> currentState = new HashMap<>();
65
66     // public static boolean supportsThingType(ThingTypeUID thingTypeUID) <- in subclasses
67
68     public MikrotikBaseThingHandler(Thing thing) {
69         super(thing);
70     }
71
72     protected @Nullable MikrotikRouterosBridgeHandler getVerifiedBridgeHandler() {
73         Bridge bridgeRef = getBridge();
74         if (bridgeRef != null && bridgeRef.getHandler() != null
75                 && (bridgeRef.getHandler() instanceof MikrotikRouterosBridgeHandler)) {
76             return (MikrotikRouterosBridgeHandler) bridgeRef.getHandler();
77         }
78         return null;
79     }
80
81     protected final @Nullable RouterosDevice getRouterOs() {
82         MikrotikRouterosBridgeHandler bridgeHandler = getVerifiedBridgeHandler();
83         return bridgeHandler == null ? null : bridgeHandler.getRouteros();
84     }
85
86     @Override
87     public void handleCommand(ChannelUID channelUID, Command command) {
88         logger.debug("Handling command = {} for channel = {}", command, channelUID);
89         if (getThing().getStatus() == ONLINE) {
90             RouterosDevice routeros = getRouterOs();
91             if (routeros != null) {
92                 if (command == REFRESH) {
93                     refreshCache.getValue();
94                     refreshChannel(channelUID);
95                 } else {
96                     try {
97                         executeCommand(channelUID, command);
98                     } catch (RuntimeException e) {
99                         logger.warn("Unexpected error handling command = {} for channel = {} : {}", command, channelUID,
100                                 e.getMessage());
101                     }
102                 }
103             }
104         }
105     }
106
107     @SuppressWarnings("unchecked")
108     @Override
109     public void initialize() {
110         cancelRefreshJob();
111         if (getVerifiedBridgeHandler() == null) {
112             updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "This thing requires a RouterOS bridge");
113             return;
114         }
115
116         var superKlass = (ParameterizedType) getClass().getGenericSuperclass();
117         if (superKlass == null) {
118             updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
119                     "getGenericSuperclass failed for thing handler");
120             return;
121         }
122         Class<?> klass = (Class<?>) (superKlass.getActualTypeArguments()[0]);
123
124         C localConfig = (C) getConfigAs(klass);
125         this.config = localConfig;
126
127         if (!localConfig.isValid()) {
128             updateStatus(OFFLINE, CONFIGURATION_ERROR, String.format("%s is invalid", klass.getSimpleName()));
129             return;
130         }
131
132         updateStatus(ONLINE);
133     }
134
135     @Override
136     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
137         if (status == ONLINE || (status == OFFLINE && statusDetail == ThingStatusDetail.COMMUNICATION_ERROR)) {
138             scheduleRefreshJob();
139         } else if (status == OFFLINE
140                 && (statusDetail == ThingStatusDetail.CONFIGURATION_ERROR || statusDetail == ThingStatusDetail.GONE)) {
141             cancelRefreshJob();
142         }
143
144         // update the status only if it's changed
145         ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(status, statusDetail).withDescription(description)
146                 .build();
147         if (!statusInfo.equals(getThing().getStatusInfo())) {
148             super.updateStatus(status, statusDetail, description);
149         }
150     }
151
152     @SuppressWarnings("null")
153     private void scheduleRefreshJob() {
154         synchronized (this) {
155             if (refreshJob == null) {
156                 var bridgeHandler = getVerifiedBridgeHandler();
157                 if (bridgeHandler == null) {
158                     updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Cannot obtain bridge handler");
159                     return;
160                 }
161                 RouterosThingConfig bridgeConfig = bridgeHandler.getBridgeConfig();
162                 int refreshPeriod = bridgeConfig.refresh;
163                 logger.debug("Scheduling refresh job every {}s", refreshPeriod);
164
165                 this.refreshCache = new ExpiringCache<>(Duration.ofSeconds(refreshPeriod), this::verifiedRefreshModels);
166                 refreshJob = scheduler.scheduleWithFixedDelay(this::scheduledRun, refreshPeriod, refreshPeriod,
167                         TimeUnit.SECONDS);
168             }
169         }
170     }
171
172     private void cancelRefreshJob() {
173         synchronized (this) {
174             var job = this.refreshJob;
175             if (job != null) {
176                 logger.debug("Cancelling refresh job");
177                 job.cancel(true);
178                 this.refreshJob = null;
179                 // Not setting to null as getValue() can potentially be called after
180                 this.refreshCache = new ExpiringCache<>(Duration.ofDays(1), () -> false);
181             }
182         }
183     }
184
185     private void scheduledRun() {
186         MikrotikRouterosBridgeHandler bridgeHandler = getVerifiedBridgeHandler();
187         if (bridgeHandler == null) {
188             updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Failed reaching out to RouterOS bridge");
189             return;
190         }
191         Bridge bridge = getBridge();
192         if (bridge != null && bridge.getStatus() == OFFLINE) {
193             updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "The RouterOS bridge is currently offline");
194             return;
195         }
196
197         if (getThing().getStatus() != ONLINE) {
198             updateStatus(ONLINE);
199         }
200         logger.debug("Refreshing all {} channels", getThing().getUID());
201         for (Channel channel : getThing().getChannels()) {
202             try {
203                 refreshChannel(channel.getUID());
204             } catch (RuntimeException e) {
205                 logger.warn("Unhandled exception while refreshing the {} Mikrotik thing", getThing().getUID(), e);
206                 updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
207             }
208         }
209     }
210
211     protected final void refresh() throws ChannelUpdateException {
212         if (getThing().getStatus() == ONLINE) {
213             if (getRouterOs() != null) {
214                 refreshCache.getValue();
215                 for (Channel channel : getThing().getChannels()) {
216                     ChannelUID channelUID = channel.getUID();
217                     try {
218                         refreshChannel(channelUID);
219                     } catch (RuntimeException e) {
220                         throw new ChannelUpdateException(getThing().getUID(), channelUID, e);
221                     }
222                 }
223             }
224         }
225     }
226
227     protected boolean verifiedRefreshModels() {
228         if (getRouterOs() != null && config != null) {
229             refreshModels();
230             return true;
231         } else {
232             return false;
233         }
234     }
235
236     @Override
237     public void dispose() {
238         cancelRefreshJob();
239     }
240
241     protected abstract void refreshModels();
242
243     protected abstract void refreshChannel(ChannelUID channelUID);
244
245     protected abstract void executeCommand(ChannelUID channelUID, Command command);
246 }