2 * Copyright (c) 2010-2020 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.ecobee.internal.handler;
15 import static org.openhab.binding.ecobee.internal.EcobeeBindingConstants.*;
17 import java.lang.reflect.Field;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.List;
23 import java.util.concurrent.ConcurrentHashMap;
24 import java.util.concurrent.CopyOnWriteArrayList;
25 import java.util.concurrent.Future;
26 import java.util.concurrent.TimeUnit;
27 import java.util.stream.Collectors;
28 import java.util.stream.Stream;
30 import javax.measure.Unit;
32 import org.apache.commons.lang.WordUtils;
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.openhab.binding.ecobee.internal.action.EcobeeActions;
36 import org.openhab.binding.ecobee.internal.api.EcobeeApi;
37 import org.openhab.binding.ecobee.internal.config.EcobeeThermostatConfiguration;
38 import org.openhab.binding.ecobee.internal.discovery.SensorDiscoveryService;
39 import org.openhab.binding.ecobee.internal.dto.SelectionDTO;
40 import org.openhab.binding.ecobee.internal.dto.thermostat.AlertDTO;
41 import org.openhab.binding.ecobee.internal.dto.thermostat.ClimateDTO;
42 import org.openhab.binding.ecobee.internal.dto.thermostat.EventDTO;
43 import org.openhab.binding.ecobee.internal.dto.thermostat.HouseDetailsDTO;
44 import org.openhab.binding.ecobee.internal.dto.thermostat.LocationDTO;
45 import org.openhab.binding.ecobee.internal.dto.thermostat.ManagementDTO;
46 import org.openhab.binding.ecobee.internal.dto.thermostat.ProgramDTO;
47 import org.openhab.binding.ecobee.internal.dto.thermostat.RemoteSensorDTO;
48 import org.openhab.binding.ecobee.internal.dto.thermostat.RuntimeDTO;
49 import org.openhab.binding.ecobee.internal.dto.thermostat.SettingsDTO;
50 import org.openhab.binding.ecobee.internal.dto.thermostat.TechnicianDTO;
51 import org.openhab.binding.ecobee.internal.dto.thermostat.ThermostatDTO;
52 import org.openhab.binding.ecobee.internal.dto.thermostat.ThermostatUpdateRequestDTO;
53 import org.openhab.binding.ecobee.internal.dto.thermostat.VersionDTO;
54 import org.openhab.binding.ecobee.internal.dto.thermostat.WeatherDTO;
55 import org.openhab.binding.ecobee.internal.dto.thermostat.WeatherForecastDTO;
56 import org.openhab.binding.ecobee.internal.function.AbstractFunction;
57 import org.openhab.binding.ecobee.internal.function.FunctionRequest;
58 import org.openhab.core.i18n.TimeZoneProvider;
59 import org.openhab.core.library.types.DecimalType;
60 import org.openhab.core.library.types.OnOffType;
61 import org.openhab.core.library.types.QuantityType;
62 import org.openhab.core.library.types.StringType;
63 import org.openhab.core.library.unit.ImperialUnits;
64 import org.openhab.core.library.unit.SIUnits;
65 import org.openhab.core.library.unit.Units;
66 import org.openhab.core.thing.Bridge;
67 import org.openhab.core.thing.Channel;
68 import org.openhab.core.thing.ChannelUID;
69 import org.openhab.core.thing.Thing;
70 import org.openhab.core.thing.ThingStatus;
71 import org.openhab.core.thing.ThingStatusDetail;
72 import org.openhab.core.thing.ThingStatusInfo;
73 import org.openhab.core.thing.binding.BaseBridgeHandler;
74 import org.openhab.core.thing.binding.ThingHandler;
75 import org.openhab.core.thing.binding.ThingHandlerService;
76 import org.openhab.core.thing.type.ChannelType;
77 import org.openhab.core.thing.type.ChannelTypeRegistry;
78 import org.openhab.core.thing.type.ChannelTypeUID;
79 import org.openhab.core.types.Command;
80 import org.openhab.core.types.RefreshType;
81 import org.openhab.core.types.State;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
86 * The {@link EcobeeThermostatBridgeHandler} is the handler for an Ecobee thermostat.
88 * @author Mark Hilbush - Initial contribution
91 public class EcobeeThermostatBridgeHandler extends BaseBridgeHandler {
93 private static final int SENSOR_DISCOVERY_STARTUP_DELAY_SECONDS = 30;
94 private static final int SENSOR_DISCOVERY_INTERVAL_SECONDS = 300;
96 private final Logger logger = LoggerFactory.getLogger(EcobeeThermostatBridgeHandler.class);
98 private TimeZoneProvider timeZoneProvider;
99 private ChannelTypeRegistry channelTypeRegistry;
101 private @NonNullByDefault({}) String thermostatId;
103 private final Map<String, EcobeeSensorThingHandler> sensorHandlers = new ConcurrentHashMap<>();
105 private @Nullable Future<?> discoverSensorsJob;
106 private @Nullable SensorDiscoveryService discoveryService;
108 private @Nullable ThermostatDTO savedThermostat;
109 private @Nullable List<RemoteSensorDTO> savedSensors;
110 private List<String> validClimateRefs = new CopyOnWriteArrayList<>();
111 private Map<String, State> stateCache = new ConcurrentHashMap<>();
112 private Map<ChannelUID, Boolean> channelReadOnlyMap = new HashMap<>();
113 private Map<Integer, String> symbolMap = new HashMap<>();
114 private Map<Integer, String> skyMap = new HashMap<>();
116 public EcobeeThermostatBridgeHandler(Bridge bridge, TimeZoneProvider timeZoneProvider,
117 ChannelTypeRegistry channelTypeRegistry) {
119 this.timeZoneProvider = timeZoneProvider;
120 this.channelTypeRegistry = channelTypeRegistry;
124 public void initialize() {
125 thermostatId = getConfigAs(EcobeeThermostatConfiguration.class).thermostatId;
126 logger.debug("ThermostatBridge: Initializing thermostat '{}'", thermostatId);
127 initializeWeatherMaps();
128 initializeReadOnlyChannels();
130 scheduleDiscoveryJob();
131 updateStatus(EcobeeUtils.isBridgeOnline(getBridge()) ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
135 public void dispose() {
136 cancelDiscoveryJob();
137 logger.debug("ThermostatBridge: Disposing thermostat '{}'", thermostatId);
141 public void childHandlerInitialized(ThingHandler sensorHandler, Thing sensorThing) {
142 String sensorId = (String) sensorThing.getConfiguration().get(CONFIG_SENSOR_ID);
143 sensorHandlers.put(sensorId, (EcobeeSensorThingHandler) sensorHandler);
144 logger.debug("ThermostatBridge: Saving sensor handler for {} with id {}", sensorThing.getUID(), sensorId);
148 public void childHandlerDisposed(ThingHandler sensorHandler, Thing sensorThing) {
149 String sensorId = (String) sensorThing.getConfiguration().get(CONFIG_SENSOR_ID);
150 sensorHandlers.remove(sensorId);
151 logger.debug("ThermostatBridge: Removing sensor handler for {} with id {}", sensorThing.getUID(), sensorId);
155 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
156 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
157 updateStatus(ThingStatus.ONLINE);
159 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
163 @SuppressWarnings("null")
165 public void handleCommand(ChannelUID channelUID, Command command) {
166 if (command instanceof RefreshType) {
167 State state = stateCache.get(channelUID.getId());
169 updateState(channelUID.getId(), state);
173 if (isChannelReadOnly(channelUID)) {
174 logger.debug("Can't apply command '{}' to '{}' because channel is readonly", command, channelUID.getId());
177 scheduler.execute(() -> {
178 handleThermostatCommand(channelUID, command);
182 public void setDiscoveryService(SensorDiscoveryService discoveryService) {
183 this.discoveryService = discoveryService;
187 * Called by the AccountBridgeHandler to create a Selection that
188 * includes only the Ecobee objects for which there's at least one
189 * item linked to one of that object's channels.
193 public SelectionDTO getSelection() {
194 final SelectionDTO selection = new SelectionDTO();
195 for (String group : CHANNEL_GROUPS) {
196 for (Channel channel : thing.getChannelsOfGroup(group)) {
197 if (isLinked(channel.getUID())) {
199 Field field = selection.getClass().getField("include" + WordUtils.capitalize(group));
200 logger.trace("ThermostatBridge: Thermostat thing '{}' including object '{}' in selection",
201 thing.getUID(), field.getName());
202 field.set(selection, Boolean.TRUE);
204 } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException
205 | SecurityException e) {
206 logger.debug("ThermostatBridge: Exception setting selection for group '{}'", group, e);
214 public List<RemoteSensorDTO> getSensors() {
215 List<RemoteSensorDTO> localSavedSensors = savedSensors;
216 return localSavedSensors == null ? EMPTY_SENSORS : localSavedSensors;
219 public @Nullable String getAlerts() {
220 ThermostatDTO thermostat = savedThermostat;
221 if (thermostat != null && thermostat.alerts != null) {
222 return EcobeeApi.getGson().toJson(thermostat.alerts);
227 public @Nullable String getEvents() {
228 ThermostatDTO thermostat = savedThermostat;
229 if (thermostat != null && thermostat.events != null) {
230 return EcobeeApi.getGson().toJson(thermostat.events);
235 public @Nullable String getClimates() {
236 ThermostatDTO thermostat = savedThermostat;
237 if (thermostat != null && thermostat.program != null && thermostat.program.climates != null) {
238 return EcobeeApi.getGson().toJson(thermostat.program.climates);
243 public boolean isValidClimateRef(String climateRef) {
244 return validClimateRefs.contains(climateRef);
247 public String getThermostatId() {
252 * Called by EcobeeActions to perform a thermostat function
254 public boolean actionPerformFunction(AbstractFunction function) {
255 logger.debug("ThermostatBridge: Perform function '{}' on thermostat {}", function.type, thermostatId);
256 SelectionDTO selection = new SelectionDTO();
257 selection.setThermostats(Collections.singleton(thermostatId));
258 FunctionRequest request = new FunctionRequest(selection);
259 request.functions = Collections.singletonList(function);
260 EcobeeAccountBridgeHandler handler = getBridgeHandler();
261 if (handler != null) {
262 return handler.performThermostatFunction(request);
268 public Collection<Class<? extends ThingHandlerService>> getServices() {
269 return Collections.unmodifiableList(
270 Stream.of(EcobeeActions.class, SensorDiscoveryService.class).collect(Collectors.toList()));
273 public void updateChannels(ThermostatDTO thermostat) {
274 logger.debug("ThermostatBridge: Updating channels for thermostat id {}", thermostat.identifier);
275 savedThermostat = thermostat;
276 updateAlert(thermostat.alerts);
277 updateHouseDetails(thermostat.houseDetails);
278 updateInfo(thermostat);
279 updateEquipmentStatus(thermostat);
280 updateLocation(thermostat.location);
281 updateManagement(thermostat.management);
282 updateProgram(thermostat.program);
283 updateEvent(thermostat.events);
284 updateRemoteSensors(thermostat.remoteSensors);
285 updateRuntime(thermostat.runtime);
286 updateSettings(thermostat.settings);
287 updateTechnician(thermostat.technician);
288 updateVersion(thermostat.version);
289 updateWeather(thermostat.weather);
290 savedSensors = thermostat.remoteSensors;
293 private void handleThermostatCommand(ChannelUID channelUID, Command command) {
294 logger.debug("Got command '{}' for channel '{}' of thing '{}'", command, channelUID, getThing().getUID());
295 String channelId = channelUID.getIdWithoutGroup();
296 String groupId = channelUID.getGroupId();
297 if (groupId == null) {
298 logger.info("Can't handle command because channel's groupId is null");
301 ThermostatDTO thermostat = new ThermostatDTO();
306 field = thermostat.getClass().getField(channelId);
307 setField(field, thermostat, command);
310 SettingsDTO settings = new SettingsDTO();
311 field = settings.getClass().getField(channelId);
312 setField(field, settings, command);
313 thermostat.settings = settings;
316 LocationDTO location = new LocationDTO();
317 field = location.getClass().getField(channelId);
318 setField(field, location, command);
319 thermostat.location = location;
321 case CHGRP_HOUSE_DETAILS:
322 HouseDetailsDTO houseDetails = new HouseDetailsDTO();
323 field = houseDetails.getClass().getField(channelId);
324 setField(field, houseDetails, command);
325 thermostat.houseDetails = houseDetails;
328 // All other groups contain only read-only fields
331 performThermostatUpdate(thermostat);
332 } catch (NoSuchFieldException | SecurityException e) {
333 logger.info("Unable to get field for '{}.{}'", groupId, channelId);
337 private void setField(Field field, Object object, Command command) {
338 logger.info("Setting field '{}.{}' to value '{}'", object.getClass().getSimpleName().toLowerCase(),
339 field.getName(), command);
340 Class<?> fieldClass = field.getType();
342 boolean success = false;
343 if (String.class.isAssignableFrom(fieldClass)) {
344 if (command instanceof StringType) {
345 logger.debug("Set field of type String to value of StringType");
346 field.set(object, command.toString());
349 } else if (Integer.class.isAssignableFrom(fieldClass)) {
350 if (command instanceof DecimalType) {
351 logger.debug("Set field of type Integer to value of DecimalType");
352 field.set(object, Integer.valueOf(((DecimalType) command).intValue()));
354 } else if (command instanceof QuantityType) {
355 Unit<?> unit = ((QuantityType<?>) command).getUnit();
356 logger.debug("Set field of type Integer to value of QuantityType with unit {}", unit);
357 if (unit.equals(ImperialUnits.FAHRENHEIT) || unit.equals(SIUnits.CELSIUS)) {
358 QuantityType<?> quantity = ((QuantityType<?>) command).toUnit(ImperialUnits.FAHRENHEIT);
359 if (quantity != null) {
360 field.set(object, quantity.intValue() * 10);
365 } else if (Boolean.class.isAssignableFrom(fieldClass)) {
366 if (command instanceof OnOffType) {
367 logger.debug("Set field of type Boolean to value of OnOffType");
368 field.set(object, command == OnOffType.ON);
373 logger.info("Don't know how to convert command of type '{}' to {}.{}",
374 command.getClass().getSimpleName(), object.getClass().getSimpleName(), field.getName());
376 } catch (IllegalArgumentException | IllegalAccessException e) {
377 logger.info("Unable to set field '{}.{}' to value '{}'", object.getClass().getSimpleName(), field.getName(),
382 private void updateInfo(ThermostatDTO thermostat) {
383 final String grp = CHGRP_INFO + "#";
384 updateChannel(grp + CH_IDENTIFIER, EcobeeUtils.undefOrString(thermostat.identifier));
385 updateChannel(grp + CH_NAME, EcobeeUtils.undefOrString(thermostat.name));
386 updateChannel(grp + CH_THERMOSTAT_REV, EcobeeUtils.undefOrString(thermostat.thermostatRev));
387 updateChannel(grp + CH_IS_REGISTERED, EcobeeUtils.undefOrOnOff(thermostat.isRegistered));
388 updateChannel(grp + CH_MODEL_NUMBER, EcobeeUtils.undefOrString(thermostat.modelNumber));
389 updateChannel(grp + CH_BRAND, EcobeeUtils.undefOrString(thermostat.brand));
390 updateChannel(grp + CH_FEATURES, EcobeeUtils.undefOrString(thermostat.features));
391 updateChannel(grp + CH_LAST_MODIFIED, EcobeeUtils.undefOrDate(thermostat.lastModified, timeZoneProvider));
392 updateChannel(grp + CH_THERMOSTAT_TIME, EcobeeUtils.undefOrDate(thermostat.thermostatTime, timeZoneProvider));
395 private void updateEquipmentStatus(ThermostatDTO thermostat) {
396 final String grp = CHGRP_EQUIPMENT_STATUS + "#";
397 updateChannel(grp + CH_EQUIPMENT_STATUS, EcobeeUtils.undefOrString(thermostat.equipmentStatus));
400 private void updateRuntime(@Nullable RuntimeDTO runtime) {
401 if (runtime == null) {
404 final String grp = CHGRP_RUNTIME + "#";
405 updateChannel(grp + CH_RUNTIME_REV, EcobeeUtils.undefOrString(runtime.runtimeRev));
406 updateChannel(grp + CH_CONNECTED, EcobeeUtils.undefOrOnOff(runtime.connected));
407 updateChannel(grp + CH_FIRST_CONNECTED, EcobeeUtils.undefOrDate(runtime.firstConnected, timeZoneProvider));
408 updateChannel(grp + CH_CONNECT_DATE_TIME, EcobeeUtils.undefOrDate(runtime.connectDateTime, timeZoneProvider));
409 updateChannel(grp + CH_DISCONNECT_DATE_TIME,
410 EcobeeUtils.undefOrDate(runtime.disconnectDateTime, timeZoneProvider));
411 updateChannel(grp + CH_RT_LAST_MODIFIED, EcobeeUtils.undefOrDate(runtime.lastModified, timeZoneProvider));
412 updateChannel(grp + CH_RT_LAST_STATUS_MODIFIED,
413 EcobeeUtils.undefOrDate(runtime.lastStatusModified, timeZoneProvider));
414 updateChannel(grp + CH_RUNTIME_DATE, EcobeeUtils.undefOrString(runtime.runtimeDate));
415 updateChannel(grp + CH_RUNTIME_INTERVAL, EcobeeUtils.undefOrDecimal(runtime.runtimeInterval));
416 updateChannel(grp + CH_ACTUAL_TEMPERATURE, EcobeeUtils.undefOrTemperature(runtime.actualTemperature));
417 updateChannel(grp + CH_ACTUAL_HUMIDITY, EcobeeUtils.undefOrQuantity(runtime.actualHumidity, Units.PERCENT));
418 updateChannel(grp + CH_RAW_TEMPERATURE, EcobeeUtils.undefOrTemperature(runtime.rawTemperature));
419 updateChannel(grp + CH_SHOW_ICON_MODE, EcobeeUtils.undefOrDecimal(runtime.showIconMode));
420 updateChannel(grp + CH_DESIRED_HEAT, EcobeeUtils.undefOrTemperature(runtime.desiredHeat));
421 updateChannel(grp + CH_DESIRED_COOL, EcobeeUtils.undefOrTemperature(runtime.desiredCool));
422 updateChannel(grp + CH_DESIRED_HUMIDITY, EcobeeUtils.undefOrQuantity(runtime.desiredHumidity, Units.PERCENT));
423 updateChannel(grp + CH_DESIRED_DEHUMIDITY,
424 EcobeeUtils.undefOrQuantity(runtime.desiredDehumidity, Units.PERCENT));
425 updateChannel(grp + CH_DESIRED_FAN_MODE, EcobeeUtils.undefOrString(runtime.desiredFanMode));
426 if (runtime.desiredHeatRange != null && runtime.desiredHeatRange.size() == 2) {
427 updateChannel(grp + CH_DESIRED_HEAT_RANGE_LOW,
428 EcobeeUtils.undefOrTemperature(runtime.desiredHeatRange.get(0)));
429 updateChannel(grp + CH_DESIRED_HEAT_RANGE_HIGH,
430 EcobeeUtils.undefOrTemperature(runtime.desiredHeatRange.get(1)));
432 if (runtime.desiredCoolRange != null && runtime.desiredCoolRange.size() == 2) {
433 updateChannel(grp + CH_DESIRED_COOL_RANGE_LOW,
434 EcobeeUtils.undefOrTemperature(runtime.desiredCoolRange.get(0)));
435 updateChannel(grp + CH_DESIRED_COOL_RANGE_HIGH,
436 EcobeeUtils.undefOrTemperature(runtime.desiredCoolRange.get(1)));
440 private void updateSettings(@Nullable SettingsDTO settings) {
441 if (settings == null) {
444 final String grp = CHGRP_SETTINGS + "#";
445 updateChannel(grp + CH_HVAC_MODE, EcobeeUtils.undefOrString(settings.hvacMode));
446 updateChannel(grp + CH_LAST_SERVICE_DATE, EcobeeUtils.undefOrString(settings.lastServiceDate));
447 updateChannel(grp + CH_SERVICE_REMIND_ME, EcobeeUtils.undefOrOnOff(settings.serviceRemindMe));
448 updateChannel(grp + CH_MONTHS_BETWEEN_SERVICE, EcobeeUtils.undefOrDecimal(settings.monthsBetweenService));
449 updateChannel(grp + CH_REMIND_ME_DATE, EcobeeUtils.undefOrString(settings.remindMeDate));
450 updateChannel(grp + CH_VENT, EcobeeUtils.undefOrString(settings.vent));
451 updateChannel(grp + CH_VENTILATOR_MIN_ON_TIME, EcobeeUtils.undefOrDecimal(settings.ventilatorMinOnTime));
452 updateChannel(grp + CH_SERVICE_REMIND_TECHNICIAN, EcobeeUtils.undefOrOnOff(settings.serviceRemindTechnician));
453 updateChannel(grp + CH_EI_LOCATION, EcobeeUtils.undefOrString(settings.eiLocation));
454 updateChannel(grp + CH_COLD_TEMP_ALERT, EcobeeUtils.undefOrTemperature(settings.coldTempAlert));
455 updateChannel(grp + CH_COLD_TEMP_ALERT_ENABLED, EcobeeUtils.undefOrOnOff(settings.coldTempAlertEnabled));
456 updateChannel(grp + CH_HOT_TEMP_ALERT, EcobeeUtils.undefOrTemperature(settings.hotTempAlert));
457 updateChannel(grp + CH_HOT_TEMP_ALERT_ENABLED, EcobeeUtils.undefOrOnOff(settings.hotTempAlertEnabled));
458 updateChannel(grp + CH_COOL_STAGES, EcobeeUtils.undefOrDecimal(settings.coolStages));
459 updateChannel(grp + CH_HEAT_STAGES, EcobeeUtils.undefOrDecimal(settings.heatStages));
460 updateChannel(grp + CH_MAX_SET_BACK, EcobeeUtils.undefOrDecimal(settings.maxSetBack));
461 updateChannel(grp + CH_MAX_SET_FORWARD, EcobeeUtils.undefOrDecimal(settings.maxSetForward));
462 updateChannel(grp + CH_QUICK_SAVE_SET_BACK, EcobeeUtils.undefOrDecimal(settings.quickSaveSetBack));
463 updateChannel(grp + CH_QUICK_SAVE_SET_FORWARD, EcobeeUtils.undefOrDecimal(settings.quickSaveSetForward));
464 updateChannel(grp + CH_HAS_HEAT_PUMP, EcobeeUtils.undefOrOnOff(settings.hasHeatPump));
465 updateChannel(grp + CH_HAS_FORCED_AIR, EcobeeUtils.undefOrOnOff(settings.hasForcedAir));
466 updateChannel(grp + CH_HAS_BOILER, EcobeeUtils.undefOrOnOff(settings.hasBoiler));
467 updateChannel(grp + CH_HAS_HUMIDIFIER, EcobeeUtils.undefOrOnOff(settings.hasHumidifier));
468 updateChannel(grp + CH_HAS_ERV, EcobeeUtils.undefOrOnOff(settings.hasErv));
469 updateChannel(grp + CH_HAS_HRV, EcobeeUtils.undefOrOnOff(settings.hasHrv));
470 updateChannel(grp + CH_CONDENSATION_AVOID, EcobeeUtils.undefOrOnOff(settings.condensationAvoid));
471 updateChannel(grp + CH_USE_CELSIUS, EcobeeUtils.undefOrOnOff(settings.useCelsius));
472 updateChannel(grp + CH_USE_TIME_FORMAT_12, EcobeeUtils.undefOrOnOff(settings.useTimeFormat12));
473 updateChannel(grp + CH_LOCALE, EcobeeUtils.undefOrString(settings.locale));
474 updateChannel(grp + CH_HUMIDITY, EcobeeUtils.undefOrString(settings.humidity));
475 updateChannel(grp + CH_HUMIDIFIER_MODE, EcobeeUtils.undefOrString(settings.humidifierMode));
476 updateChannel(grp + CH_BACKLIGHT_ON_INTENSITY, EcobeeUtils.undefOrDecimal(settings.backlightOnIntensity));
477 updateChannel(grp + CH_BACKLIGHT_SLEEP_INTENSITY, EcobeeUtils.undefOrDecimal(settings.backlightSleepIntensity));
478 updateChannel(grp + CH_BACKLIGHT_OFF_TIME, EcobeeUtils.undefOrDecimal(settings.backlightOffTime));
479 updateChannel(grp + CH_SOUND_TICK_VOLUME, EcobeeUtils.undefOrDecimal(settings.soundTickVolume));
480 updateChannel(grp + CH_SOUND_ALERT_VOLUME, EcobeeUtils.undefOrDecimal(settings.soundAlertVolume));
481 updateChannel(grp + CH_COMPRESSOR_PROTECTION_MIN_TIME,
482 EcobeeUtils.undefOrDecimal(settings.compressorProtectionMinTime));
483 updateChannel(grp + CH_COMPRESSOR_PROTECTION_MIN_TEMP,
484 EcobeeUtils.undefOrTemperature(settings.compressorProtectionMinTemp));
485 updateChannel(grp + CH_STAGE1_HEATING_DIFFERENTIAL_TEMP,
486 EcobeeUtils.undefOrDecimal(settings.stage1HeatingDifferentialTemp));
487 updateChannel(grp + CH_STAGE1_COOLING_DIFFERENTIAL_TEMP,
488 EcobeeUtils.undefOrDecimal(settings.stage1CoolingDifferentialTemp));
489 updateChannel(grp + CH_STAGE1_HEATING_DISSIPATION_TIME,
490 EcobeeUtils.undefOrDecimal(settings.stage1HeatingDissipationTime));
491 updateChannel(grp + CH_STAGE1_COOLING_DISSIPATION_TIME,
492 EcobeeUtils.undefOrDecimal(settings.stage1CoolingDissipationTime));
493 updateChannel(grp + CH_HEAT_PUMP_REVERSAL_ON_COOL, EcobeeUtils.undefOrOnOff(settings.heatPumpReversalOnCool));
494 updateChannel(grp + CH_FAN_CONTROLLER_REQUIRED, EcobeeUtils.undefOrOnOff(settings.fanControlRequired));
495 updateChannel(grp + CH_FAN_MIN_ON_TIME, EcobeeUtils.undefOrDecimal(settings.fanMinOnTime));
496 updateChannel(grp + CH_HEAT_COOL_MIN_DELTA, EcobeeUtils.undefOrDecimal(settings.heatCoolMinDelta));
497 updateChannel(grp + CH_TEMP_CORRECTION, EcobeeUtils.undefOrDecimal(settings.tempCorrection));
498 updateChannel(grp + CH_HOLD_ACTION, EcobeeUtils.undefOrString(settings.holdAction));
499 updateChannel(grp + CH_HEAT_PUMP_GROUND_WATER, EcobeeUtils.undefOrOnOff(settings.heatPumpGroundWater));
500 updateChannel(grp + CH_HAS_ELECTRIC, EcobeeUtils.undefOrOnOff(settings.hasElectric));
501 updateChannel(grp + CH_HAS_DEHUMIDIFIER, EcobeeUtils.undefOrOnOff(settings.hasDehumidifier));
502 updateChannel(grp + CH_DEHUMIDIFIER_MODE, EcobeeUtils.undefOrString(settings.dehumidifierMode));
503 updateChannel(grp + CH_DEHUMIDIFIER_LEVEL, EcobeeUtils.undefOrDecimal(settings.dehumidifierLevel));
504 updateChannel(grp + CH_DEHUMIDIFY_WITH_AC, EcobeeUtils.undefOrOnOff(settings.dehumidifyWithAC));
505 updateChannel(grp + CH_DEHUMIDIFY_OVERCOOL_OFFSET,
506 EcobeeUtils.undefOrDecimal(settings.dehumidifyOvercoolOffset));
507 updateChannel(grp + CH_AUTO_HEAT_COOL_FEATURE_ENABLED,
508 EcobeeUtils.undefOrOnOff(settings.autoHeatCoolFeatureEnabled));
509 updateChannel(grp + CH_WIFI_OFFLINE_ALERT, EcobeeUtils.undefOrOnOff(settings.wifiOfflineAlert));
510 updateChannel(grp + CH_HEAT_MIN_TEMP, EcobeeUtils.undefOrTemperature(settings.heatMinTemp));
511 updateChannel(grp + CH_HEAT_MAX_TEMP, EcobeeUtils.undefOrTemperature(settings.heatMaxTemp));
512 updateChannel(grp + CH_COOL_MIN_TEMP, EcobeeUtils.undefOrTemperature(settings.coolMinTemp));
513 updateChannel(grp + CH_COOL_MAX_TEMP, EcobeeUtils.undefOrTemperature(settings.coolMaxTemp));
514 updateChannel(grp + CH_HEAT_RANGE_HIGH, EcobeeUtils.undefOrTemperature(settings.heatRangeHigh));
515 updateChannel(grp + CH_HEAT_RANGE_LOW, EcobeeUtils.undefOrTemperature(settings.heatRangeLow));
516 updateChannel(grp + CH_COOL_RANGE_HIGH, EcobeeUtils.undefOrTemperature(settings.coolRangeHigh));
517 updateChannel(grp + CH_COOL_RANGE_LOW, EcobeeUtils.undefOrTemperature(settings.coolRangeLow));
518 updateChannel(grp + CH_USER_ACCESS_CODE, EcobeeUtils.undefOrString(settings.userAccessCode));
519 updateChannel(grp + CH_USER_ACCESS_SETTING, EcobeeUtils.undefOrDecimal(settings.userAccessSetting));
520 updateChannel(grp + CH_AUX_RUNTIME_ALERT, EcobeeUtils.undefOrDecimal(settings.auxRuntimeAlert));
521 updateChannel(grp + CH_AUX_OUTDOOR_TEMP_ALERT, EcobeeUtils.undefOrTemperature(settings.auxOutdoorTempAlert));
522 updateChannel(grp + CH_AUX_MAX_OUTDOOR_TEMP, EcobeeUtils.undefOrTemperature(settings.auxMaxOutdoorTemp));
523 updateChannel(grp + CH_AUX_RUNTIME_ALERT_NOTIFY, EcobeeUtils.undefOrOnOff(settings.auxRuntimeAlertNotify));
524 updateChannel(grp + CH_AUX_OUTDOOR_TEMP_ALERT_NOTIFY,
525 EcobeeUtils.undefOrOnOff(settings.auxOutdoorTempAlertNotify));
526 updateChannel(grp + CH_AUX_RUNTIME_ALERT_NOTIFY_TECHNICIAN,
527 EcobeeUtils.undefOrOnOff(settings.auxRuntimeAlertNotifyTechnician));
528 updateChannel(grp + CH_AUX_OUTDOOR_TEMP_ALERT_NOTIFY_TECHNICIAN,
529 EcobeeUtils.undefOrOnOff(settings.auxOutdoorTempAlertNotifyTechnician));
530 updateChannel(grp + CH_DISABLE_PREHEATING, EcobeeUtils.undefOrOnOff(settings.disablePreHeating));
531 updateChannel(grp + CH_DISABLE_PRECOOLING, EcobeeUtils.undefOrOnOff(settings.disablePreCooling));
532 updateChannel(grp + CH_INSTALLER_CODE_REQUIRED, EcobeeUtils.undefOrOnOff(settings.installerCodeRequired));
533 updateChannel(grp + CH_DR_ACCEPT, EcobeeUtils.undefOrString(settings.drAccept));
534 updateChannel(grp + CH_IS_RENTAL_PROPERTY, EcobeeUtils.undefOrOnOff(settings.isRentalProperty));
535 updateChannel(grp + CH_USE_ZONE_CONTROLLER, EcobeeUtils.undefOrOnOff(settings.useZoneController));
536 updateChannel(grp + CH_RANDOM_START_DELAY_COOL, EcobeeUtils.undefOrDecimal(settings.randomStartDelayCool));
537 updateChannel(grp + CH_RANDOM_START_DELAY_HEAT, EcobeeUtils.undefOrDecimal(settings.randomStartDelayHeat));
538 updateChannel(grp + CH_HUMIDITY_HIGH_ALERT,
539 EcobeeUtils.undefOrQuantity(settings.humidityHighAlert, Units.PERCENT));
540 updateChannel(grp + CH_HUMIDITY_LOW_ALERT,
541 EcobeeUtils.undefOrQuantity(settings.humidityLowAlert, Units.PERCENT));
542 updateChannel(grp + CH_DISABLE_HEAT_PUMP_ALERTS, EcobeeUtils.undefOrOnOff(settings.disableHeatPumpAlerts));
543 updateChannel(grp + CH_DISABLE_ALERTS_ON_IDT, EcobeeUtils.undefOrOnOff(settings.disableAlertsOnIdt));
544 updateChannel(grp + CH_HUMIDITY_ALERT_NOTIFY, EcobeeUtils.undefOrOnOff(settings.humidityAlertNotify));
545 updateChannel(grp + CH_HUMIDITY_ALERT_NOTIFY_TECHNICIAN,
546 EcobeeUtils.undefOrOnOff(settings.humidityAlertNotifyTechnician));
547 updateChannel(grp + CH_TEMP_ALERT_NOTIFY, EcobeeUtils.undefOrOnOff(settings.tempAlertNotify));
548 updateChannel(grp + CH_TEMP_ALERT_NOTIFY_TECHNICIAN,
549 EcobeeUtils.undefOrOnOff(settings.tempAlertNotifyTechnician));
550 updateChannel(grp + CH_MONTHLY_ELECTRICITY_BILL_LIMIT,
551 EcobeeUtils.undefOrDecimal(settings.monthlyElectricityBillLimit));
552 updateChannel(grp + CH_ENABLE_ELECTRICITY_BILL_ALERT,
553 EcobeeUtils.undefOrOnOff(settings.enableElectricityBillAlert));
554 updateChannel(grp + CH_ENABLE_PROJECTED_ELECTRICITY_BILL_ALERT,
555 EcobeeUtils.undefOrOnOff(settings.enableProjectedElectricityBillAlert));
556 updateChannel(grp + CH_ELECTRICITY_BILLING_DAY_OF_MONTH,
557 EcobeeUtils.undefOrDecimal(settings.electricityBillingDayOfMonth));
558 updateChannel(grp + CH_ELECTRICITY_BILL_CYCLE_MONTHS,
559 EcobeeUtils.undefOrDecimal(settings.electricityBillCycleMonths));
560 updateChannel(grp + CH_ELECTRICITY_BILL_START_MONTH,
561 EcobeeUtils.undefOrDecimal(settings.electricityBillStartMonth));
562 updateChannel(grp + CH_VENTILATOR_MIN_ON_TIME_HOME,
563 EcobeeUtils.undefOrDecimal(settings.ventilatorMinOnTimeHome));
564 updateChannel(grp + CH_VENTILATOR_MIN_ON_TIME_AWAY,
565 EcobeeUtils.undefOrDecimal(settings.ventilatorMinOnTimeAway));
566 updateChannel(grp + CH_BACKLIGHT_OFF_DURING_SLEEP, EcobeeUtils.undefOrOnOff(settings.backlightOffDuringSleep));
567 updateChannel(grp + CH_AUTO_AWAY, EcobeeUtils.undefOrOnOff(settings.autoAway));
568 updateChannel(grp + CH_SMART_CIRCULATION, EcobeeUtils.undefOrOnOff(settings.smartCirculation));
569 updateChannel(grp + CH_FOLLOW_ME_COMFORT, EcobeeUtils.undefOrOnOff(settings.followMeComfort));
570 updateChannel(grp + CH_VENTILATOR_TYPE, EcobeeUtils.undefOrString(settings.ventilatorType));
571 updateChannel(grp + CH_IS_VENTILATOR_TIMER_ON, EcobeeUtils.undefOrOnOff(settings.isVentilatorTimerOn));
572 updateChannel(grp + CH_VENTILATOR_OFF_DATE_TIME, EcobeeUtils.undefOrString(settings.ventilatorOffDateTime));
573 updateChannel(grp + CH_HAS_UV_FILTER, EcobeeUtils.undefOrOnOff(settings.hasUVFilter));
574 updateChannel(grp + CH_COOLING_LOCKOUT, EcobeeUtils.undefOrOnOff(settings.coolingLockout));
575 updateChannel(grp + CH_VENTILATOR_FREE_COOLING, EcobeeUtils.undefOrOnOff(settings.ventilatorFreeCooling));
576 updateChannel(grp + CH_DEHUMIDIFY_WHEN_HEATING, EcobeeUtils.undefOrOnOff(settings.dehumidifyWhenHeating));
577 updateChannel(grp + CH_VENTILATOR_DEHUMIDIFY, EcobeeUtils.undefOrOnOff(settings.ventilatorDehumidify));
578 updateChannel(grp + CH_GROUP_REF, EcobeeUtils.undefOrString(settings.groupRef));
579 updateChannel(grp + CH_GROUP_NAME, EcobeeUtils.undefOrString(settings.groupName));
580 updateChannel(grp + CH_GROUP_SETTING, EcobeeUtils.undefOrDecimal(settings.groupSetting));
583 private void updateProgram(@Nullable ProgramDTO program) {
584 if (program == null) {
587 final String grp = CHGRP_PROGRAM + "#";
588 updateChannel(grp + CH_PROGRAM_CURRENT_CLIMATE_REF, EcobeeUtils.undefOrString(program.currentClimateRef));
589 if (program.climates != null) {
590 saveValidClimateRefs(program.climates);
594 private void saveValidClimateRefs(List<ClimateDTO> climates) {
595 validClimateRefs.clear();
596 for (ClimateDTO climate : climates) {
597 validClimateRefs.add(climate.climateRef);
601 private void updateAlert(@Nullable List<AlertDTO> alerts) {
603 if (alerts == null || alerts.isEmpty()) {
604 firstAlert = EMPTY_ALERT;
606 firstAlert = alerts.get(0);
608 final String grp = CHGRP_ALERT + "#";
609 updateChannel(grp + CH_ALERT_ACKNOWLEDGE_REF, EcobeeUtils.undefOrString(firstAlert.acknowledgeRef));
610 updateChannel(grp + CH_ALERT_DATE, EcobeeUtils.undefOrString(firstAlert.date));
611 updateChannel(grp + CH_ALERT_TIME, EcobeeUtils.undefOrString(firstAlert.time));
612 updateChannel(grp + CH_ALERT_SEVERITY, EcobeeUtils.undefOrString(firstAlert.severity));
613 updateChannel(grp + CH_ALERT_TEXT, EcobeeUtils.undefOrString(firstAlert.text));
614 updateChannel(grp + CH_ALERT_ALERT_NUMBER, EcobeeUtils.undefOrDecimal(firstAlert.alertNumber));
615 updateChannel(grp + CH_ALERT_ALERT_TYPE, EcobeeUtils.undefOrString(firstAlert.alertType));
616 updateChannel(grp + CH_ALERT_IS_OPERATOR_ALERT, EcobeeUtils.undefOrOnOff(firstAlert.isOperatorAlert));
617 updateChannel(grp + CH_ALERT_REMINDER, EcobeeUtils.undefOrString(firstAlert.reminder));
618 updateChannel(grp + CH_ALERT_SHOW_IDT, EcobeeUtils.undefOrOnOff(firstAlert.showIdt));
619 updateChannel(grp + CH_ALERT_SHOW_WEB, EcobeeUtils.undefOrOnOff(firstAlert.showWeb));
620 updateChannel(grp + CH_ALERT_SEND_EMAIL, EcobeeUtils.undefOrOnOff(firstAlert.sendEmail));
621 updateChannel(grp + CH_ALERT_ACKNOWLEDGEMENT, EcobeeUtils.undefOrString(firstAlert.acknowledgement));
622 updateChannel(grp + CH_ALERT_REMIND_ME_LATER, EcobeeUtils.undefOrOnOff(firstAlert.remindMeLater));
623 updateChannel(grp + CH_ALERT_THERMOSTAT_IDENTIFIER, EcobeeUtils.undefOrString(firstAlert.thermostatIdentifier));
624 updateChannel(grp + CH_ALERT_NOTIFICATION_TYPE, EcobeeUtils.undefOrString(firstAlert.notificationType));
627 private void updateEvent(@Nullable List<EventDTO> events) {
628 EventDTO runningEvent = EMPTY_EVENT;
629 if (events != null && !events.isEmpty()) {
630 for (EventDTO event : events) {
632 runningEvent = event;
637 final String grp = CHGRP_EVENT + "#";
638 updateChannel(grp + CH_EVENT_NAME, EcobeeUtils.undefOrString(runningEvent.name));
639 updateChannel(grp + CH_EVENT_TYPE, EcobeeUtils.undefOrString(runningEvent.type));
640 updateChannel(grp + CH_EVENT_RUNNING, EcobeeUtils.undefOrOnOff(runningEvent.running));
641 updateChannel(grp + CH_EVENT_START_DATE, EcobeeUtils.undefOrString(runningEvent.startDate));
642 updateChannel(grp + CH_EVENT_START_TIME, EcobeeUtils.undefOrString(runningEvent.startTime));
643 updateChannel(grp + CH_EVENT_END_DATE, EcobeeUtils.undefOrString(runningEvent.endDate));
644 updateChannel(grp + CH_EVENT_END_TIME, EcobeeUtils.undefOrString(runningEvent.endTime));
645 updateChannel(grp + CH_EVENT_IS_OCCUPIED, EcobeeUtils.undefOrOnOff(runningEvent.isOccupied));
646 updateChannel(grp + CH_EVENT_IS_COOL_OFF, EcobeeUtils.undefOrOnOff(runningEvent.isCoolOff));
647 updateChannel(grp + CH_EVENT_IS_HEAT_OFF, EcobeeUtils.undefOrOnOff(runningEvent.isHeatOff));
648 updateChannel(grp + CH_EVENT_COOL_HOLD_TEMP, EcobeeUtils.undefOrTemperature(runningEvent.coolHoldTemp));
649 updateChannel(grp + CH_EVENT_HEAT_HOLD_TEMP, EcobeeUtils.undefOrTemperature(runningEvent.heatHoldTemp));
650 updateChannel(grp + CH_EVENT_FAN, EcobeeUtils.undefOrString(runningEvent.fan));
651 updateChannel(grp + CH_EVENT_VENT, EcobeeUtils.undefOrString(runningEvent.vent));
652 updateChannel(grp + CH_EVENT_VENTILATOR_MIN_ON_TIME,
653 EcobeeUtils.undefOrDecimal(runningEvent.ventilatorMinOnTime));
654 updateChannel(grp + CH_EVENT_IS_OPTIONAL, EcobeeUtils.undefOrOnOff(runningEvent.isOptional));
655 updateChannel(grp + CH_EVENT_IS_TEMPERATURE_RELATIVE,
656 EcobeeUtils.undefOrOnOff(runningEvent.isTemperatureRelative));
657 updateChannel(grp + CH_EVENT_COOL_RELATIVE_TEMP, EcobeeUtils.undefOrDecimal(runningEvent.coolRelativeTemp));
658 updateChannel(grp + CH_EVENT_HEAT_RELATIVE_TEMP, EcobeeUtils.undefOrDecimal(runningEvent.heatRelativeTemp));
659 updateChannel(grp + CH_EVENT_IS_TEMPERATURE_ABSOLUTE,
660 EcobeeUtils.undefOrOnOff(runningEvent.isTemperatureAbsolute));
661 updateChannel(grp + CH_EVENT_DUTY_CYCLE_PERCENTAGE,
662 EcobeeUtils.undefOrDecimal(runningEvent.dutyCyclePercentage));
663 updateChannel(grp + CH_EVENT_FAN_MIN_ON_TIME, EcobeeUtils.undefOrDecimal(runningEvent.fanMinOnTime));
664 updateChannel(grp + CH_EVENT_OCCUPIED_SENSOR_ACTIVE,
665 EcobeeUtils.undefOrOnOff(runningEvent.occupiedSensorActive));
666 updateChannel(grp + CH_EVENT_UNOCCUPIED_SENSOR_ACTIVE,
667 EcobeeUtils.undefOrOnOff(runningEvent.unoccupiedSensorActive));
668 updateChannel(grp + CH_EVENT_DR_RAMP_UP_TEMP, EcobeeUtils.undefOrDecimal(runningEvent.drRampUpTemp));
669 updateChannel(grp + CH_EVENT_DR_RAMP_UP_TIME, EcobeeUtils.undefOrDecimal(runningEvent.drRampUpTime));
670 updateChannel(grp + CH_EVENT_LINK_REF, EcobeeUtils.undefOrString(runningEvent.linkRef));
671 updateChannel(grp + CH_EVENT_HOLD_CLIMATE_REF, EcobeeUtils.undefOrString(runningEvent.holdClimateRef));
674 private void updateWeather(@Nullable WeatherDTO weather) {
675 if (weather == null || weather.forecasts == null) {
678 final String weatherGrp = CHGRP_WEATHER + "#";
679 updateChannel(weatherGrp + CH_WEATHER_TIMESTAMP, EcobeeUtils.undefOrDate(weather.timestamp, timeZoneProvider));
680 updateChannel(weatherGrp + CH_WEATHER_WEATHER_STATION, EcobeeUtils.undefOrString(weather.weatherStation));
682 for (int index = 0; index < weather.forecasts.size(); index++) {
683 final String grp = CHGRP_FORECAST + String.format("%d", index) + "#";
684 WeatherForecastDTO forecast = weather.forecasts.get(index);
685 if (forecast != null) {
686 updateChannel(grp + CH_FORECAST_WEATHER_SYMBOL, EcobeeUtils.undefOrDecimal(forecast.weatherSymbol));
687 updateChannel(grp + CH_FORECAST_WEATHER_SYMBOL_TEXT,
688 EcobeeUtils.undefOrString(symbolMap.get(forecast.weatherSymbol)));
689 updateChannel(grp + CH_FORECAST_DATE_TIME,
690 EcobeeUtils.undefOrDate(forecast.dateTime, timeZoneProvider));
691 updateChannel(grp + CH_FORECAST_CONDITION, EcobeeUtils.undefOrString(forecast.condition));
692 updateChannel(grp + CH_FORECAST_TEMPERATURE, EcobeeUtils.undefOrTemperature(forecast.temperature));
693 updateChannel(grp + CH_FORECAST_PRESSURE,
694 EcobeeUtils.undefOrQuantity(forecast.pressure, Units.MILLIBAR));
695 updateChannel(grp + CH_FORECAST_RELATIVE_HUMIDITY,
696 EcobeeUtils.undefOrQuantity(forecast.relativeHumidity, Units.PERCENT));
697 updateChannel(grp + CH_FORECAST_DEWPOINT, EcobeeUtils.undefOrTemperature(forecast.dewpoint));
698 updateChannel(grp + CH_FORECAST_VISIBILITY,
699 EcobeeUtils.undefOrQuantity(forecast.visibility, SIUnits.METRE));
700 updateChannel(grp + CH_FORECAST_WIND_SPEED,
701 EcobeeUtils.undefOrQuantity(forecast.windSpeed, ImperialUnits.MILES_PER_HOUR));
702 updateChannel(grp + CH_FORECAST_WIND_GUST,
703 EcobeeUtils.undefOrQuantity(forecast.windGust, ImperialUnits.MILES_PER_HOUR));
704 updateChannel(grp + CH_FORECAST_WIND_DIRECTION, EcobeeUtils.undefOrString(forecast.windDirection));
705 updateChannel(grp + CH_FORECAST_WIND_BEARING,
706 EcobeeUtils.undefOrQuantity(forecast.windBearing, Units.DEGREE_ANGLE));
707 updateChannel(grp + CH_FORECAST_POP, EcobeeUtils.undefOrQuantity(forecast.pop, Units.PERCENT));
708 updateChannel(grp + CH_FORECAST_TEMP_HIGH, EcobeeUtils.undefOrTemperature(forecast.tempHigh));
709 updateChannel(grp + CH_FORECAST_TEMP_LOW, EcobeeUtils.undefOrTemperature(forecast.tempLow));
710 updateChannel(grp + CH_FORECAST_SKY, EcobeeUtils.undefOrDecimal(forecast.sky));
711 updateChannel(grp + CH_FORECAST_SKY_TEXT, EcobeeUtils.undefOrString(skyMap.get(forecast.sky)));
716 private void updateVersion(@Nullable VersionDTO version) {
717 if (version == null) {
720 final String grp = CHGRP_VERSION + "#";
721 updateChannel(grp + CH_THERMOSTAT_FIRMWARE_VERSION,
722 EcobeeUtils.undefOrString(version.thermostatFirmwareVersion));
725 private void updateLocation(@Nullable LocationDTO loc) {
726 LocationDTO location = EMPTY_LOCATION;
730 final String grp = CHGRP_LOCATION + "#";
731 updateChannel(grp + CH_TIME_ZONE_OFFSET_MINUTES, EcobeeUtils.undefOrDecimal(location.timeZoneOffsetMinutes));
732 updateChannel(grp + CH_TIME_ZONE, EcobeeUtils.undefOrString(location.timeZone));
733 updateChannel(grp + CH_IS_DAYLIGHT_SAVING, EcobeeUtils.undefOrOnOff(location.isDaylightSaving));
734 updateChannel(grp + CH_STREET_ADDRESS, EcobeeUtils.undefOrString(location.streetAddress));
735 updateChannel(grp + CH_CITY, EcobeeUtils.undefOrString(location.city));
736 updateChannel(grp + CH_PROVINCE_STATE, EcobeeUtils.undefOrString(location.provinceState));
737 updateChannel(grp + CH_COUNTRY, EcobeeUtils.undefOrString(location.country));
738 updateChannel(grp + CH_POSTAL_CODE, EcobeeUtils.undefOrString(location.postalCode));
739 updateChannel(grp + CH_PHONE_NUMBER, EcobeeUtils.undefOrString(location.phoneNumber));
740 updateChannel(grp + CH_MAP_COORDINATES, EcobeeUtils.undefOrPoint(location.mapCoordinates));
743 private void updateHouseDetails(@Nullable HouseDetailsDTO hd) {
744 HouseDetailsDTO houseDetails = EMPTY_HOUSEDETAILS;
748 final String grp = CHGRP_HOUSE_DETAILS + "#";
749 updateChannel(grp + CH_HOUSEDETAILS_STYLE, EcobeeUtils.undefOrString(houseDetails.style));
750 updateChannel(grp + CH_HOUSEDETAILS_SIZE, EcobeeUtils.undefOrDecimal(houseDetails.size));
751 updateChannel(grp + CH_HOUSEDETAILS_NUMBER_OF_FLOORS, EcobeeUtils.undefOrDecimal(houseDetails.numberOfFloors));
752 updateChannel(grp + CH_HOUSEDETAILS_NUMBER_OF_ROOMS, EcobeeUtils.undefOrDecimal(houseDetails.numberOfRooms));
753 updateChannel(grp + CH_HOUSEDETAILS_NUMBER_OF_OCCUPANTS,
754 EcobeeUtils.undefOrDecimal(houseDetails.numberOfOccupants));
755 updateChannel(grp + CH_HOUSEDETAILS_AGE, EcobeeUtils.undefOrDecimal(houseDetails.age));
756 updateChannel(grp + CH_HOUSEDETAILS_WINDOW_EFFICIENCY,
757 EcobeeUtils.undefOrDecimal(houseDetails.windowEfficiency));
760 private void updateManagement(@Nullable ManagementDTO mgmt) {
761 ManagementDTO management = EMPTY_MANAGEMENT;
765 final String grp = CHGRP_MANAGEMENT + "#";
766 updateChannel(grp + CH_MANAGEMENT_ADMIN_CONTACT, EcobeeUtils.undefOrString(management.administrativeContact));
767 updateChannel(grp + CH_MANAGEMENT_BILLING_CONTACT, EcobeeUtils.undefOrString(management.billingContact));
768 updateChannel(grp + CH_MANAGEMENT_NAME, EcobeeUtils.undefOrString(management.name));
769 updateChannel(grp + CH_MANAGEMENT_PHONE, EcobeeUtils.undefOrString(management.phone));
770 updateChannel(grp + CH_MANAGEMENT_EMAIL, EcobeeUtils.undefOrString(management.email));
771 updateChannel(grp + CH_MANAGEMENT_WEB, EcobeeUtils.undefOrString(management.web));
772 updateChannel(grp + CH_MANAGEMENT_SHOW_ALERT_IDT, EcobeeUtils.undefOrOnOff(management.showAlertIdt));
773 updateChannel(grp + CH_MANAGEMENT_SHOW_ALERT_WEB, EcobeeUtils.undefOrOnOff(management.showAlertWeb));
776 private void updateTechnician(@Nullable TechnicianDTO tech) {
777 TechnicianDTO technician = EMPTY_TECHNICIAN;
781 final String grp = CHGRP_TECHNICIAN + "#";
782 updateChannel(grp + CH_TECHNICIAN_CONTRACTOR_REF, EcobeeUtils.undefOrString(technician.contractorRef));
783 updateChannel(grp + CH_TECHNICIAN_NAME, EcobeeUtils.undefOrString(technician.name));
784 updateChannel(grp + CH_TECHNICIAN_PHONE, EcobeeUtils.undefOrString(technician.phone));
785 updateChannel(grp + CH_TECHNICIAN_STREET_ADDRESS, EcobeeUtils.undefOrString(technician.streetAddress));
786 updateChannel(grp + CH_TECHNICIAN_CITY, EcobeeUtils.undefOrString(technician.city));
787 updateChannel(grp + CH_TECHNICIAN_PROVINCE_STATE, EcobeeUtils.undefOrString(technician.provinceState));
788 updateChannel(grp + CH_TECHNICIAN_COUNTRY, EcobeeUtils.undefOrString(technician.country));
789 updateChannel(grp + CH_TECHNICIAN_POSTAL_CODE, EcobeeUtils.undefOrString(technician.postalCode));
790 updateChannel(grp + CH_TECHNICIAN_EMAIL, EcobeeUtils.undefOrString(technician.email));
791 updateChannel(grp + CH_TECHNICIAN_WEB, EcobeeUtils.undefOrString(technician.web));
794 private void updateChannel(String channelId, State state) {
795 updateState(channelId, state);
796 stateCache.put(channelId, state);
799 @SuppressWarnings("null")
800 private void updateRemoteSensors(@Nullable List<RemoteSensorDTO> remoteSensors) {
801 if (remoteSensors == null) {
804 logger.debug("ThermostatBridge: Thermostat '{}' has {} remote sensors", thermostatId, remoteSensors.size());
805 for (RemoteSensorDTO sensor : remoteSensors) {
806 EcobeeSensorThingHandler handler = sensorHandlers.get(sensor.id);
807 if (handler != null) {
808 logger.debug("ThermostatBridge: Sending data to sensor handler '{}({})' of type '{}'", sensor.id,
809 sensor.name, sensor.type);
810 handler.updateChannels(sensor);
815 private void performThermostatUpdate(ThermostatDTO thermostat) {
816 SelectionDTO selection = new SelectionDTO();
817 selection.setThermostats(Collections.singleton(thermostatId));
818 ThermostatUpdateRequestDTO request = new ThermostatUpdateRequestDTO(selection);
819 request.thermostat = thermostat;
820 EcobeeAccountBridgeHandler handler = getBridgeHandler();
821 if (handler != null) {
822 handler.performThermostatUpdate(request);
826 private void scheduleDiscoveryJob() {
827 logger.debug("ThermostatBridge: Scheduling sensor discovery job");
828 cancelDiscoveryJob();
829 discoverSensorsJob = scheduler.scheduleWithFixedDelay(this::discoverSensors,
830 SENSOR_DISCOVERY_STARTUP_DELAY_SECONDS, SENSOR_DISCOVERY_INTERVAL_SECONDS, TimeUnit.SECONDS);
833 private void cancelDiscoveryJob() {
834 Future<?> localDiscoverSensorsJob = discoverSensorsJob;
835 if (localDiscoverSensorsJob != null) {
836 localDiscoverSensorsJob.cancel(true);
837 logger.debug("ThermostatBridge: Canceling sensor discovery job");
841 private void discoverSensors() {
842 EcobeeAccountBridgeHandler handler = getBridgeHandler();
843 if (handler != null && handler.isDiscoveryEnabled()) {
844 SensorDiscoveryService localDiscoveryService = discoveryService;
845 if (localDiscoveryService != null) {
846 logger.debug("ThermostatBridge: Running sensor discovery");
847 localDiscoveryService.startBackgroundDiscovery();
852 private @Nullable EcobeeAccountBridgeHandler getBridgeHandler() {
853 EcobeeAccountBridgeHandler handler = null;
854 Bridge bridge = getBridge();
855 if (bridge != null) {
856 handler = (EcobeeAccountBridgeHandler) bridge.getHandler();
861 @SuppressWarnings("null")
862 private boolean isChannelReadOnly(ChannelUID channelUID) {
863 Boolean isReadOnly = channelReadOnlyMap.get(channelUID);
864 return isReadOnly != null ? isReadOnly : true;
867 private void clearSavedState() {
868 savedThermostat = null;
873 private void initializeReadOnlyChannels() {
874 channelReadOnlyMap.clear();
875 for (Channel channel : thing.getChannels()) {
876 ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
877 if (channelTypeUID != null) {
878 ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID, null);
879 if (channelType != null) {
880 channelReadOnlyMap.putIfAbsent(channel.getUID(), channelType.getState().isReadOnly());
886 private void initializeWeatherMaps() {
887 initializeSymbolMap();
891 private void initializeSymbolMap() {
893 symbolMap.put(-2, "NO SYMBOL");
894 symbolMap.put(0, "SUNNY");
895 symbolMap.put(1, "FEW CLOUDS");
896 symbolMap.put(2, "PARTLY CLOUDY");
897 symbolMap.put(3, "MOSTLY CLOUDY");
898 symbolMap.put(4, "OVERCAST");
899 symbolMap.put(5, "DRIZZLE");
900 symbolMap.put(6, "RAIN");
901 symbolMap.put(7, "FREEZING RAIN");
902 symbolMap.put(8, "SHOWERS");
903 symbolMap.put(9, "HAIL");
904 symbolMap.put(10, "SNOW");
905 symbolMap.put(11, "FLURRIES");
906 symbolMap.put(12, "FREEZING SNOW");
907 symbolMap.put(13, "BLIZZARD");
908 symbolMap.put(14, "PELLETS");
909 symbolMap.put(15, "THUNDERSTORM");
910 symbolMap.put(16, "WINDY");
911 symbolMap.put(17, "TORNADO");
912 symbolMap.put(18, "FOG");
913 symbolMap.put(19, "HAZE");
914 symbolMap.put(20, "SMOKE");
915 symbolMap.put(21, "DUST");
918 private void initializeSkyMap() {
920 skyMap.put(1, "SUNNY");
921 skyMap.put(2, "CLEAR");
922 skyMap.put(3, "MOSTLY SUNNY");
923 skyMap.put(4, "MOSTLY CLEAR");
924 skyMap.put(5, "HAZY SUNSHINE");
925 skyMap.put(6, "HAZE");
926 skyMap.put(7, "PASSING CLOUDS");
927 skyMap.put(8, "MORE SUN THAN CLOUDS");
928 skyMap.put(9, "SCATTERED CLOUDS");
929 skyMap.put(10, "PARTLY CLOUDY");
930 skyMap.put(11, "A MIXTURE OF SUN AND CLOUDS");
931 skyMap.put(12, "HIGH LEVEL CLOUDS");
932 skyMap.put(13, "MORE CLOUDS THAN SUN");
933 skyMap.put(14, "PARTLY SUNNY");
934 skyMap.put(15, "BROKEN CLOUDS");
935 skyMap.put(16, "MOSTLY CLOUDY");
936 skyMap.put(17, "CLOUDY");
937 skyMap.put(18, "OVERCAST");
938 skyMap.put(19, "LOW CLOUDS");
939 skyMap.put(20, "LIGHT FOG");
940 skyMap.put(21, "FOG");
941 skyMap.put(22, "DENSE FOG");
942 skyMap.put(23, "ICE FOG");
943 skyMap.put(24, "SANDSTORM");
944 skyMap.put(25, "DUSTSTORM");
945 skyMap.put(26, "INCREASING CLOUDINESS");
946 skyMap.put(27, "DECREASING CLOUDINESS");
947 skyMap.put(28, "CLEARING SKIES");
948 skyMap.put(29, "BREAKS OF SUN LATE");
949 skyMap.put(30, "EARLY FOG FOLLOWED BY SUNNY SKIES");
950 skyMap.put(31, "AFTERNOON CLOUDS");
951 skyMap.put(32, "MORNING CLOUDS");
952 skyMap.put(33, "SMOKE");
953 skyMap.put(34, "LOW LEVEL HAZE");