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