]> git.basschouten.com Git - openhab-addons.git/blob
29df9e1164f924f57d88bc6b376177baf27b9a77
[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.nobohub.internal;
14
15 import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.CHANNEL_HUB_ACTIVE_OVERRIDE_NAME;
16 import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_HOSTNAME;
17 import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_PRODUCTION_DATE;
18 import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_SOFTWARE_VERSION;
19 import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.RECOMMENDED_KEEPALIVE_INTERVAL;
20
21 import java.time.Duration;
22 import java.util.Collection;
23 import java.util.Map;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.nobohub.internal.connection.HubCommunicationThread;
28 import org.openhab.binding.nobohub.internal.connection.HubConnection;
29 import org.openhab.binding.nobohub.internal.discovery.NoboThingDiscoveryService;
30 import org.openhab.binding.nobohub.internal.model.Component;
31 import org.openhab.binding.nobohub.internal.model.ComponentRegister;
32 import org.openhab.binding.nobohub.internal.model.Hub;
33 import org.openhab.binding.nobohub.internal.model.NoboCommunicationException;
34 import org.openhab.binding.nobohub.internal.model.NoboDataException;
35 import org.openhab.binding.nobohub.internal.model.OverrideMode;
36 import org.openhab.binding.nobohub.internal.model.OverridePlan;
37 import org.openhab.binding.nobohub.internal.model.OverrideRegister;
38 import org.openhab.binding.nobohub.internal.model.SerialNumber;
39 import org.openhab.binding.nobohub.internal.model.Temperature;
40 import org.openhab.binding.nobohub.internal.model.WeekProfile;
41 import org.openhab.binding.nobohub.internal.model.WeekProfileRegister;
42 import org.openhab.binding.nobohub.internal.model.Zone;
43 import org.openhab.binding.nobohub.internal.model.ZoneRegister;
44 import org.openhab.core.library.types.StringType;
45 import org.openhab.core.thing.Bridge;
46 import org.openhab.core.thing.ChannelUID;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingStatus;
49 import org.openhab.core.thing.ThingStatusDetail;
50 import org.openhab.core.thing.binding.BaseBridgeHandler;
51 import org.openhab.core.thing.binding.ThingHandler;
52 import org.openhab.core.types.Command;
53 import org.openhab.core.types.RefreshType;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * The {@link NoboHubBridgeHandler} is responsible for handling commands, which are
59  * sent to one of the channels.
60  *
61  * @author Jørgen Austvik - Initial contribution
62  * @author Espen Fossen - Initial contribution
63  */
64 @NonNullByDefault
65 public class NoboHubBridgeHandler extends BaseBridgeHandler {
66
67     private final Logger logger = LoggerFactory.getLogger(NoboHubBridgeHandler.class);
68     private @Nullable HubCommunicationThread hubThread;
69     private @Nullable NoboThingDiscoveryService discoveryService;
70     private @Nullable Hub hub;
71
72     private final OverrideRegister overrideRegister = new OverrideRegister();
73     private final WeekProfileRegister weekProfileRegister = new WeekProfileRegister();
74     private final ZoneRegister zoneRegister = new ZoneRegister();
75     private final ComponentRegister componentRegister = new ComponentRegister();
76
77     public NoboHubBridgeHandler(Bridge bridge) {
78         super(bridge);
79     }
80
81     @Override
82     public void handleCommand(ChannelUID channelUID, Command command) {
83         logger.info("Handle command {} for channel {}!", command.toFullString(), channelUID);
84
85         HubCommunicationThread ht = this.hubThread;
86         Hub h = this.hub;
87         if (command instanceof RefreshType) {
88             try {
89                 if (ht != null) {
90                     ht.getConnection().refreshAll();
91                 }
92             } catch (NoboCommunicationException noboEx) {
93                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
94                         "@text/message.bridge.status.failed [\"" + noboEx.getMessage() + "\"]");
95             }
96
97             return;
98         }
99
100         if (CHANNEL_HUB_ACTIVE_OVERRIDE_NAME.equals(channelUID.getId())) {
101             if (ht != null && h != null) {
102                 if (command instanceof StringType) {
103                     StringType strCommand = (StringType) command;
104                     logger.debug("Changing override for hub {} to {}", channelUID, strCommand);
105                     try {
106                         OverrideMode mode = OverrideMode.getByName(strCommand.toFullString());
107                         ht.getConnection().setOverride(h, mode);
108                     } catch (NoboCommunicationException nce) {
109                         logger.debug("Failed setting override mode", nce);
110                     } catch (NoboDataException nde) {
111                         logger.debug("Date format error setting override mode", nde);
112                     }
113                 } else {
114                     logger.debug("Command of wrong type: {} ({})", command, command.getClass().getName());
115                 }
116             } else {
117                 if (null == h) {
118                     logger.debug("Could not set override, hub not detected yet");
119                 }
120
121                 if (null == ht) {
122                     logger.debug("Could not set override, hub connection thread not set up yet");
123                 }
124             }
125         }
126     }
127
128     @Override
129     public void initialize() {
130         NoboHubBridgeConfiguration config = getConfigAs(NoboHubBridgeConfiguration.class);
131
132         String serialNumber = config.serialNumber;
133         if (null == serialNumber) {
134             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/message.missing.serial");
135             return;
136         }
137
138         String hostName = config.hostName;
139         if (null == hostName || hostName.isEmpty()) {
140             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
141                     "@text/message.bridge.missing.hostname");
142             return;
143         }
144
145         logger.debug("Looking for Hub {} at {}", config.serialNumber, config.hostName);
146
147         // Set the thing status to UNKNOWN temporarily and let the background task decide for the real status.
148         updateStatus(ThingStatus.UNKNOWN);
149
150         // Background handshake:
151         scheduler.execute(() -> {
152             try {
153                 HubConnection conn = new HubConnection(hostName, serialNumber, this);
154                 conn.connect();
155
156                 logger.debug("Done connecting to {} ({})", hostName, serialNumber);
157
158                 Duration timeout = RECOMMENDED_KEEPALIVE_INTERVAL;
159                 if (config.pollingInterval > 0) {
160                     timeout = Duration.ofSeconds(config.pollingInterval);
161                 }
162
163                 logger.debug("Starting communication thread to {}", hostName);
164
165                 HubCommunicationThread ht = new HubCommunicationThread(conn, this, timeout);
166                 ht.start();
167                 hubThread = ht;
168
169                 if (ht.getConnection().isConnected()) {
170                     logger.debug("Communication thread to {} is up and running, we are online", hostName);
171                     updateProperty(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
172                     updateStatus(ThingStatus.ONLINE);
173                 } else {
174                     logger.debug("HubCommunicationThread is not connected anymore, setting to OFFLINE");
175                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
176                             "@text/message.bridge.connection.failed");
177                 }
178             } catch (NoboCommunicationException commEx) {
179                 logger.debug("HubCommunicationThread failed, exiting thread");
180                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, commEx.getMessage());
181             }
182         });
183     }
184
185     @Override
186     public void dispose() {
187         logger.debug("Disposing NoboHub '{}'", getThing().getUID().getId());
188
189         final NoboThingDiscoveryService discoveryService = this.discoveryService;
190         if (discoveryService != null) {
191             discoveryService.stopScan();
192         }
193
194         HubCommunicationThread ht = this.hubThread;
195         if (ht != null) {
196             logger.debug("Stopping communication thread");
197             ht.stopNow();
198         }
199     }
200
201     @Override
202     public void childHandlerInitialized(ThingHandler handler, Thing thing) {
203         logger.info("Adding thing: {}", thing.getLabel());
204     }
205
206     @Override
207     public void childHandlerDisposed(ThingHandler handler, Thing thing) {
208         logger.info("Disposing thing: {}", thing.getLabel());
209     }
210
211     private void onUpdate(Hub hub) {
212         logger.debug("Updating Hub: {}", hub.getName());
213         this.hub = hub;
214         OverridePlan activeOverridePlan = getOverride(hub.getActiveOverrideId());
215
216         if (null != activeOverridePlan) {
217             logger.debug("Updating Hub with ActiveOverrideId {} with Name {}", activeOverridePlan.getId(),
218                     activeOverridePlan.getMode().name());
219
220             updateState(NoboHubBindingConstants.CHANNEL_HUB_ACTIVE_OVERRIDE_NAME,
221                     StringType.valueOf(activeOverridePlan.getMode().name()));
222         }
223
224         // Update all zones to set online status and update profile name from weekProfileRegister
225         for (Zone zone : zoneRegister.values()) {
226             refreshZone(zone);
227         }
228
229         Map<String, String> properties = editProperties();
230         properties.put(PROPERTY_HOSTNAME, hub.getName());
231         properties.put(Thing.PROPERTY_SERIAL_NUMBER, hub.getSerialNumber().toString());
232         properties.put(PROPERTY_SOFTWARE_VERSION, hub.getSoftwareVersion());
233         properties.put(Thing.PROPERTY_HARDWARE_VERSION, hub.getHardwareVersion());
234         properties.put(PROPERTY_PRODUCTION_DATE, hub.getProductionDate());
235         updateProperties(properties);
236     }
237
238     public void receivedData(@Nullable String line) {
239         try {
240             parseLine(line);
241         } catch (NoboDataException nde) {
242             logger.debug("Failed parsing line '{}': {}", line, nde.getMessage());
243         }
244     }
245
246     private void parseLine(@Nullable String line) throws NoboDataException {
247         if (null == line) {
248             return;
249         }
250
251         NoboThingDiscoveryService ds = this.discoveryService;
252         if (line.startsWith("H01")) {
253             Zone zone = Zone.fromH01(line);
254             zoneRegister.put(zone);
255             if (null != ds) {
256                 ds.detectZones(zoneRegister.values());
257             }
258         } else if (line.startsWith("H02")) {
259             Component component = Component.fromH02(line);
260             componentRegister.put(component);
261             if (null != ds) {
262                 ds.detectComponents(componentRegister.values());
263             }
264         } else if (line.startsWith("H03")) {
265             WeekProfile weekProfile = WeekProfile.fromH03(line);
266             weekProfileRegister.put(weekProfile);
267         } else if (line.startsWith("H04")) {
268             OverridePlan overridePlan = OverridePlan.fromH04(line);
269             overrideRegister.put(overridePlan);
270         } else if (line.startsWith("H05")) {
271             Hub hub = Hub.fromH05(line);
272             onUpdate(hub);
273         } else if (line.startsWith("S00")) {
274             Zone zone = Zone.fromH01(line);
275             zoneRegister.remove(zone.getId());
276         } else if (line.startsWith("S01")) {
277             Component component = Component.fromH02(line);
278             componentRegister.remove(component.getSerialNumber());
279         } else if (line.startsWith("S02")) {
280             WeekProfile weekProfile = WeekProfile.fromH03(line);
281             weekProfileRegister.remove(weekProfile.getId());
282         } else if (line.startsWith("S03")) {
283             OverridePlan overridePlan = OverridePlan.fromH04(line);
284             overrideRegister.remove(overridePlan.getId());
285         } else if (line.startsWith("B00")) {
286             Zone zone = Zone.fromH01(line);
287             zoneRegister.put(zone);
288             if (null != ds) {
289                 ds.detectZones(zoneRegister.values());
290             }
291         } else if (line.startsWith("B01")) {
292             Component component = Component.fromH02(line);
293             componentRegister.put(component);
294             if (null != ds) {
295                 ds.detectComponents(componentRegister.values());
296             }
297         } else if (line.startsWith("B02")) {
298             WeekProfile weekProfile = WeekProfile.fromH03(line);
299             weekProfileRegister.put(weekProfile);
300         } else if (line.startsWith("B03")) {
301             OverridePlan overridePlan = OverridePlan.fromH04(line);
302             overrideRegister.put(overridePlan);
303         } else if (line.startsWith("V00")) {
304             Zone zone = Zone.fromH01(line);
305             zoneRegister.put(zone);
306             refreshZone(zone);
307         } else if (line.startsWith("V01")) {
308             Component component = Component.fromH02(line);
309             componentRegister.put(component);
310             refreshComponent(component);
311         } else if (line.startsWith("V02")) {
312             WeekProfile weekProfile = WeekProfile.fromH03(line);
313             weekProfileRegister.put(weekProfile);
314         } else if (line.startsWith("V03")) {
315             Hub hub = Hub.fromH05(line);
316             onUpdate(hub);
317         } else if (line.startsWith("Y02")) {
318             Temperature temp = Temperature.fromY02(line);
319             Component component = getComponent(temp.getSerialNumber());
320             if (null != component) {
321                 component.setTemperature(temp.getTemperature());
322                 refreshComponent(component);
323                 int zoneId = component.getTemperatureSensorForZoneId();
324                 if (zoneId >= 0) {
325                     Zone zone = getZone(zoneId);
326                     if (null != zone) {
327                         zone.setTemperature(temp.getTemperature());
328                         refreshZone(zone);
329                     }
330                 }
331             }
332         } else if (line.startsWith("E00")) {
333             logger.debug("Error from Hub: {}", line);
334         } else {
335             // HANDSHAKE: Basic part of keepalive
336             // V06: Encryption key
337             // H00: contains no information
338             if (!line.startsWith("HANDSHAKE") && !line.startsWith("V06") && !line.startsWith("H00")) {
339                 logger.info("Unknown information from Hub: '{}}'", line);
340             }
341         }
342     }
343
344     public @Nullable Zone getZone(Integer id) {
345         return zoneRegister.get(id);
346     }
347
348     public @Nullable WeekProfile getWeekProfile(Integer id) {
349         return weekProfileRegister.get(id);
350     }
351
352     public @Nullable Component getComponent(SerialNumber serialNumber) {
353         return componentRegister.get(serialNumber);
354     }
355
356     public @Nullable OverridePlan getOverride(Integer id) {
357         return overrideRegister.get(id);
358     }
359
360     public void sendCommand(String command) {
361         @Nullable
362         HubCommunicationThread ht = this.hubThread;
363         if (ht != null) {
364             HubConnection conn = ht.getConnection();
365             conn.sendCommand(command);
366         }
367     }
368
369     private void refreshZone(Zone zone) {
370         this.getThing().getThings().forEach(thing -> {
371             if (thing.getHandler() instanceof ZoneHandler) {
372                 ZoneHandler handler = (ZoneHandler) thing.getHandler();
373                 if (handler != null && handler.getZoneId() == zone.getId()) {
374                     handler.onUpdate(zone);
375                 }
376             }
377         });
378     }
379
380     private void refreshComponent(Component component) {
381         this.getThing().getThings().forEach(thing -> {
382             if (thing.getHandler() instanceof ComponentHandler) {
383                 ComponentHandler handler = (ComponentHandler) thing.getHandler();
384                 if (handler != null) {
385                     SerialNumber handlerSerial = handler.getSerialNumber();
386                     if (handlerSerial != null && component.getSerialNumber().equals(handlerSerial)) {
387                         handler.onUpdate(component);
388                     }
389                 }
390             }
391         });
392     }
393
394     public void startScan() {
395         try {
396             @Nullable
397             HubCommunicationThread ht = this.hubThread;
398             if (ht != null) {
399                 ht.getConnection().refreshAll();
400             }
401         } catch (NoboCommunicationException noboEx) {
402             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
403                     "@text/message.bridge.status.failed [\"" + noboEx.getMessage() + "\"]");
404         }
405     }
406
407     public void setDicsoveryService(NoboThingDiscoveryService discoveryService) {
408         this.discoveryService = discoveryService;
409     }
410
411     public Collection<WeekProfile> getWeekProfiles() {
412         return weekProfileRegister.values();
413     }
414
415     public void setStatusInfo(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
416         updateStatus(status, statusDetail, description);
417     }
418 }