package be.abollaert.domotics.light.drivers.tcp; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.util.Date; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; import be.abollaert.domotics.light.api.ChannelState; import be.abollaert.domotics.light.api.DigitalInputChannelConfiguration; import be.abollaert.domotics.light.api.DigitalModule; import be.abollaert.domotics.light.api.DigitalModuleConfiguration; import be.abollaert.domotics.light.api.DimMoodElement; import be.abollaert.domotics.light.api.DimmerDirection; import be.abollaert.domotics.light.api.DimmerInputChannelConfiguration; import be.abollaert.domotics.light.api.DimmerModuleConfiguration; import be.abollaert.domotics.light.api.Mood; import be.abollaert.domotics.light.api.SwitchMoodElement; import be.abollaert.domotics.light.protocolbuffers.Api; import be.abollaert.domotics.light.protocolbuffers.Api.GetDigitalModuleConfigResponse; import be.abollaert.domotics.light.protocolbuffers.Api.MessageResponse; import com.google.protobuf.Message; /** * The TCP client class provides access to the API. * * @author alex */ public final class TCPClient { /** The URI for the GetModules request. */ private static final String URI_GET_MODULES = "/api/GetModules"; /** URI for the GetDigitalChannelConfig request. */ private static final String URI_GET_DIGITAL_CHANNEL_CONFIG = "/api/GetDigitalInputChannelConfig"; /** URI for the SwitchOutput request. */ private static final String URI_SWITCH_OUTPUT ="/api/SwitchOutput"; /** The URI to get the output channels. */ private static final String URI_GET_OUTPUT_CHANNELS = "/api/GetOutputChannels"; /** The URL to set an input channel configuration. */ private static final String URI_SET_DIGITAL_INPUT_CONFIG = "/api/SetDigitalInputChannelConfig"; /** The URL to set a digital module configuration. */ private static final String URI_SET_DIGITAL_MODULE_CONFIG = "/api/SetDigitalModuleConfig"; /** THe URL for saving the configuration of a {@link DigitalModule}. */ private static final String URI_SAVE_DIGITAL_MODULE_CONFIG = "/api/SaveModuleConfig"; /** URI to get the digital module configuration. */ private static final String URI_GET_DIGITAL_MODULE_CONFIG = "/api/GetDigitalModuleConfiguration"; /** URI used to get dimmer input channel configuration. */ private static final String URI_GET_DIMMER_INPUT_CHANNEL_CONFIG = "/api/GetDimmerInputChannelConfig"; /** URI for saving a mood. */ private static final String URI_SAVE_MOOD = "/api/SaveMood"; /** The URI for dimming. */ private static final String URI_DIM = "/api/Dim"; /** URI to set the dimmer module config. */ private static final String URI_SET_DIMMER_MODULE_CONFIG = "/api/SetDimmerModuleConfig"; /** URI to set the dimmer input configuration. */ private static final String URI_SET_DIMMER_INPUT_CONFIG = "/api/SetDimmerInputConfiguration"; /** URI to get the switch requests. */ private static final String URI_GET_SWITCH_EVENTS = "/api/GetSwitchEvents"; /** URI for getting the output state of a digital channel. */ private static final String URI_GET_DIGITAL_OUTPUT_STATE = "/api/GetDigitalOutputState"; /** URI for getting the output state of a dimmer channel. */ private static final String URI_GET_DIMMER_OUTPUT_STATE = "/api/GetDimmerOutputState"; /** URI for getting all moods. */ private static final String URI_GET_MOODS = "/api/GetMoods"; private static final String URI_ACTIVATE_MOOD = "/api/ActivateMood"; private static final String URI_REMOVE_MOOD = "/api/RemoveMood"; private static final String URI_ALL_LIGHTS_OFF = "/api/AllLightsOff"; /** The HTTP client. */ private HttpClient httpClient; /** The event listener. */ private EventListener eventListener; /** The server address. */ private String serverAddress; /** The server port. */ private int port; /** The http client lock. */ private final Lock httpClientLock = new ReentrantLock(); /** * Connects the client. * * @param serverAddress The server address. * @param port The port. * * @throws IOException If an IO error occurs during the connect. */ public void connect(final String serverAddress, final int port, final InetAddress iface) throws IOException { this.serverAddress = serverAddress; this.port = port; this.httpClient = new HttpClient(); this.eventListener = new EventListener(); this.eventListener.start(iface); } /** * Disconnects the client. * * @throws IOException If an IO error occurs while disconnecting. */ public void disconnect() throws IOException { this.eventListener.stop(); this.eventListener = null; this.httpClient = null; } /** * Gets the modules from the server. * * @return The modules from the server. * * @throws IOException If an IO error occurs. */ public final Api.GetModulesResponse getModules() throws IOException { return (Api.GetModulesResponse)this.execute(URI_GET_MODULES, null, Api.GetModulesResponse.newBuilder()); } /** * Returns the digital channel config for the requested channel. * * @param moduleId The module ID. * @param channelNumber The channel number. * * @return The config. * * @throws IOException */ public final Api.GetDigitalInputChannelConfigResponse getDigitalChannelConfiguration(final int moduleId, final int channelNumber) throws IOException { final Api.GetDigitalInputChannelConfig.Builder builder = Api.GetDigitalInputChannelConfig.newBuilder(); builder.setModuleId(moduleId); builder.setChannelNumber(channelNumber); return (Api.GetDigitalInputChannelConfigResponse)this.execute(URI_GET_DIGITAL_CHANNEL_CONFIG, builder.build(), Api.GetDigitalInputChannelConfigResponse.newBuilder()); } public final void switchOutput(final int moduleId, final int channelNumber, final ChannelState desiredState) throws IOException { final Api.SwitchOutput.Builder builder = Api.SwitchOutput.newBuilder(); builder.setModuleId(moduleId); builder.setChannelNumber(channelNumber); builder.setRequiredState(desiredState == ChannelState.ON ? true : false); this.executeVoidMessage(URI_SWITCH_OUTPUT, builder.build()); } /** * Returns the switch events for the given module ID and channel number. * * @param moduleId * @param channelNumber * @param startDate * @param endDate * @return * @throws IOException */ public final Api.SwitchEventList getSwitchEvents(final int moduleId, final int channelNumber, final Date startDate, final Date endDate) throws IOException { final Api.GetSwitchEvents.Builder requestBuilder = Api.GetSwitchEvents.newBuilder(); requestBuilder.setModuleId(moduleId); requestBuilder.setChannelNumber(channelNumber); if (startDate != null) { requestBuilder.setStartDate(startDate.getTime()); } if (endDate != null) { requestBuilder.setEndDate(endDate.getTime()); } return (Api.SwitchEventList)this.execute(URI_GET_SWITCH_EVENTS, requestBuilder.build(), Api.SwitchEventList.newBuilder()); } /** * Dim the given channel. * * @param moduleId The module ID. * @param channelNumber The channel number. * @param percentage The percentage. * * @throws IOException If an IO error occurs during the action. */ public final void dim(final int moduleId, final int channelNumber, final int percentage) throws IOException { final Api.Dim.Builder messageBuilder = Api.Dim.newBuilder(); messageBuilder.setModuleId(moduleId); messageBuilder.setChannelNumber(channelNumber); messageBuilder.setPercentage(percentage); this.executeVoidMessage(URI_DIM, messageBuilder.build()); } public final Api.GetOutputChannelsResponse getOutputChannels() throws IOException { try { return (Api.GetOutputChannelsResponse)this.execute(URI_GET_OUTPUT_CHANNELS, null, Api.GetOutputChannelsResponse.newBuilder()); } catch (HttpException e) { throw new IOException("HTTP error while getting modules : [" + e.getMessage() + "]"); } } /** * Set the configuration for a particular digital input channel. * * @param newConfiguration The new configuration. * * @return The response. * * @throws IOException If an IO error occurs during the set. */ public final void setDigitalInputChannelConfiguration(final DigitalInputChannelConfiguration newConfiguration) throws IOException { final Api.DigitalInputChannelConfig.Builder configurationBuilder = Api.DigitalInputChannelConfig.newBuilder(); configurationBuilder.setCurrentOutputState(false); configurationBuilder.setCurrentSwitchState(false); configurationBuilder.setDefaultState(newConfiguration.getDefaultState() == ChannelState.ON ? true : false); configurationBuilder.setMappedOutputChannel(newConfiguration.getMappedOutputChannel()); configurationBuilder.setTimerInSec(newConfiguration.getTimerInSeconds()); if (newConfiguration.getName() != null) { configurationBuilder.setName(newConfiguration.getName()); } configurationBuilder.setEnableLogging(newConfiguration.isLoggingEnabled()); final Api.SetDigitalInputConfig.Builder messageBuilder = Api.SetDigitalInputConfig.newBuilder(); messageBuilder.setModuleId(newConfiguration.getModuleId()); messageBuilder.setChannelNumber(newConfiguration.getChannelNumber()); messageBuilder.setConfig(configurationBuilder); this.executeVoidMessage(URI_SET_DIGITAL_INPUT_CONFIG, messageBuilder.build()); } /** * Sets the configuration for a dimmer module. * * @param newConfiguration The new configuration. * * @throws IOException If an IO error occurs while setting. */ public final void setDimmerModuleConfiguration(final DimmerModuleConfiguration newConfiguration) throws IOException { final Api.DimmerModuleConfig.Builder configBuilder = Api.DimmerModuleConfig.newBuilder(); configBuilder.setDimmerDelay(newConfiguration.getDimmerDelay()); configBuilder.setDimmerThresholdInMs(newConfiguration.getDimmerThreshold()); configBuilder.setSwitchThresholdInMs(newConfiguration.getSwitchThreshold()); final Api.SetDimmerModuleConfig.Builder messageBuilder = Api.SetDimmerModuleConfig.newBuilder(); messageBuilder.setConfiguration(configBuilder); messageBuilder.setModuleId(newConfiguration.getModuleId()); this.executeVoidMessage(URI_SET_DIMMER_MODULE_CONFIG, messageBuilder.build()); } /** * Sets the configuration for a dimmer input. * * @param newConfiguration The new configuration. * * @throws IOException If an IO error occurs while setting. */ public final void setDimmerInputConfiguration(final int moduleId, final int channelNumber, final DimmerInputChannelConfiguration newConfiguration) throws IOException { final Api.DimmerInputChannelConfig.Builder configBuilder = Api.DimmerInputChannelConfig.newBuilder(); configBuilder.setCurrentDimmerPercentage(0); configBuilder.setCurrentOutputState(false); configBuilder.setCurrentSwitchState(false); configBuilder.setDefaultDirection(newConfiguration.getDefaultDirection() == DimmerDirection.UP ? true : false); configBuilder.setDefaultPercentage(newConfiguration.getDefaultPercentage()); configBuilder.setDefaultState(newConfiguration.getDefaultState() == ChannelState.ON ? true : false); configBuilder.setMappedOutputChannel(newConfiguration.getMappedOutputChannel()); configBuilder.setTimerInSec(newConfiguration.getTimerInSeconds()); if (newConfiguration.getName() != null) { configBuilder.setName(newConfiguration.getName()); } configBuilder.setEnableLogging(newConfiguration.isLoggingEnabled()); final Api.SetDimmerInputConfig.Builder messageBuilder = Api.SetDimmerInputConfig.newBuilder(); messageBuilder.setConfig(configBuilder); messageBuilder.setModuleId(moduleId); messageBuilder.setChannelNumber(channelNumber); this.executeVoidMessage(URI_SET_DIMMER_INPUT_CONFIG, messageBuilder.build()); } /** * Sets the new digital module configuration. * * @param newConfiguration The new configuration. * * @return The response. * * @throws IOException If an IO error occurs. */ public final void setDigitalModuleConfiguration(final DigitalModuleConfiguration newConfiguration) throws IOException { final Api.DigitalModuleConfig.Builder configurationBuilder = Api.DigitalModuleConfig.newBuilder(); configurationBuilder.setSwitchThresholdInMs(newConfiguration.getSwitchThreshold()); final Api.SetDigitalModuleConfig.Builder messageBuilder = Api.SetDigitalModuleConfig.newBuilder(); messageBuilder.setModuleId(newConfiguration.getModuleId()); messageBuilder.setConfiguration(configurationBuilder); this.executeVoidMessage(URI_SET_DIGITAL_MODULE_CONFIG, messageBuilder.build()); } /** * Saves a digital module configuration. * * @param moduleId The module ID. * * @return The response. * * @throws IOException If an IO error occurs. */ public final void saveModuleConfiguration(final int moduleId) throws IOException { final Api.SaveDigitalModuleConfig.Builder messageBuilder = Api.SaveDigitalModuleConfig.newBuilder(); messageBuilder.setModuleId(moduleId); this.executeVoidMessage(URI_SAVE_DIGITAL_MODULE_CONFIG, messageBuilder.build()); } public final int saveMood(final Mood mood) throws IOException { final Api.Mood.Builder moodBuilder = Api.Mood.newBuilder(); moodBuilder.setName(mood.getName()); moodBuilder.setMoodId(mood.getId()); for (final SwitchMoodElement switchElement : mood.getSwitchMoodElements()) { final Api.SwitchMoodElement.Builder elementBuilder = Api.SwitchMoodElement.newBuilder(); elementBuilder.setModuleId(switchElement.getModuleId()); elementBuilder.setChannelNumber(switchElement.getChannelNumber()); elementBuilder.setRequestedState(switchElement.getRequestedState() == ChannelState.ON); moodBuilder.addSwitchElements(elementBuilder.build()); } for (final DimMoodElement dimmerElement : mood.getDimMoodElements()) { final Api.DimmerMoodElement.Builder elementBuilder = Api.DimmerMoodElement.newBuilder(); elementBuilder.setModuleId(dimmerElement.getModuleId()); elementBuilder.setChannelNumber(dimmerElement.getChannelNumber()); elementBuilder.setPercentage(dimmerElement.getTargetPercentage()); moodBuilder.addDimmerElements(elementBuilder.build()); } final Api.SaveMood.Builder requestBuilder = Api.SaveMood.newBuilder(); requestBuilder.setMood(moodBuilder.build()); return ((Api.SaveMoodResponse)this.execute(URI_SAVE_MOOD, requestBuilder.build(), Api.SaveMoodResponse.newBuilder())).getMoodId(); } /** * REturns the configuration of a digital module. * * @param moduleId The ID of the module. * * @return The configuration. * * @throws IOException */ public final Api.DigitalModuleConfig getDigitalModuleConfig(final int moduleId) throws IOException { final Api.GetDigitalModuleConfig.Builder messageBuilder = Api.GetDigitalModuleConfig.newBuilder(); messageBuilder.setModuleId(moduleId); final GetDigitalModuleConfigResponse response = (Api.GetDigitalModuleConfigResponse)this.execute(URI_GET_DIGITAL_MODULE_CONFIG, messageBuilder.build(), Api.GetDigitalModuleConfigResponse.newBuilder()); return response.getConfig(); } /** * Returns the event listener. * * @return The event listener. */ public final EventListener getEventListener() { return this.eventListener; } /** * Generates the URL for the given URI. * * @param uri The URI to generate an URL for. * * @return The URL to use. */ private final String generateURL(final String uri) { return new StringBuilder("http://").append(this.serverAddress).append(":").append(this.port).append(uri).toString(); } /** * Get the configuration of an input channel on a dimmer module. * * @param moduleId The ID of the module. * @param channelNumber The channel number. * * @return The configuration of the channel. * * @throws IOException If an IO error occurs. */ public final Api.DimmerInputChannelConfig getDimmerInputChannelConfiguration(final int moduleId, final int channelNumber) throws IOException { final Api.GetDimmerInputChannelConfig.Builder requestMessageBuilder = Api.GetDimmerInputChannelConfig.newBuilder(); requestMessageBuilder.setModuleId(moduleId); requestMessageBuilder.setChannelNumber(channelNumber); final Api.GetDimmerInputChannelConfigResponse response = (Api.GetDimmerInputChannelConfigResponse)this.execute(URI_GET_DIMMER_INPUT_CHANNEL_CONFIG, requestMessageBuilder.build(), Api.GetDimmerInputChannelConfigResponse.newBuilder()); return response.getConfig(); } public final Api.DigitalChannelOutputState getDigitalChannelOutputState(final int moduleId, final int channelNumber) throws IOException { final Api.GetOutputChannelState.Builder requestBuilder = Api.GetOutputChannelState.newBuilder(); requestBuilder.setModuleId(moduleId); requestBuilder.setChannelNumber(channelNumber); return (Api.DigitalChannelOutputState)this.execute(URI_GET_DIGITAL_OUTPUT_STATE, requestBuilder.build(), Api.DigitalChannelOutputState.newBuilder()); } public final Api.DimmerChannelOutputState getDimmerChannelOutputState(final int moduleId, final int channelNumber) throws IOException { final Api.GetOutputChannelState.Builder requestBuilder = Api.GetOutputChannelState.newBuilder(); requestBuilder.setModuleId(moduleId); requestBuilder.setChannelNumber(channelNumber); return (Api.DimmerChannelOutputState)this.execute(URI_GET_DIMMER_OUTPUT_STATE, requestBuilder.build(), Api.DimmerChannelOutputState.newBuilder()); } /** * Executes the given message against the given URI, and returns the reult message if any. * * @param uri The URI. * @param message The message. * @param responseBuilder The response builder. * * @return The response message. * * @throws IOException If an IO error occurs. */ private final Message execute(final String uri, final Message message, final Message.Builder responseBuilder) throws IOException { PostMethod method = new PostMethod(this.generateURL(uri)); try { this.httpClientLock.lock(); if (message != null) { method.setRequestEntity(new ByteArrayRequestEntity(message.toByteArray())); } this.httpClient.executeMethod(method); final InputStream responseBodyStream = method.getResponseBodyAsStream(); if (responseBodyStream != null) { responseBuilder.mergeFrom(responseBodyStream); } return responseBuilder.build(); } finally { method.releaseConnection(); this.httpClientLock.unlock(); } } private final void executeVoidMessage(final String uri, final Message message) throws IOException { final Api.MessageResponse response = (Api.MessageResponse)this.execute(uri, message, MessageResponse.newBuilder()); if (response.getType() == Api.MessageResponse.Type.ERROR) { throw new IOException(response.getMessage()); } } public final Api.MoodList getAllMoods() throws IOException { return (Api.MoodList)this.execute(URI_GET_MOODS, null, Api.MoodList.newBuilder()); } public final void activateMood(final int moodId) throws IOException { final Api.ActivateMood.Builder requestBuilder = Api.ActivateMood.newBuilder(); requestBuilder.setMoodId(moodId); this.executeVoidMessage(URI_ACTIVATE_MOOD, requestBuilder.build()); } public final void removeMood(final int moodId) throws IOException { final Api.RemoveMood.Builder requestBuilder = Api.RemoveMood.newBuilder(); requestBuilder.setMoodId(moodId); this.executeVoidMessage(URI_REMOVE_MOOD, requestBuilder.build()); } /** * Switch all lights off. * * @throws IOException If an IO error occurs. */ public final void allLightsOff() throws IOException { this.executeVoidMessage(URI_ALL_LIGHTS_OFF, null); } }