/* * 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.intent; import android.content.Intent; import android.os.Bundle; import android.os.ResultReceiver; import android.util.Log; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import javax.inject.Inject; import li.klass.fhem.constants.Actions; import li.klass.fhem.constants.BundleExtraKeys; import li.klass.fhem.constants.ResultCodes; import li.klass.fhem.dagger.ApplicationComponent; import li.klass.fhem.domain.GCMSendDevice; import li.klass.fhem.domain.core.DimmableDevice; import li.klass.fhem.domain.core.FhemDevice; import li.klass.fhem.domain.core.ToggleableDevice; import li.klass.fhem.domain.heating.ComfortTempDevice; import li.klass.fhem.domain.heating.DesiredTempDevice; import li.klass.fhem.domain.heating.EcoTempDevice; import li.klass.fhem.domain.heating.HeatingDevice; import li.klass.fhem.domain.heating.WindowOpenTempDevice; import li.klass.fhem.service.Command; import li.klass.fhem.service.CommandExecutionService; import li.klass.fhem.service.NotificationService; import li.klass.fhem.service.device.AtService; import li.klass.fhem.service.device.DeviceService; import li.klass.fhem.service.device.DimmableDeviceService; import li.klass.fhem.service.device.GCMSendDeviceService; import li.klass.fhem.service.device.GenericDeviceService; import li.klass.fhem.service.device.GraphDefinitionsForDeviceService; import li.klass.fhem.service.device.HeatingService; import li.klass.fhem.service.device.ToggleableService; import li.klass.fhem.service.graph.GraphData; import li.klass.fhem.service.graph.GraphService; import li.klass.fhem.service.graph.gplot.SvgGraphDefinition; import li.klass.fhem.service.room.FavoritesService; import li.klass.fhem.service.room.RoomListService; import li.klass.fhem.util.BundleUtil; import li.klass.fhem.util.StateToSet; import static li.klass.fhem.constants.Actions.DEVICE_DELETE; import static li.klass.fhem.constants.Actions.DEVICE_DIM; import static li.klass.fhem.constants.Actions.DEVICE_GRAPH; import static li.klass.fhem.constants.Actions.DEVICE_GRAPH_DEFINITIONS; import static li.klass.fhem.constants.Actions.DEVICE_MOVE_ROOM; import static li.klass.fhem.constants.Actions.DEVICE_RENAME; import static li.klass.fhem.constants.Actions.DEVICE_RESET_WEEK_PROFILE; import static li.klass.fhem.constants.Actions.DEVICE_SET_ALIAS; import static li.klass.fhem.constants.Actions.DEVICE_SET_COMFORT_TEMPERATURE; import static li.klass.fhem.constants.Actions.DEVICE_SET_DESIRED_TEMPERATURE; import static li.klass.fhem.constants.Actions.DEVICE_SET_ECO_TEMPERATURE; import static li.klass.fhem.constants.Actions.DEVICE_SET_MODE; import static li.klass.fhem.constants.Actions.DEVICE_SET_STATE; import static li.klass.fhem.constants.Actions.DEVICE_SET_SUB_STATE; import static li.klass.fhem.constants.Actions.DEVICE_SET_SUB_STATES; import static li.klass.fhem.constants.Actions.DEVICE_SET_WEEK_PROFILE; import static li.klass.fhem.constants.Actions.DEVICE_SET_WINDOW_OPEN_TEMPERATURE; import static li.klass.fhem.constants.Actions.DEVICE_TIMER_MODIFY; import static li.klass.fhem.constants.Actions.DEVICE_TIMER_NEW; import static li.klass.fhem.constants.Actions.DEVICE_TOGGLE_STATE; import static li.klass.fhem.constants.Actions.DEVICE_WIDGET_TOGGLE; import static li.klass.fhem.constants.Actions.GCM_ADD_SELF; import static li.klass.fhem.constants.Actions.GCM_REMOVE_ID; import static li.klass.fhem.constants.Actions.RESEND_LAST_FAILED_COMMAND; import static li.klass.fhem.constants.BundleExtraKeys.CONNECTION_ID; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_DIM_PROGRESS; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_GRAPH_DEFINITION; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_GRAPH_ENTRY_MAP; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_MODE; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_NAME; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_NEW_ALIAS; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_NEW_NAME; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_NEW_ROOM; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_TARGET_STATE; import static li.klass.fhem.constants.BundleExtraKeys.DEVICE_TEMPERATURE; import static li.klass.fhem.constants.BundleExtraKeys.DO_REFRESH; import static li.klass.fhem.constants.BundleExtraKeys.END_DATE; import static li.klass.fhem.constants.BundleExtraKeys.GCM_REGISTRATION_ID; import static li.klass.fhem.constants.BundleExtraKeys.START_DATE; import static li.klass.fhem.constants.BundleExtraKeys.STATES; import static li.klass.fhem.constants.BundleExtraKeys.STATE_NAME; import static li.klass.fhem.constants.BundleExtraKeys.STATE_VALUE; import static li.klass.fhem.constants.BundleExtraKeys.TIMER_HOUR; import static li.klass.fhem.constants.BundleExtraKeys.TIMER_IS_ACTIVE; import static li.klass.fhem.constants.BundleExtraKeys.TIMER_MINUTE; import static li.klass.fhem.constants.BundleExtraKeys.TIMER_REPETITION; import static li.klass.fhem.constants.BundleExtraKeys.TIMER_SECOND; import static li.klass.fhem.constants.BundleExtraKeys.TIMER_TARGET_DEVICE_NAME; import static li.klass.fhem.constants.BundleExtraKeys.TIMER_TARGET_STATE; import static li.klass.fhem.constants.BundleExtraKeys.TIMER_TARGET_STATE_APPENDIX; import static li.klass.fhem.constants.BundleExtraKeys.TIMER_TYPE; import static li.klass.fhem.constants.BundleExtraKeys.TIMES_TO_SEND; import static li.klass.fhem.service.intent.ConvenientIntentService.State.DONE; import static li.klass.fhem.service.intent.ConvenientIntentService.State.ERROR; import static li.klass.fhem.service.intent.ConvenientIntentService.State.SUCCESS; import static li.klass.fhem.service.room.RoomListService.RemoteUpdateRequired.REQUIRED; public class DeviceIntentService extends ConvenientIntentService { @Inject RoomListService roomListService; @Inject HeatingService heatingService; @Inject DeviceService deviceService; @Inject FavoritesService favoritesService; @Inject GenericDeviceService genericDeviceService; @Inject GCMSendDeviceService gcmSendDeviceService; @Inject CommandExecutionService commandExecutionService; @Inject AtService atService; @Inject DimmableDeviceService dimmableDeviceService; @Inject ToggleableService toggleableService; @Inject GraphService graphService; @Inject NotificationService notificationService; @Inject GraphDefinitionsForDeviceService graphDefinitionsForDeviceService; private static final Logger LOG = LoggerFactory.getLogger(DeviceIntentService.class); public DeviceIntentService() { super(DeviceIntentService.class.getName()); } @Override protected State handleIntent(Intent intent, long updatePeriod, ResultReceiver resultReceiver) { if (roomListService.updateRoomDeviceListIfRequired(intent, updatePeriod, this) == REQUIRED) { return DONE; } String deviceName = intent.getStringExtra(DEVICE_NAME); Optional<String> connectionId = Optional.fromNullable(intent.getStringExtra(CONNECTION_ID)); Optional<FhemDevice> deviceOptional = roomListService.getDeviceForName(deviceName, connectionId, this); if (!deviceOptional.isPresent()) { LOG.info("handleIntent() - cannot find device for {}", deviceName); } FhemDevice device = deviceOptional.orNull(); Log.d(DeviceIntentService.class.getName(), intent.getAction()); String action = intent.getAction(); State result = State.SUCCESS; if (DEVICE_GRAPH.equals(action)) { result = graphIntent(intent, device, resultReceiver); } else if (DEVICE_TOGGLE_STATE.equals(action)) { result = toggleIntent(device, connectionId); } else if (DEVICE_SET_STATE.equals(action)) { result = setStateIntent(intent, device, connectionId); } else if (DEVICE_DIM.equals(action)) { result = dimIntent(intent, device); } else if (DEVICE_SET_MODE.equals(action)) { if (device instanceof HeatingDevice) { Enum mode = (Enum) intent.getSerializableExtra(DEVICE_MODE); HeatingDevice heatingDevice = (HeatingDevice) device; heatingService.setMode(heatingDevice, mode, this); } } else if (DEVICE_SET_WEEK_PROFILE.equals(action)) { if (!(device instanceof HeatingDevice)) return ERROR; heatingService.setWeekProfileFor((HeatingDevice) device, this); } else if (DEVICE_SET_WINDOW_OPEN_TEMPERATURE.equals(action)) { if (!(device instanceof WindowOpenTempDevice)) return SUCCESS; double temperature = intent.getDoubleExtra(DEVICE_TEMPERATURE, -1); heatingService.setWindowOpenTemp((WindowOpenTempDevice) device, temperature, this); } else if (DEVICE_SET_DESIRED_TEMPERATURE.equals(action)) { double temperature = intent.getDoubleExtra(DEVICE_TEMPERATURE, -1); if (device instanceof DesiredTempDevice) { heatingService.setDesiredTemperature((DesiredTempDevice) device, temperature, this); } } else if (DEVICE_SET_COMFORT_TEMPERATURE.equals(action)) { double temperature = intent.getDoubleExtra(DEVICE_TEMPERATURE, -1); if (device instanceof DesiredTempDevice) { heatingService.setComfortTemperature((ComfortTempDevice) device, temperature, this); } } else if (DEVICE_SET_ECO_TEMPERATURE.equals(action)) { double temperature = intent.getDoubleExtra(DEVICE_TEMPERATURE, -1); if (device instanceof DesiredTempDevice) { heatingService.setEcoTemperature((EcoTempDevice) device, temperature, this); } } else if (DEVICE_RESET_WEEK_PROFILE.equals(action)) { if (!(device instanceof HeatingDevice)) return ERROR; heatingService.resetWeekProfile((HeatingDevice) device); } else if (DEVICE_RENAME.equals(action)) { String newName = intent.getStringExtra(DEVICE_NEW_NAME); deviceService.renameDevice(device, newName, this); notificationService.rename(deviceName, newName, this); favoritesService.removeFavorite(getBaseContext(), deviceName); favoritesService.addFavorite(getBaseContext(), newName); } else if (DEVICE_DELETE.equals(action)) { deviceService.deleteDevice(device, this); } else if (DEVICE_MOVE_ROOM.equals(action)) { String newRoom = intent.getStringExtra(DEVICE_NEW_ROOM); deviceService.moveDevice(device, newRoom, this); } else if (DEVICE_SET_ALIAS.equals(action)) { String newAlias = intent.getStringExtra(DEVICE_NEW_ALIAS); deviceService.setAlias(device, newAlias, this); } else if (DEVICE_WIDGET_TOGGLE.equals(action)) { result = toggleIntent(device, connectionId); } else if (DEVICE_TIMER_MODIFY.equals(action)) { processTimerIntent(intent, true); } else if (DEVICE_TIMER_NEW.equals(action)) { processTimerIntent(intent, false); } else if (DEVICE_SET_SUB_STATE.equals(action)) { String name = intent.getStringExtra(STATE_NAME); String value = intent.getStringExtra(STATE_VALUE); genericDeviceService.setSubState(device, name, value, connectionId, this, true); } else if (DEVICE_SET_SUB_STATES.equals(action)) { @SuppressWarnings("unchecked") List<StateToSet> statesToSet = (List<StateToSet>) intent.getSerializableExtra(STATES); genericDeviceService.setSubStates(device, statesToSet, connectionId, this); } else if (GCM_ADD_SELF.equals(action)) { gcmSendDeviceService.addSelf((GCMSendDevice) device, this); } else if (GCM_REMOVE_ID.equals(action)) { String registrationId = intent.getStringExtra(GCM_REGISTRATION_ID); gcmSendDeviceService.removeRegistrationId((GCMSendDevice) device, registrationId, this); } else if (RESEND_LAST_FAILED_COMMAND.equals(action)) { Command lastFailedCommand = commandExecutionService.getLastFailedCommand(); if (lastFailedCommand != null && "xmllist".equalsIgnoreCase(lastFailedCommand.getCommand())) { sendBroadcast(new Intent(Actions.DO_UPDATE) .putExtra(DO_REFRESH, true)); } else { commandExecutionService.resendLastFailedCommand(this); } } else if (DEVICE_GRAPH_DEFINITIONS.equals(action) && device != null) { ImmutableSet<SvgGraphDefinition> definitions = graphDefinitionsForDeviceService.graphDefinitionsFor(this, device.getXmlListDevice(), connectionId); sendSingleExtraResult(resultReceiver, ResultCodes.SUCCESS, DEVICE_GRAPH_DEFINITION, definitions); return State.DONE; } return result; } /** * Find out graph data for a given device and notify the result receiver with the read graph data * * @param intent received intent * @param device device to read the graph data * @param resultReceiver receiver to notify on result * @return success? */ private State graphIntent(Intent intent, FhemDevice device, ResultReceiver resultReceiver) { SvgGraphDefinition graphDefinition = (SvgGraphDefinition) intent.getSerializableExtra(DEVICE_GRAPH_DEFINITION); DateTime startDate = (DateTime) intent.getSerializableExtra(START_DATE); DateTime endDate = (DateTime) intent.getSerializableExtra(END_DATE); Optional<String> connectionId = Optional.fromNullable(intent.getStringExtra(BundleExtraKeys.CONNECTION_ID)); GraphData graphData = graphService.getGraphData(device, connectionId, graphDefinition, startDate, endDate, this); sendResult(resultReceiver, ResultCodes.SUCCESS, BundleUtil.mapToBundle( ImmutableMap.of(DEVICE_GRAPH_ENTRY_MAP, graphData.getData(), START_DATE, graphData.getInterval().getStart(), END_DATE, graphData.getInterval().getEnd()) )); return State.DONE; } /** * Toggle a device and notify the result receiver * * @param device device to toggle * @param connectionId connection ID * @return success? */ private State toggleIntent(FhemDevice device, Optional<String> connectionId) { if (device instanceof ToggleableDevice && ((ToggleableDevice) device).supportsToggle()) { toggleableService.toggleState((ToggleableDevice) device, connectionId, this); return SUCCESS; } else { return ERROR; } } /** * Set the state of a device and notify the result receiver * * @param intent received intent * @param device device to set the state on * @param connectionId * @return success ? */ private State setStateIntent(Intent intent, FhemDevice device, Optional<String> connectionId) { String targetState = intent.getStringExtra(DEVICE_TARGET_STATE); int timesToSend = intent.getIntExtra(TIMES_TO_SEND, 1); for (int i = 0; i < timesToSend; i++) { genericDeviceService.setState(device, targetState, connectionId, this); } return State.SUCCESS; } /** * Dim a device and notify the result receiver * * @param intent received intent * @param device device to dim * @return success? */ private State dimIntent(Intent intent, FhemDevice device) { float dimProgress = intent.getFloatExtra(DEVICE_DIM_PROGRESS, -1); if (device instanceof DimmableDevice) { dimmableDeviceService.dim((DimmableDevice) device, dimProgress, this); return State.SUCCESS; } return State.ERROR; } @SuppressWarnings("ConstantConditions") private State processTimerIntent(Intent intent, boolean isModify) { Bundle extras = intent.getExtras(); assert extras != null; String targetDeviceName = extras.getString(TIMER_TARGET_DEVICE_NAME); String targetState = extras.getString(TIMER_TARGET_STATE); int hour = extras.getInt(TIMER_HOUR, 0); int minute = extras.getInt(TIMER_MINUTE, 0); int second = extras.getInt(TIMER_SECOND, 0); String repetition = extras.getString(TIMER_REPETITION); String type = extras.getString(TIMER_TYPE); String stateAppendix = extras.getString(TIMER_TARGET_STATE_APPENDIX); String timerName = extras.getString(DEVICE_NAME); boolean isActive = extras.getBoolean(TIMER_IS_ACTIVE); if (isModify) { atService.modify(timerName, hour, minute, second, repetition, type, targetDeviceName, targetState, stateAppendix, isActive, this); } else { atService.createNew(timerName, hour, minute, second, repetition, type, targetDeviceName, targetState, stateAppendix, isActive, this); } return SUCCESS; } @Override protected void inject(ApplicationComponent applicationComponent) { applicationComponent.inject(this); } }