Java tutorial
/** * Copyright (c) 2010-2019 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 */ package org.openhab.binding.sensebox.internal.handler; import static org.eclipse.smarthome.core.library.unit.MetricPrefix.HECTO; import static org.openhab.binding.sensebox.internal.SenseBoxBindingConstants.*; import java.math.BigDecimal; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.eclipse.smarthome.core.cache.ExpiringCacheMap; import org.eclipse.smarthome.core.library.types.DateTimeType; import org.eclipse.smarthome.core.library.types.DecimalType; import org.eclipse.smarthome.core.library.types.PointType; import org.eclipse.smarthome.core.library.types.QuantityType; import org.eclipse.smarthome.core.library.unit.SIUnits; import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingStatusDetail; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.RefreshType; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.UnDefType; import org.openhab.binding.sensebox.internal.SenseBoxAPIConnection; import org.openhab.binding.sensebox.internal.config.SenseBoxConfiguration; import org.openhab.binding.sensebox.internal.model.SenseBoxData; import org.openhab.binding.sensebox.internal.model.SenseBoxLocation; import org.openhab.binding.sensebox.internal.model.SenseBoxSensor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The {@link SenseBoxHandler} is responsible for handling commands, which are * sent to one of the channels. * * @author Hakan Tandogan - Initial contribution * @author Hakan Tandogan - Ignore incorrect data for brightness readings * @author Hakan Tandogan - Changed use of caching utils to ESH ExpiringCacheMap * @author Hakan Tandogan - Unit of Measurement support */ public class SenseBoxHandler extends BaseThingHandler { private Logger logger = LoggerFactory.getLogger(SenseBoxHandler.class); protected SenseBoxConfiguration thingConfiguration; private SenseBoxData data = new SenseBoxData(); ScheduledFuture<?> refreshJob; private static final BigDecimal ONEHUNDRED = BigDecimal.valueOf(100l); private static final String CACHE_KEY_DATA = "DATA"; private final ExpiringCacheMap<String, SenseBoxData> cache = new ExpiringCacheMap<String, SenseBoxData>( CACHE_EXPIRY); private final SenseBoxAPIConnection connection = new SenseBoxAPIConnection(); public SenseBoxHandler(Thing thing) { super(thing); } @Override public void initialize() { logger.debug("Start initializing!"); thingConfiguration = getConfigAs(SenseBoxConfiguration.class); String senseBoxId = thingConfiguration.getSenseBoxId(); logger.debug("Thing Configuration {} initialized {}", getThing().getUID().toString(), senseBoxId); String offlineReason = ""; boolean validConfig = true; if (StringUtils.trimToNull(senseBoxId) == null) { offlineReason = "senseBox ID is mandatory and must be configured"; validConfig = false; } if (thingConfiguration.getRefreshInterval() < MINIMUM_UPDATE_INTERVAL) { logger.info("Refresh interval is much too small, setting to default of {} seconds", MINIMUM_UPDATE_INTERVAL); thingConfiguration.setRefreshInterval(MINIMUM_UPDATE_INTERVAL); } if (validConfig) { updateStatus(ThingStatus.UNKNOWN); startAutomaticRefresh(); } else { updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR, offlineReason); } cache.put(CACHE_KEY_DATA, () -> { return connection.reallyFetchDataFromServer(senseBoxId); }); logger.debug("Thing {} initialized {}", getThing().getUID(), getThing().getStatus()); } @Override public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof RefreshType) { data = fetchData(); if (ThingStatus.ONLINE == data.getStatus()) { publishDataForChannel(channelUID.getId()); updateStatus(ThingStatus.ONLINE); } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } } else { logger.debug("Unsupported command {}! Supported commands: REFRESH", command); } } @Override public void dispose() { if (refreshJob != null) { refreshJob.cancel(true); } } private void startAutomaticRefresh() { Runnable runnable = () -> { logger.debug("Refreshing data for box {}, scheduled after {} seconds...", thingConfiguration.getSenseBoxId(), thingConfiguration.getRefreshInterval()); data = fetchData(); if (ThingStatus.ONLINE == data.getStatus()) { publishProperties(); publishDataForChannel(CHANNEL_LOCATION); publishDataForChannel(CHANNEL_UV_INTENSITY); publishDataForChannel(CHANNEL_ILLUMINANCE); publishDataForChannel(CHANNEL_PRESSURE); publishDataForChannel(CHANNEL_HUMIDITY); publishDataForChannel(CHANNEL_TEMPERATURE); publishDataForChannel(CHANNEL_PARTICULATE_MATTER_2_5); publishDataForChannel(CHANNEL_PARTICULATE_MATTER_10); publishDataForChannel(CHANNEL_UV_INTENSITY_LR); publishDataForChannel(CHANNEL_ILLUMINANCE_LR); publishDataForChannel(CHANNEL_PRESSURE_LR); publishDataForChannel(CHANNEL_HUMIDITY_LR); publishDataForChannel(CHANNEL_TEMPERATURE_LR); publishDataForChannel(CHANNEL_PARTICULATE_MATTER_2_5_LR); publishDataForChannel(CHANNEL_PARTICULATE_MATTER_10_LR); updateStatus(ThingStatus.ONLINE); } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } }; refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, thingConfiguration.getRefreshInterval(), TimeUnit.SECONDS); } private SenseBoxData fetchData() { return cache.get(CACHE_KEY_DATA); } private void publishProperties() { thing.setProperty(PROPERTY_NAME, data.getName()); thing.setProperty(PROPERTY_EXPOSURE, data.getExposure()); thing.setProperty(PROPERTY_IMAGE_URL, data.getDescriptor().getImageUrl()); thing.setProperty(PROPERTY_MAP_URL, data.getDescriptor().getMapUrl()); } private void publishDataForChannel(String channelID) { switch (channelID) { case CHANNEL_LOCATION: updateState(CHANNEL_LOCATION, locationFromData(data.getLocation())); break; case CHANNEL_UV_INTENSITY: updateState(CHANNEL_UV_INTENSITY, decimalFromSensor(data.getUvIntensity())); break; case CHANNEL_ILLUMINANCE: updateState(CHANNEL_ILLUMINANCE, decimalFromSensor(data.getLuminance())); break; case CHANNEL_PRESSURE: updateState(CHANNEL_PRESSURE, decimalFromSensor(data.getPressure())); break; case CHANNEL_HUMIDITY: updateState(CHANNEL_HUMIDITY, decimalFromSensor(data.getHumidity())); break; case CHANNEL_TEMPERATURE: updateState(CHANNEL_TEMPERATURE, decimalFromSensor(data.getTemperature())); break; case CHANNEL_PARTICULATE_MATTER_2_5: updateState(CHANNEL_PARTICULATE_MATTER_2_5, decimalFromSensor(data.getParticulateMatter2dot5())); break; case CHANNEL_PARTICULATE_MATTER_10: updateState(CHANNEL_PARTICULATE_MATTER_10, decimalFromSensor(data.getParticulateMatter10())); break; case CHANNEL_UV_INTENSITY_LR: updateState(CHANNEL_UV_INTENSITY_LR, dateTimeFromSensor(data.getUvIntensity())); break; case CHANNEL_ILLUMINANCE_LR: updateState(CHANNEL_ILLUMINANCE_LR, dateTimeFromSensor(data.getLuminance())); break; case CHANNEL_PRESSURE_LR: updateState(CHANNEL_PRESSURE_LR, dateTimeFromSensor(data.getPressure())); break; case CHANNEL_HUMIDITY_LR: updateState(CHANNEL_HUMIDITY_LR, dateTimeFromSensor(data.getHumidity())); break; case CHANNEL_TEMPERATURE_LR: updateState(CHANNEL_TEMPERATURE_LR, dateTimeFromSensor(data.getTemperature())); break; case CHANNEL_PARTICULATE_MATTER_2_5_LR: updateState(CHANNEL_PARTICULATE_MATTER_2_5_LR, dateTimeFromSensor(data.getParticulateMatter2dot5())); break; case CHANNEL_PARTICULATE_MATTER_10_LR: updateState(CHANNEL_PARTICULATE_MATTER_10_LR, dateTimeFromSensor(data.getParticulateMatter10())); break; default: logger.debug("Command received for an unknown channel: {}", channelID); break; } } private State dateTimeFromSensor(SenseBoxSensor data) { State result = UnDefType.UNDEF; if (data != null) { if (data.getLastMeasurement() != null) { if (StringUtils.isNotEmpty(data.getLastMeasurement().getCreatedAt())) { result = new DateTimeType(data.getLastMeasurement().getCreatedAt()); } } } return result; } private State decimalFromSensor(SenseBoxSensor data) { State result = UnDefType.UNDEF; if (data != null) { if (data.getLastMeasurement() != null) { if (StringUtils.isNotEmpty(data.getLastMeasurement().getValue())) { logger.debug("About to determine quantity for {} / {}", data.getLastMeasurement().getValue(), data.getUnit()); BigDecimal bd = new BigDecimal(data.getLastMeasurement().getValue()); switch (data.getUnit()) { case "%": result = new QuantityType<>(bd, SmartHomeUnits.ONE); break; case "C": result = new QuantityType<>(bd, SIUnits.CELSIUS); break; case "Pa": result = new QuantityType<>(bd, SIUnits.PASCAL); break; case "hPa": if (BigDecimal.valueOf(10000l).compareTo(bd) < 0) { // Some stations report measurements in Pascal, but send 'hPa' as units... bd = bd.divide(ONEHUNDRED); } result = new QuantityType<>(bd, HECTO(SIUnits.PASCAL)); break; case "lx": result = new QuantityType<>(bd, SmartHomeUnits.LUX); break; case "\u00b5g/m": result = new QuantityType<>(bd, SmartHomeUnits.MICROGRAM_PER_CUBICMETRE); break; case "\u00b5W/cm": result = new QuantityType<>(bd, SmartHomeUnits.MICROWATT_PER_SQUARE_CENTIMETRE); break; default: // The data provider might have configured some unknown unit, accept at least the measurement logger.debug("Could not determine unit for '{}', using default", data.getUnit()); result = new QuantityType<>(bd, SmartHomeUnits.ONE); } logger.debug("State: '{}'", result); } } } return result; } private State locationFromData(SenseBoxLocation data) { State result = UnDefType.UNDEF; if (data != null) { result = new PointType(new DecimalType(data.getLatitude()), new DecimalType(data.getLongitude()), new DecimalType(data.getHeight())); } return result; } }