2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bticinosmarther.internal.handler;
15 import static org.openhab.binding.bticinosmarther.internal.SmartherBindingConstants.*;
17 import java.time.Duration;
18 import java.util.List;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.TimeUnit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.bticinosmarther.internal.api.dto.Chronothermostat;
25 import org.openhab.binding.bticinosmarther.internal.api.dto.Enums.BoostTime;
26 import org.openhab.binding.bticinosmarther.internal.api.dto.Enums.Mode;
27 import org.openhab.binding.bticinosmarther.internal.api.dto.ModuleStatus;
28 import org.openhab.binding.bticinosmarther.internal.api.dto.Notification;
29 import org.openhab.binding.bticinosmarther.internal.api.dto.Program;
30 import org.openhab.binding.bticinosmarther.internal.api.exception.SmartherGatewayException;
31 import org.openhab.binding.bticinosmarther.internal.api.exception.SmartherIllegalPropertyValueException;
32 import org.openhab.binding.bticinosmarther.internal.api.exception.SmartherSubscriptionAlreadyExistsException;
33 import org.openhab.binding.bticinosmarther.internal.config.SmartherModuleConfiguration;
34 import org.openhab.binding.bticinosmarther.internal.model.ModuleSettings;
35 import org.openhab.binding.bticinosmarther.internal.util.StringUtil;
36 import org.openhab.core.cache.ExpiringCache;
37 import org.openhab.core.library.types.DecimalType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.QuantityType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.library.unit.SIUnits;
42 import org.openhab.core.scheduler.CronScheduler;
43 import org.openhab.core.scheduler.ScheduledCompletableFuture;
44 import org.openhab.core.thing.Bridge;
45 import org.openhab.core.thing.Channel;
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.ThingStatusInfo;
51 import org.openhab.core.thing.binding.BaseThingHandler;
52 import org.openhab.core.types.Command;
53 import org.openhab.core.types.RefreshType;
54 import org.openhab.core.types.State;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * The {@code SmartherModuleHandler} class is responsible of a single Smarther Chronothermostat, handling the commands
60 * that are sent to one of its channels.
61 * Each Smarther Chronothermostat communicates with the Smarther API via its assigned {@code SmartherBridgeHandler}.
63 * @author Fabio Possieri - Initial contribution
66 public class SmartherModuleHandler extends BaseThingHandler {
68 private static final String DAILY_MIDNIGHT = "1 0 0 * * ? *";
69 private static final long POLL_INITIAL_DELAY = 5;
71 private final Logger logger = LoggerFactory.getLogger(SmartherModuleHandler.class);
73 private final CronScheduler cronScheduler;
74 private final SmartherDynamicStateDescriptionProvider dynamicStateDescriptionProvider;
75 private final ChannelUID programChannelUID;
76 private final ChannelUID endDateChannelUID;
78 // Module configuration
79 private SmartherModuleConfiguration config;
81 // Field members assigned in initialize method
82 private @Nullable ScheduledCompletableFuture<Void> jobFuture;
83 private @Nullable Future<?> pollFuture;
84 private @Nullable SmartherBridgeHandler bridgeHandler;
85 private @Nullable ExpiringCache<List<Program>> programCache;
86 private @Nullable ModuleSettings moduleSettings;
88 // Chronothermostat local status
89 private @Nullable Chronothermostat chronothermostat;
92 * Constructs a {@code SmartherModuleHandler} for the given thing, scheduler and dynamic state description provider.
95 * the {@link Thing} thing to be used
97 * the {@link CronScheduler} periodic job scheduler to be used
99 * the {@link SmartherDynamicStateDescriptionProvider} dynamic state description provider to be used
101 public SmartherModuleHandler(Thing thing, CronScheduler scheduler,
102 SmartherDynamicStateDescriptionProvider provider) {
104 this.cronScheduler = scheduler;
105 this.dynamicStateDescriptionProvider = provider;
106 this.programChannelUID = new ChannelUID(thing.getUID(), CHANNEL_SETTINGS_PROGRAM);
107 this.endDateChannelUID = new ChannelUID(thing.getUID(), CHANNEL_SETTINGS_ENDDATE);
108 this.config = new SmartherModuleConfiguration();
111 // ===========================================================================
113 // Chronothermostat thing lifecycle management methods
115 // ===========================================================================
118 public void initialize() {
119 logger.debug("Module[{}] Initialize handler", thing.getUID());
121 final Bridge localBridge = getBridge();
122 if (localBridge == null) {
123 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
127 final SmartherBridgeHandler localBridgeHandler = (SmartherBridgeHandler) localBridge.getHandler();
128 this.bridgeHandler = localBridgeHandler;
129 if (localBridgeHandler == null) {
130 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format(
131 "Missing configuration from the Smarther Bridge (UID:%s). Fix configuration or report if this problem remains.",
132 localBridge.getBridgeUID()));
136 this.config = getConfigAs(SmartherModuleConfiguration.class);
137 if (StringUtil.isBlank(config.getPlantId())) {
138 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
139 "The 'Plant Id' property is not set or empty. If you have an older thing please recreate it.");
142 if (StringUtil.isBlank(config.getModuleId())) {
143 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
144 "The 'Module Id' property is not set or empty. If you have an older thing please recreate it.");
147 if (config.getProgramsRefreshPeriod() <= 0) {
148 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
149 "The 'Programs Refresh Period' must be > 0. If you have an older thing please recreate it.");
152 if (config.getStatusRefreshPeriod() <= 0) {
153 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
154 "The 'Module Status Refresh Period' must be > 0. If you have an older thing please recreate it.");
158 // Initialize automatic mode programs local cache
159 final ExpiringCache<List<Program>> localProgramCache = new ExpiringCache<>(
160 Duration.ofHours(config.getProgramsRefreshPeriod()), this::programCacheAction);
161 this.programCache = localProgramCache;
163 // Initialize module local settings
164 final ModuleSettings localModuleSettings = new ModuleSettings(config.getPlantId(), config.getModuleId());
165 this.moduleSettings = localModuleSettings;
167 updateStatus(ThingStatus.UNKNOWN);
172 logger.debug("Module[{}] Finished initializing!", thing.getUID());
176 public void handleCommand(ChannelUID channelUID, Command command) {
178 handleCommandInternal(channelUID, command);
179 updateModuleStatus();
180 } catch (SmartherIllegalPropertyValueException e) {
181 logger.warn("Module[{}] Received command {} with illegal value {} on channel {}", thing.getUID(), command,
182 e.getMessage(), channelUID.getId());
183 } catch (SmartherGatewayException e) {
184 // catch exceptions and handle it in your binding
185 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
190 * Handles the command sent to a given Channel of this Chronothermostat.
193 * the identifier of the Channel
195 * the command sent to the given Channel
197 * @throws {@link SmartherIllegalPropertyValueException}
198 * if the command contains an illegal value that cannot be mapped to any valid enum value
199 * @throws {@link SmartherGatewayException}
200 * in case of communication issues with the Smarther API
202 private void handleCommandInternal(ChannelUID channelUID, Command command)
203 throws SmartherIllegalPropertyValueException, SmartherGatewayException {
204 final ModuleSettings localModuleSettings = this.moduleSettings;
205 if (localModuleSettings == null) {
209 switch (channelUID.getId()) {
210 case CHANNEL_SETTINGS_MODE:
211 if (command instanceof StringType) {
212 localModuleSettings.setMode(Mode.fromValue(command.toString()));
216 case CHANNEL_SETTINGS_TEMPERATURE:
217 if (changeTemperature(command, localModuleSettings)) {
221 case CHANNEL_SETTINGS_PROGRAM:
222 if (command instanceof DecimalType) {
223 localModuleSettings.setProgram(((DecimalType) command).intValue());
227 case CHANNEL_SETTINGS_BOOSTTIME:
228 if (command instanceof DecimalType) {
229 localModuleSettings.setBoostTime(BoostTime.fromValue(((DecimalType) command).intValue()));
233 case CHANNEL_SETTINGS_ENDDATE:
234 if (command instanceof StringType) {
235 localModuleSettings.setEndDate(command.toString());
239 case CHANNEL_SETTINGS_ENDHOUR:
240 if (changeTimeHour(command, localModuleSettings)) {
244 case CHANNEL_SETTINGS_ENDMINUTE:
245 if (changeTimeMinute(command, localModuleSettings)) {
249 case CHANNEL_SETTINGS_POWER:
250 if (command instanceof OnOffType) {
251 if (OnOffType.ON.equals(command)) {
252 // Apply module settings to the remote module
253 if (getBridgeHandler().setModuleStatus(localModuleSettings)) {
254 // Change applied, update module status
255 logger.debug("Module[{}] New settings applied!", thing.getUID());
257 updateChannelState(CHANNEL_SETTINGS_POWER, OnOffType.OFF);
262 case CHANNEL_CONFIG_FETCH_PROGRAMS:
263 if (command instanceof OnOffType) {
264 if (OnOffType.ON.equals(command)) {
266 "Module[{}] Manually triggered channel to remotely fetch the updated programs list",
269 refreshProgramsList();
270 updateChannelState(CHANNEL_CONFIG_FETCH_PROGRAMS, OnOffType.OFF);
277 if (command instanceof RefreshType) {
278 // Avoid logging wrong command when refresh command is sent
282 logger.debug("Module[{}] Received command {} of wrong type {} on channel {}", thing.getUID(), command,
283 command.getClass().getTypeName(), channelUID.getId());
287 * Changes the "temperature" in module settings, based on the received Command.
288 * The new value is checked against the temperature limits allowed by the device.
291 * the command received on temperature Channel
293 * @return {@code true} if the change succeeded, {@code false} otherwise
295 private boolean changeTemperature(Command command, final ModuleSettings settings) {
296 if (!(command instanceof QuantityType)) {
300 QuantityType<?> quantity = (QuantityType<?>) command;
301 QuantityType<?> newMeasure = quantity.toUnit(SIUnits.CELSIUS);
303 // Check remote device temperature limits
304 if (newMeasure != null && newMeasure.doubleValue() >= 7.1 && newMeasure.doubleValue() <= 40.0) {
305 // Only tenth degree increments are allowed
306 double newTemperature = Math.round(newMeasure.doubleValue() * 10) / 10.0;
308 settings.setSetPointTemperature(QuantityType.valueOf(newTemperature, SIUnits.CELSIUS));
315 * Changes the "end hour" for manual mode in module settings, based on the received Command.
316 * The new value is checked against the 24-hours clock allowed range.
319 * the command received on end hour Channel
321 * @return {@code true} if the change succeeded, {@code false} otherwise
323 private boolean changeTimeHour(Command command, final ModuleSettings settings) {
324 if (command instanceof DecimalType) {
325 int endHour = ((DecimalType) command).intValue();
326 if (endHour >= 0 && endHour <= 23) {
327 settings.setEndHour(endHour);
335 * Changes the "end minute" for manual mode in module settings, based on the received Command.
336 * The new value is modified to match a 15 min step increment.
339 * the command received on end minute Channel
341 * @return {@code true} if the change succeeded, {@code false} otherwise
343 private boolean changeTimeMinute(Command command, final ModuleSettings settings) {
344 if (command instanceof DecimalType) {
345 int endMinute = ((DecimalType) command).intValue();
346 if (endMinute >= 0 && endMinute <= 59) {
347 // Only 15 min increments are allowed
348 endMinute = Math.round(endMinute / 15) * 15;
349 settings.setEndMinute(endMinute);
357 * Handles the notification dispatched to this Chronothermostat from the reference Smarther Bridge.
359 * @param notification
360 * the notification to handle
362 public void handleNotification(Notification notification) {
364 final Chronothermostat notificationChrono = notification.getChronothermostat();
365 if (notificationChrono != null) {
366 this.chronothermostat = notificationChrono;
367 if (config.isSettingsAutoupdate()) {
368 final ModuleSettings localModuleSettings = this.moduleSettings;
369 if (localModuleSettings != null) {
370 localModuleSettings.updateFromChronothermostat(notificationChrono);
373 logger.debug("Module[{}] Handle notification: [{}]", thing.getUID(), this.chronothermostat);
374 updateModuleStatus();
376 } catch (SmartherIllegalPropertyValueException e) {
377 logger.warn("Module[{}] Notification has illegal value: [{}]", thing.getUID(), e.getMessage());
382 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
383 if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
384 // Put module offline when the parent bridge goes offline
385 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Smarther Bridge Offline");
386 logger.debug("Module[{}] Bridge switched {}", thing.getUID(), bridgeStatusInfo.getStatus());
388 // Update the module status when the parent bridge return online
389 logger.debug("Module[{}] Bridge is back ONLINE", thing.getUID());
390 // Restart polling to collect module data
396 public void handleRemoval() {
397 super.handleRemoval();
403 public void dispose() {
404 logger.debug("Module[{}] Dispose handler", thing.getUID());
408 getBridgeHandler().unregisterNotification(config.getPlantId());
409 } catch (SmartherGatewayException e) {
410 logger.warn("Module[{}] API Gateway error during disposing: {}", thing.getUID(), e.getMessage());
412 logger.debug("Module[{}] Finished disposing!", thing.getUID());
415 // ===========================================================================
417 // Chronothermostat data cache management methods
419 // ===========================================================================
422 * Returns the available automatic mode programs to be cached for this Chronothermostat.
424 * @return the available programs to be cached for this Chronothermostat, or {@code null} if the list of available
425 * programs cannot be retrieved
427 private @Nullable List<Program> programCacheAction() {
429 final List<Program> programs = getBridgeHandler().getModulePrograms(config.getPlantId(),
430 config.getModuleId());
431 logger.debug("Module[{}] Available programs: {}", thing.getUID(), programs);
435 } catch (SmartherGatewayException e) {
436 logger.warn("Module[{}] Cannot retrieve available programs: {}", thing.getUID(), e.getMessage());
442 * Sets all the cache to "expired" for this Chronothermostat.
444 private void expireCache() {
445 logger.debug("Module[{}] Invalidating program cache", thing.getUID());
446 final ExpiringCache<List<Program>> localProgramCache = this.programCache;
447 if (localProgramCache != null) {
448 localProgramCache.invalidateValue();
452 // ===========================================================================
454 // Chronothermostat job scheduler methods
456 // ===========================================================================
459 * Starts a new cron scheduler to execute the internal recurring jobs.
461 private synchronized void scheduleJob() {
464 // Schedule daily job to start daily, at midnight
465 final ScheduledCompletableFuture<Void> localJobFuture = cronScheduler.schedule(this::dailyJob, DAILY_MIDNIGHT);
466 this.jobFuture = localJobFuture;
468 logger.debug("Module[{}] Scheduled recurring job {} to start at midnight", thing.getUID(),
469 Integer.toHexString(localJobFuture.hashCode()));
471 // Execute daily job immediately at startup
476 * Cancels all running jobs.
478 * @param mayInterruptIfRunning
479 * {@code true} if the thread executing this task should be interrupted, {@code false} if the in-progress
480 * tasks are allowed to complete
482 private synchronized void stopJob(boolean mayInterruptIfRunning) {
483 final ScheduledCompletableFuture<Void> localJobFuture = this.jobFuture;
484 if (localJobFuture != null) {
485 if (!localJobFuture.isCancelled()) {
486 localJobFuture.cancel(mayInterruptIfRunning);
488 this.jobFuture = null;
493 * Action to be executed by the daily job: refresh the end dates list for "manual" mode.
495 private void dailyJob() {
496 logger.debug("Module[{}] Daily job, refreshing the end dates list for \"manual\" mode", thing.getUID());
497 // Refresh the end dates list for "manual" mode
498 dynamicStateDescriptionProvider.setEndDates(endDateChannelUID, config.getNumberOfEndDays());
499 // If expired, update EndDate in module settings
500 final ModuleSettings localModuleSettings = this.moduleSettings;
501 if (localModuleSettings != null && localModuleSettings.isEndDateExpired()) {
502 localModuleSettings.refreshEndDate();
503 updateChannelState(CHANNEL_SETTINGS_ENDDATE, new StringType(localModuleSettings.getEndDate()));
507 // ===========================================================================
509 // Chronothermostat status polling mechanism methods
511 // ===========================================================================
514 * Starts a new scheduler to periodically poll and update this Chronothermostat status.
516 private void schedulePoll() {
519 // Schedule poll to start after POLL_INITIAL_DELAY sec and run periodically based on status refresh period
520 final Future<?> localPollFuture = scheduler.scheduleWithFixedDelay(this::poll, POLL_INITIAL_DELAY,
521 config.getStatusRefreshPeriod() * 60, TimeUnit.SECONDS);
522 this.pollFuture = localPollFuture;
524 logger.debug("Module[{}] Scheduled poll for {} sec out, then every {} min", thing.getUID(), POLL_INITIAL_DELAY,
525 config.getStatusRefreshPeriod());
529 * Cancels all running poll schedulers.
531 * @param mayInterruptIfRunning
532 * {@code true} if the thread executing this task should be interrupted, {@code false} if the in-progress
533 * tasks are allowed to complete
535 private synchronized void stopPoll(boolean mayInterruptIfRunning) {
536 final Future<?> localPollFuture = this.pollFuture;
537 if (localPollFuture != null) {
538 if (!localPollFuture.isCancelled()) {
539 localPollFuture.cancel(mayInterruptIfRunning);
541 this.pollFuture = null;
546 * Polls to update this Chronothermostat status.
548 * @return {@code true} if the method completes without errors, {@code false} otherwise
550 private synchronized boolean poll() {
552 final Bridge bridge = getBridge();
553 if (bridge != null) {
554 final ThingStatusInfo bridgeStatusInfo = bridge.getStatusInfo();
555 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
556 ModuleStatus moduleStatus = getBridgeHandler().getModuleStatus(config.getPlantId(),
557 config.getModuleId());
559 final Chronothermostat statusChrono = moduleStatus.toChronothermostat();
560 if (statusChrono != null) {
561 if ((this.chronothermostat == null) || config.isSettingsAutoupdate()) {
562 final ModuleSettings localModuleSettings = this.moduleSettings;
563 if (localModuleSettings != null) {
564 localModuleSettings.updateFromChronothermostat(statusChrono);
567 this.chronothermostat = statusChrono;
568 logger.debug("Module[{}] Status: [{}]", thing.getUID(), this.chronothermostat);
570 throw new SmartherGatewayException("No chronothermostat data found");
573 // Refresh the programs list for "automatic" mode
574 refreshProgramsList();
576 updateModuleStatus();
578 getBridgeHandler().registerNotification(config.getPlantId());
580 // Everything is ok > set the Thing state to Online
581 updateStatus(ThingStatus.ONLINE);
583 } else if (thing.getStatus() != ThingStatus.OFFLINE) {
584 logger.debug("Module[{}] Switched {} as Bridge is not online", thing.getUID(),
585 bridgeStatusInfo.getStatus());
586 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Smarther Bridge Offline");
590 } catch (SmartherIllegalPropertyValueException e) {
591 logger.debug("Module[{}] Illegal property value error during polling: {}", thing.getUID(), e.getMessage());
592 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
593 } catch (SmartherSubscriptionAlreadyExistsException e) {
594 logger.debug("Module[{}] Subscription error during polling: {}", thing.getUID(), e.getMessage());
595 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
596 } catch (SmartherGatewayException e) {
597 logger.warn("Module[{}] API Gateway error during polling: {}", thing.getUID(), e.getMessage());
598 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
599 } catch (RuntimeException e) {
600 // All other exceptions apart from Subscription and Gateway issues
601 logger.warn("Module[{}] Unexpected error during polling, please report if this keeps occurring: ",
603 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
609 // ===========================================================================
611 // Chronothermostat convenience methods
613 // ===========================================================================
616 * Convenience method to check and get the Smarther Bridge handler instance for this Module.
618 * @return the Smarther Bridge handler instance
620 * @throws {@link SmartherGatewayException}
621 * in case the Smarther Bridge handler instance is {@code null}
623 private SmartherBridgeHandler getBridgeHandler() throws SmartherGatewayException {
624 final SmartherBridgeHandler localBridgeHandler = this.bridgeHandler;
625 if (localBridgeHandler == null) {
626 throw new SmartherGatewayException("Smarther Bridge handler instance is null");
628 return localBridgeHandler;
632 * Returns this Chronothermostat plant identifier
634 * @return a string containing the plant identifier
636 public String getPlantId() {
637 return config.getPlantId();
641 * Returns this Chronothermostat module identifier
643 * @return a string containing the module identifier
645 public String getModuleId() {
646 return config.getModuleId();
650 * Checks whether this Chronothermostat matches with the given plant and module identifiers.
653 * the plant identifier to match to
655 * the module identifier to match to
657 * @return {@code true} if the Chronothermostat matches the given plant and module identifiers, {@code false}
660 public boolean isLinkedTo(String plantId, String moduleId) {
661 return (config.getPlantId().equals(plantId) && config.getModuleId().equals(moduleId));
665 * Convenience method to refresh the module programs list from cache.
667 private void refreshProgramsList() {
668 final ExpiringCache<List<Program>> localProgramCache = this.programCache;
669 if (localProgramCache != null) {
670 final List<Program> programs = localProgramCache.getValue();
671 if (programs != null) {
672 dynamicStateDescriptionProvider.setPrograms(programChannelUID, programs);
678 * Convenience method to update the given Channel state "only" if the Channel is linked.
681 * the identifier of the Channel to be updated
683 * the new state to be applied to the given Channel
685 private void updateChannelState(String channelId, State state) {
686 final Channel channel = thing.getChannel(channelId);
688 if (channel != null && isLinked(channel.getUID())) {
689 updateState(channel.getUID(), state);
694 * Convenience method to update the whole status of the Chronothermostat associated to this handler.
695 * Channels are updated based on the local {@code chronothermostat} and {@code moduleSettings} objects.
697 * @throws {@link SmartherIllegalPropertyValueException}
698 * if at least one of the module properties cannot be mapped to any valid enum value
700 private void updateModuleStatus() throws SmartherIllegalPropertyValueException {
701 final Chronothermostat localChrono = this.chronothermostat;
702 if (localChrono != null) {
703 // Update the Measures channels
704 updateChannelState(CHANNEL_MEASURES_TEMPERATURE, localChrono.getThermometer().toState());
705 updateChannelState(CHANNEL_MEASURES_HUMIDITY, localChrono.getHygrometer().toState());
706 // Update the Status channels
707 updateChannelState(CHANNEL_STATUS_STATE, (localChrono.isActive() ? OnOffType.ON : OnOffType.OFF));
708 updateChannelState(CHANNEL_STATUS_FUNCTION,
709 new StringType(StringUtil.capitalize(localChrono.getFunction().toLowerCase())));
710 updateChannelState(CHANNEL_STATUS_MODE,
711 new StringType(StringUtil.capitalize(localChrono.getMode().toLowerCase())));
712 updateChannelState(CHANNEL_STATUS_TEMPERATURE, localChrono.getSetPointTemperature().toState());
713 updateChannelState(CHANNEL_STATUS_ENDTIME, new StringType(localChrono.getActivationTimeLabel()));
714 updateChannelState(CHANNEL_STATUS_TEMP_FORMAT, new StringType(localChrono.getTemperatureFormat()));
715 final Program localProgram = localChrono.getProgram();
716 if (localProgram != null) {
717 updateChannelState(CHANNEL_STATUS_PROGRAM, new StringType(String.valueOf(localProgram.getNumber())));
721 final ModuleSettings localSettings = this.moduleSettings;
722 if (localSettings != null) {
723 // Update the Settings channels
724 updateChannelState(CHANNEL_SETTINGS_MODE, new StringType(localSettings.getMode().getValue()));
725 updateChannelState(CHANNEL_SETTINGS_TEMPERATURE, localSettings.getSetPointTemperature());
726 updateChannelState(CHANNEL_SETTINGS_PROGRAM, new DecimalType(localSettings.getProgram()));
727 updateChannelState(CHANNEL_SETTINGS_BOOSTTIME, new DecimalType(localSettings.getBoostTime().getValue()));
728 updateChannelState(CHANNEL_SETTINGS_ENDDATE, new StringType(localSettings.getEndDate()));
729 updateChannelState(CHANNEL_SETTINGS_ENDHOUR, new DecimalType(localSettings.getEndHour()));
730 updateChannelState(CHANNEL_SETTINGS_ENDMINUTE, new DecimalType(localSettings.getEndMinute()));
731 updateChannelState(CHANNEL_SETTINGS_POWER, OnOffType.OFF);