/* * AndFHEM - Open Source Android application to control a FHEM home automation * server. * * Copyright (c) 2011, Matthias Klass or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU GENERAL PUBLIC LICENSE, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU GENERAL PUBLIC LICENSE * for more details. * * You should have received a copy of the GNU GENERAL PUBLIC LICENSE * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package li.klass.fhem.service.deviceConfiguration; import android.support.annotation.NonNull; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.io.Resources; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; import li.klass.fhem.domain.core.DeviceFunctionality; import li.klass.fhem.domain.core.FhemDevice; import li.klass.fhem.service.room.xmllist.XmlListDevice; import static com.google.common.collect.Maps.newHashMap; import static com.google.common.collect.Sets.newHashSet; import static java.util.Collections.emptySet; import static org.apache.commons.lang3.StringUtils.trimToNull; @Singleton public class DeviceConfigurationProvider { public static final String SUPPORTED_WIDGETS = "supportedWidgets"; public static final String DELAY_FOR_UPDATE_AFTER_COMMAND = "delayForUpdateAfterCommand"; public static final String DEFAULT_GROUP = "defaultGroup"; public static final String SENSOR_DEVICE = "sensorDevice"; public static final String STATES = "states"; public static final String SHOW_IN_OVERVIEW = "showInOverview"; public static final String SHOW_DELAY_NOTIFICATION_ON_SWITCH = "showDelayNotificationOnSwitch"; public static final String DESC = "desc"; public static final String KEY = "key"; public static final String MARKERS = "markers"; public static final String SANITISE_KEY = "sanitise"; private static final String SHOW_STATE_IN_OVERVIEW = "showStateInOverview"; private static final String SHOW_MEASURED_IN_OVERVIEW = "showMeasuredInOverview"; public static final String SHOW_AFTER = "showAfter"; private static final String ATTRIBUTES = "attributes"; private static final String INTERNALS = "internals"; public static final String DEFAULTS_CONFIGURATION = "defaults"; private final JSONObject options; private static final Logger LOGGER = LoggerFactory.getLogger(DeviceConfigurationProvider.class); @Inject public DeviceConfigurationProvider() { try { options = new JSONObject(Resources.toString(Resources.getResource(DeviceConfigurationProvider.class, "deviceConfiguration.json"), Charsets.UTF_8)); } catch (Exception e) { throw new RuntimeException(e); } } public Optional<JSONObject> plainConfigurationFor(XmlListDevice device) { return plainConfigurationFor(device.getType()); } public Optional<JSONObject> plainConfigurationFor(String type) { JSONObject deviceConfig = options.optJSONObject(type); return deviceConfig == null ? Optional.<JSONObject>absent() : Optional.of(deviceConfig); } public Optional<DeviceConfiguration> configurationFor(FhemDevice<?> device) { return configurationFor(device.getXmlListDevice()); } public Optional<DeviceConfiguration> configurationFor(XmlListDevice device) { return configurationFor(device.getType()); } public Optional<JSONObject> sanitiseConfigurationFor(String type) { try { JSONObject typeConfiguration = plainConfigurationFor(type).or(new JSONObject()); JSONObject typeSanitiseConfig = Optional.fromNullable(typeConfiguration.optJSONObject(SANITISE_KEY)).or(new JSONObject()); JSONObject defaultsConfiguration = plainConfigurationFor(DEFAULTS_CONFIGURATION).or(new JSONObject()); JSONObject defaultsSanitiseConfiguration = Optional.fromNullable(defaultsConfiguration.optJSONObject(SANITISE_KEY)).or(new JSONObject()); Iterator<String> keyIterator = defaultsSanitiseConfiguration.keys(); while (keyIterator.hasNext()) { String key = keyIterator.next(); if (!typeSanitiseConfig.has(key)) { typeSanitiseConfig.put(key, defaultsSanitiseConfiguration.optJSONObject(key)); } } return Optional.of(typeSanitiseConfig); } catch (JSONException e) { LOGGER.error("sanitiseConfigurationFor(" + type + ")", e); return Optional.absent(); } } public Optional<DeviceConfiguration> configurationFor(String type) { Optional<JSONObject> configOpt = plainConfigurationFor(type); if (!configOpt.isPresent()) { return Optional.absent(); } JSONObject jsonObject = configOpt.get(); String defaultGroupValue = trimToNull(jsonObject.optString(DEFAULT_GROUP)); DeviceFunctionality defaultGroup = defaultGroupValue != null ? DeviceFunctionality.valueOf(defaultGroupValue) : DeviceFunctionality.UNKNOWN; DeviceConfiguration.Builder builder = new DeviceConfiguration.Builder() .withSensorDevice(jsonObject.optBoolean(SENSOR_DEVICE, false)) .withDefaultGroup(defaultGroup) .withSupportedWidgets(transformStringJsonArray(jsonObject.optJSONArray(SUPPORTED_WIDGETS))) .withShowStateInOverview(jsonObject.optBoolean(SHOW_STATE_IN_OVERVIEW, true)) .withShowMeasuredInOverview(jsonObject.optBoolean(SHOW_MEASURED_IN_OVERVIEW, true)) .withDelayForUpdateAfterCommand(jsonObject.optInt(DELAY_FOR_UPDATE_AFTER_COMMAND, 0)); Optional<JSONObject> defaults = plainConfigurationFor(DEFAULTS_CONFIGURATION); if (defaults.isPresent()) { addFields(defaults.get(), builder); } addFields(jsonObject, builder); return Optional.of(builder.build()); } private void addFields(JSONObject jsonObject, DeviceConfiguration.Builder builder) { addStates(jsonObject, builder); addAttributes(jsonObject, builder); addInternals(jsonObject, builder); } private void addAttributes(JSONObject jsonObject, DeviceConfiguration.Builder builder) { JSONArray attributes = jsonObject.optJSONArray(ATTRIBUTES); if (attributes != null) { for (int i = 0; i < attributes.length(); i++) { JSONObject attribute = attributes.optJSONObject(i); builder.withAttribute(viewItemConfigFor(attribute)); } } } private void addStates(JSONObject jsonObject, DeviceConfiguration.Builder builder) { JSONArray states = jsonObject.optJSONArray(STATES); if (states != null) { for (int i = 0; i < states.length(); i++) { JSONObject state = states.optJSONObject(i); builder.withState(viewItemConfigFor(state)); String stateKey = state.optString(KEY); JSONArray beforeCommandReplace = state.optJSONArray("beforeCommandReplace"); if (beforeCommandReplace != null) { Map<String, String> commandReplace = handleCommandReplace(beforeCommandReplace); builder.withCommandReplace(stateKey, commandReplace); } String subStateReplace = state.optString("subStateReplace"); if (subStateReplace != null) { builder.withSubStateReplace(stateKey, subStateReplace); } } } } @NonNull private Map<String, String> handleCommandReplace(JSONArray beforeCommandReplace) { Map<String, String> commandReplace = newHashMap(); for (int j = 0; j < beforeCommandReplace.length(); j++) { try { JSONObject toReplace = beforeCommandReplace.getJSONObject(j); commandReplace.put(toReplace.getString("search"), toReplace.getString("replaceBy")); } catch (Exception e) { LOGGER.error("handleCommandReplace() - cannot read device configuration", e); } } return commandReplace; } private void addInternals(JSONObject jsonObject, DeviceConfiguration.Builder builder) { JSONArray internals = jsonObject.optJSONArray(INTERNALS); if (internals != null) { for (int i = 0; i < internals.length(); i++) { JSONObject internal = internals.optJSONObject(i); builder.withInternal(viewItemConfigFor(internal)); } } } @NonNull private ViewItemConfig viewItemConfigFor(JSONObject jsonConfig) { return new ViewItemConfig.Builder() .withKey(jsonConfig.optString(KEY)) .withDesc(jsonConfig.optString(DESC)) .withShowAfter(jsonConfig.optString(SHOW_AFTER)) .withShowInOverview(jsonConfig.optBoolean(SHOW_IN_OVERVIEW, false)) .withShowInDetail(jsonConfig.optBoolean("showInDetail", true)) .withMarkers(transformStringJsonArray(jsonConfig.optJSONArray(MARKERS))) .withShowDelayNotificationOnSwitch(jsonConfig.optBoolean(SHOW_DELAY_NOTIFICATION_ON_SWITCH)) .build(); } private Set<String> transformStringJsonArray(JSONArray array) { if (array == null) return emptySet(); Set<String> markersResult = newHashSet(); for (int i = 0; i < array.length(); i++) { markersResult.add(array.optString(i)); } return markersResult; } public boolean isSensorDevice(XmlListDevice xmlListDevice) { Optional<JSONObject> configOpt = plainConfigurationFor(xmlListDevice); return configOpt.isPresent() && configOpt.get().optBoolean(SENSOR_DEVICE, false); } }