package com.homesnap.engine.controller; /* * #%L * HomeSnapEngine * %% * Copyright (C) 2011 - 2016 A. de Giuli * %% * This file is part of HomeSnap done by Arnaud de Giuli (arnaud.degiuli(at)free.fr) * helped by Olivier Driesbach (olivier.driesbach(at)gmail.com). * * HomeSnap is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HomeSnap 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 HomeSnap. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.json.JSONException; import org.json.JSONObject; import com.homesnap.engine.JsonSerializable; import com.homesnap.engine.connector.Command; import com.homesnap.engine.connector.Command.Type; import com.homesnap.engine.connector.CommandListener; import com.homesnap.engine.connector.CommandResult; import com.homesnap.engine.connector.CommandResultStatus; import com.homesnap.engine.connector.Commander; import com.homesnap.engine.connector.DefaultCommandResult; import com.homesnap.engine.connector.Monitor; import com.homesnap.engine.controller.what.State; import com.homesnap.engine.controller.what.What; import com.homesnap.engine.controller.where.Where; import com.homesnap.engine.controller.who.Who; /** * Controller is the abstract class for all device supporter by SnapHome. * Each sub class represent a device. Controller contains the main logic to manage status. * */ public abstract class Controller implements JsonSerializable, Serializable { /** serial uid */ private static final long serialVersionUID = 1L; private boolean waitingResult = false; protected Where where; // Represent the address of the controller private String title; // string representing the controller private String description; // string describing the controller protected transient Commander server; private List<ControllerChangeListener> controllerChangeListenerList = new ArrayList<ControllerChangeListener>(); private LabelList labelList = new LabelList(this); /** List of all state names with their current values of the controller. This map repesents the complete status of the controller. */ private Map<String, State<?>> whatList = new HashMap<String, State<?>>(); public static final String JSON_TITLE = "title"; public static final String JSON_STATES = "states"; public static final String JSON_WHERE = "where"; public static final String JSON_WHO = "who"; public static final String JSON_DESCRIPTION = "description"; public String getTitle() { return title; } public void setTitle(String name) { this.title = name; } /** * Return the description of the controller * @return description of the controller */ public String getDescription() { return description; } /** * Define the description of the controller. * @param description of the controller */ public void setDescription(String description) { this.description = description; } /** * Return the list of label associated to this component. * @return list of label. */ public LabelList getLabels() { return labelList; } public abstract Who getWho(); /** * Returns the address of the targeted device * @return the address of the targeted device */ public Where getWhere() { return where; } /** * Defines the address of the targeted device to control * @param newValue the address of the targeted device to control */ public void setWhere(Where newValue) { this.whatList.clear(); this.where = newValue; // Request all status for (String state : getStateList()) { get(state); } } public abstract List<String> getStateList(); public Map<String, State<?>> getStateMap() { return whatList; } /** * If current value of a state name is known, provide it directly * without request the server. Else (if value * is null) request the server and return null without waiting: to get * the value use a StatusListener, or wait a bit and call get again. * Normally the value is never null since we request the value when you set the where * and later the {@link Monitor} refresh the value when domotic server push an event. * * @param name The state name * @return The current value of a state name or null if a request to the server * is done (when current value is null). */ protected State<?> get(final String name) { if (name == null) { throw new NullPointerException("Could not get null state name."); } State<?> value = whatList.get(name); if (value == null) { executeStatus(name, new StatusListener() { @Override public void onStatus(What what, CommandResult result) { // The major pb with that: if we get the value of what // before initial response (done when setWhere call get the first time) // we get null and nothing is done to be // advertise of the change when value arrive... if (what != null) { // what can be null since sometime only acknowledgement is return... whatList.put(what.getName(), what.getValue()); } } }); } return value; } /** * Update a value of the status. * @param name The state name to update * @param state The new value of the state name */ protected void set(String name, State<?> state) { if (state == null || name == null) { throw new NullPointerException("Could not set null state name."); } // The command is sent to the gateway. Gateway transmits it to the actuator. // If everything is fine, Gateway provides through the monitor session // the new status => not need to set it here since it will be set by the // monitor way. final What oldWhat = new What(name, whatList.get(name)); final What newWhat= new What(name, state); // what = newWhat; => it will be done with changeWhat by the monitor listener executeAction(newWhat, new CommandListener() { @Override public void onCommand(CommandResult result) { if (CommandResultStatus.ok.equals(result.getStatus())) { // Status has been changed // what = newWhat; => it will be done with changeWhat by the // monitor listener // notifyWhatChange(oldStatus, newWhat); call by monitor! => // it will be done with changeWhat by the monitor listener } else { // Error // what = oldStatus; => it will be done with changeWhat by // the monitor listener notifyStateChangeError(oldWhat, newWhat, result); } } }); } /** * Define the new {@link Status} of the device without sent the command on * the bus. * * @param newWhat * {@link Status} of the device. */ public void receivedAction(What newWhat) { State<?> oldState = whatList.get(newWhat.getName()); whatList.put(newWhat.getName(), newWhat.getValue()); notifyStateChange(new What(newWhat.getName(),oldState), newWhat); } /** * Define the gateway to connect on. * @param server */ public void setServer(Commander server) { this.server = server; } /** * Executes an action. The result is wait by a command listener. * * @param what The state to update * @param commandListener The listener which will wait for action result */ protected void executeAction(final What what, final CommandListener commandListener) { if (server == null || what == null || what.getName() == null) { commandListener.onCommand(new DefaultCommandResult("", CommandResultStatus.nok)); } else { waitingResult = true; server.sendCommand( new Command(getWho(), what, where, Type.ACTION, this), new CommandListener() { @Override public void onCommand(CommandResult commandResult) { // TODO tester que le type du status match avec ceux supporté! waitingResult = false; commandListener.onCommand(commandResult); } } ); } } /** * Executes a read operation on a state name of the controller. * The result is wait by a status listener. * * @param stateName The state name to read * @param statusListener The listener which will wait for result */ protected void executeStatus(final String name, final StatusListener statusListener) { if (server == null || name == null) { statusListener.onStatus(new What(name, null), new DefaultCommandResult("", CommandResultStatus.nok)); } else { waitingResult = true; server.sendCommand( new Command(getWho(), new What(name, null), where, Type.STATUS, this), new CommandListener() { @Override public void onCommand(CommandResult result) { waitingResult = false; if (CommandResultStatus.ok.equals(result.getStatus())) { // Return the status of the controller from the server statusListener.onStatus( result.getWhat(name), result); } else { // ERROR: message not sent on Bus or error // return... we keep the last value statusListener.onStatus(new What(name, null), result); } } }); } } /** * @param listener * the new change listener. */ public void addControllerChangeListener(ControllerChangeListener listener) { synchronized (controllerChangeListenerList) { controllerChangeListenerList.add(listener); } } /** * @param listener * the change listener to remove. */ public void removeControllerChangeListener(ControllerChangeListener listener) { synchronized (controllerChangeListenerList) { controllerChangeListenerList.remove(listener); } } private void notifyStateChange(What oldWhat, What newWhat) { synchronized (controllerChangeListenerList) { for (ControllerChangeListener listener : controllerChangeListenerList) { listener.onStateChange(this,oldWhat , newWhat); } } } private void notifyStateChangeError(What oldStatus, What newStatus, CommandResult result) { synchronized (controllerChangeListenerList) { for (ControllerChangeListener listener : controllerChangeListenerList) { listener.onStateChangeError(this, oldStatus, newStatus, result); } } } /** * Indicates if the controller is waiting information from the gateway. * @return <code>true</code> if controller is waiting information and <code>false</code> otherwise */ public boolean isWaitingResult() { return waitingResult; } @Override public String toString() { return toJson().toString(); } @Override public JSONObject toJson() { JSONObject controllerJson = new JSONObject(); controllerJson.put(JSON_WHO, getWho()) .put(JSON_TITLE, getTitle()) .put(JSON_DESCRIPTION, getDescription()); if (getWhere() != null) { controllerJson.put(JSON_WHERE, getWhere().getTo()); } JSONObject states = new JSONObject(); if (! whatList.isEmpty()) { for (Entry<String, State<?>> entry : whatList.entrySet()) { State<?> state = entry.getValue(); String name = entry.getKey(); states.put(name, state == null ? null : state.toString()); } } controllerJson.put(JSON_STATES, states); return controllerJson; } @Override public void fromJson(JSONObject jsonObject) { setTitle(jsonObject.getString(JSON_TITLE)); try { setDescription(jsonObject.getString(JSON_DESCRIPTION)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } String to = jsonObject.getString(JSON_WHERE); if (!"null".equals(String.valueOf(to))) { setWhere(new Where(to, to)); } // JSONObject states = jsonObject.getJSONObject(JSON_STATES); // for (final String name: states.keySet()) { // String value = states.getString(name); // StateName sname = new StateName() { // // @Override // public String getName() { // return name; // } // }; // StateValue svalue; Les valeurs sont en lecture seul??? // try { // svalue = stateTypes.get(sname).newInstance(); // svalue.setValue(value); // stateList.put(sname, svalue); // } catch (InstantiationException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } catch (IllegalAccessException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // } } }