Package org.openhab.binding.jointspace.internal

Source Code of org.openhab.binding.jointspace.internal.JointSpaceBinding

/**
* Copyright (c) 2010-2014, openHAB.org and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.jointspace.internal;

import java.awt.Color;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.Dictionary;

import org.openhab.binding.jointspace.JointSpaceBindingProvider;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;

import org.openhab.core.library.types.OnOffType;
import org.openhab.io.net.actions.Ping;
import org.openhab.io.net.http.HttpUtil;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

/**
* Binding to handle communication with jointSPACE device. For items that are
* configured for polling, the jointspace server is polled every
* {@code refreshInterval} ms
*
* @author David Lenz
* @since 1.5.0
*/
public class JointSpaceBinding extends AbstractActiveBinding<JointSpaceBindingProvider> implements ManagedService {

  private static final Logger logger = LoggerFactory.getLogger(JointSpaceBinding.class);

  /** Constant which represents the content type <code>application/json</code> */
  public final static String CONTENT_TYPE_JSON = "application/json";

  public final static String PREFIX_HSB_TYPE = "HSB";
  public final static String PREFIX_DECIMAL_TYPE = "DEC";

  /**
   * the refresh interval which is used to poll values from the JointSpace
   * server (optional, defaults to 60000ms)
   */
  private long refreshInterval = 60000;

  /**
   * The ip of the TV set
   */
  private String ip;

  /**
   * The port of the TV set, (optional, defaults to 1925)
   */
  private String port = "1925";

  public JointSpaceBinding() {
  }

  public void activate() {
  }

  public void deactivate() {
  }

  /**
   * @{inheritDoc
   */
  @Override
  protected long getRefreshInterval() {
    return refreshInterval;
  }

  /**
   * @{inheritDoc
   */
  @Override
  protected String getName() {
    return "JointSpace Refresh Service";
  }

  /**
   * @{inheritDoc
   *
   *              Calls @see updateItemState() for all items with a "POLL"
   *              command in the configuration
   */
  @Override
  protected void execute() {
    logger.debug("Checking if host is available");

    boolean success = false;
    int timeout = 5000;
    try {
      success = Ping.checkVitality(ip, 0, timeout);
      if (success) {
        logger.debug("established connection [host '{}' port '{}' timeout '{}']",
            new Object[] { ip, 0, timeout });

        // After connection is possible, do the actual polling
        for (JointSpaceBindingProvider provider : providers) {
          for (String itemName : provider.getItemNames()) {
            String tvcommand = provider.getTVCommand(itemName, "POLL");
            if (tvcommand != null) {
              updateItemState(itemName, tvcommand);
            }
          }
        }

      } else {
        logger.debug("couldn't establish network connection [host '{}' port '{}' timeout '{}']", new Object[] {
            ip, 0, timeout });
      }
    } catch (SocketTimeoutException se) {
      logger.debug("timed out while connecting to host '{}' port '{}' timeout '{}'", new Object[] { ip, 0,
          timeout });
    } catch (IOException ioe) {
      logger.debug("couldn't establish network connection [host '{}' port '{}' timeout '{}']", new Object[] { ip,
          0, timeout });
    }
  }

  /**
   * Parses an ambilight command and extracts the layers. for example
   * "ambilight[layer1[left]]" will return a list {"layer1","left"}
   *
   * @param command
   *            ambilight command string. For example "ambilight[layer1].color
   * @return a stringlist containing all the layers present in the command
   */
  private String[] command2LayerString(String command) {
    String[] temp = command.split("\\.")[0].split("\\[");
    String[] layer = null;
    if (temp.length > 1) {
      layer = new String[temp.length - 1];
      System.arraycopy(temp, 1, layer, 0, temp.length - 1);
      layer[layer.length - 1] = layer[layer.length - 1].replace(']', ' ').trim();
    } else {
      layer = null;
    }
    return layer;
  }

  /**
   * Polls the TV for the values specified in @see tvCommand and posts state
   * update for @see itemName Currently only the following commands are
   * available - "ambilight[...]" returning a HSBType state for the given
   * ambilight pixel specified in [...] - "volume" returning a DecimalType -
   * "volume.mute" returning 'On' or 'Off' - "source" returning a String with
   * selected source (e.g. "hdmi1", "tv", etc)
   *
   * @param itemName
   * @param tvCommand
   */
  private void updateItemState(String itemName, String tvCommand) {
    if (tvCommand.contains("ambilight")) {
      String[] layer = command2LayerString(tvCommand);
      HSBType state = new HSBType(getAmbilightColor(ip + ":" + port, layer));
      eventPublisher.postUpdate(itemName, state);
    } else if (tvCommand.contains("volume")) {
      if (tvCommand.contains("mute")) {
        eventPublisher.postUpdate(itemName, getTVVolume(ip + ":" + port).mute ? OnOffType.ON : OnOffType.OFF);
      } else {
        eventPublisher.postUpdate(itemName, new DecimalType(getTVVolume(ip + ":" + port).volume));
      }
    } else if (tvCommand.contains("source")) {
      eventPublisher.postUpdate(itemName, new StringType(getSource(ip + ":" + port)));
    } else {
      logger.error("Could not parse item state\"" + tvCommand + "\" for polling");
      return;
    }

  }

  /**
   * @{inheritDoc Processes the commands and maps them to jointspace commands
   */
  @Override
  protected void internalReceiveCommand(String itemName, Command command) {

    if (itemName != null && !this.providers.isEmpty()) {
      JointSpaceBindingProvider provider = this.providers.iterator().next();

      if (provider == null) {
        logger.warn("Doesn't find matching binding provider [itemName={}, command={}]", itemName, command);
        return;
      }

      logger.debug("Received command (item='{}', state='{}', class='{}')",
          new Object[] { itemName, command.toString(), command.getClass().toString() });

      String tvCommandString = null;

      // first check if we can translate the command directly
      tvCommandString = provider.getTVCommand(itemName, command.toString());

      // if not try some special notations
      if (tvCommandString == null) {
        if (command instanceof HSBType) {
          tvCommandString = provider.getTVCommand(itemName, "HSB");
        } else if (command instanceof DecimalType) {
          tvCommandString = provider.getTVCommand(itemName, "DEC");
        }
        if (tvCommandString == null) {
          tvCommandString = provider.getTVCommand(itemName, "*");
        }
      }

      if (tvCommandString == null) {
        logger.warn("Unrecognized command \"{}\"", command.toString());
        return;

      }

      if (tvCommandString.contains("key")) {
        logger.debug("Found a Key command: " + tvCommandString);
        String[] commandlist = tvCommandString.split("\\.");
        if (commandlist.length != 2) {
          logger.warn("wrong number of arguments for key command \"{}\". Should be key.X", tvCommandString);
          return;
        }
        String key = commandlist[1];
        sendTVCommand(key, ip + ":" + port);
      } else if (tvCommandString.contains("ambilight")) {
        logger.debug("Found an ambilight command: {}", tvCommandString);
        String[] commandlist = tvCommandString.split("\\.");
        String[] layer = command2LayerString(tvCommandString);
        if (commandlist.length < 2) {
          logger.warn(
              "wrong number of arguments for ambilight command \"{}\". Should be at least ambilight.color, ambilight.mode.X, etc...",
              tvCommandString);
          return;
        }
        if (commandlist[1].contains("color")) {
          setAmbilightColor(ip + ":" + port, command, layer);
        } else if (commandlist[1].contains("mode")) {
          if (commandlist.length != 3) {
            logger.warn(
                "wrong number of arguments for ambilight.mode command \"{}\". Should be ambilight.mode.internal, ambilight.mode.manual, ambilight.mode.expert",
                tvCommandString);
            return;
          }
          setAmbilightMode(commandlist[2], ip + ":" + port);
        }
      } else if (tvCommandString.contains("volume")) {
        logger.debug("Found a Volume command: {}", tvCommandString);
        sendVolume(ip + ":" + port, command);
      } else if (tvCommandString.contains("source")) {
        logger.debug("Found a Source command: {}", tvCommandString);
        String[] commandlist = tvCommandString.split("\\.");
        if (commandlist.length < 2) {
          logger.warn("wrong number of arguments for source command \"{}\". Should be at least mode.X...",
              tvCommandString);
          return;
        }
        sendSource(ip + ":" + port, commandlist[1]);

      } else {
        logger.warn("Unrecognized tv command \"{}\". Only key.X or ambilight[].X is supported", tvCommandString);
        return;
      }
    }
  }

  /**
   * Gets the color for a specified ambilight pixel from the host and tries to
   * parse the returned json value
   *
   * @param host
   *            hostname including port to query the jointspace api.
   * @param layers
   *            a list of layers to the requested pixel. For example
   *            [layer1[right[2]]]
   * @return Color of the ambilight pixel, or NULL if value could not be
   *         retrieved
   */
  private Color getAmbilightColor(String host, String[] layers) {

    logger.debug("Getting ambilight color for host {} for layers {}", host, layers);
    Color retval = new Color(0, 0, 0);
    String url = "http://" + host + "/1/ambilight/processed";

    String ambilight_json = HttpUtil.executeUrl("GET", url, IOUtils.toInputStream(""), CONTENT_TYPE_JSON, 1000);
    if (ambilight_json != null) {
      logger.trace("TV returned for ambilight request: {}", ambilight_json);
      try {
        Object obj = JSONValue.parse(ambilight_json);
        JSONObject array = (JSONObject) obj;
        for (String layer : layers) {
          array = (JSONObject) array.get((Object) layer.trim());
          if (array == null) {
            logger.warn("Could not find layer {} in the json string", layer);
            return null;
          }
        }
        int r = 0, g = 0, b = 0;
        r = Integer.parseInt(array.get("r").toString());
        g = Integer.parseInt(array.get("g").toString());
        b = Integer.parseInt(array.get("b").toString());
        retval = new Color(r, g, b);
      } catch (Throwable t) {
        logger.warn("Could not parse JSON String for ambilight value. Error: {}", t.toString());
      }

    } else {
      logger.debug("Could not get ambilight value from JointSpace Server \"{}\"", host);
      return null;
    }

    return retval;
  }

  /**
   * Polls the source from the tv and returns it as a string
   *
   * @param host
   * @return a string containig the "source" returned by the TV, or null if
   *         unsuccesfull
   */
  private String getSource(String host) {
    String url = "http://" + host + "/1/sources/current";
    String source_json = HttpUtil.executeUrl("GET", url, IOUtils.toInputStream(""), CONTENT_TYPE_JSON, 1000);
    logger.debug("Getting source for host {}", host);
    if (source_json != null) {
      logger.trace("TV returned for source request: {}", source_json);
      try {
        Object obj = JSONValue.parse(source_json);
        JSONObject array = (JSONObject) obj;
        return array.get("id").toString();
      } catch (Throwable t) {
        logger.warn("Could not parse JSON String for source. Error: {}", t.toString());
      }

    } else {
      logger.debug("Could not get source from JointSpace Server \"{}\"", host);
    }
    return null;
  }

  /**
   * Sets the current source at the TV
   *
   * @param host
   * @param source
   *            string identifying the desired source. valid values are
   *            "hdmi1", "tv", etc. (@see
   *            http://jointspace.sourceforge.net/projectdata
   *            /documentation/jasonApi/1/doc/API-Method-sources-GET.html)
   */

  private void sendSource(String host, String source) {

    String url = "http://" + host + "/1/sources/current";

    String content = "{\"id\":\"" + source + "\"}";
    logger.debug("Switching source of host {} to {}", host, source);
    logger.trace(content.toString());
    HttpUtil.executeUrl("POST", url, IOUtils.toInputStream(content), CONTENT_TYPE_JSON, 1000);
  }

  /**
   * Sends a volume command to the TV, depending on the command received For
   * commands of type DecimalType, the value for volume will be set directly
   * (mute will not be affected) For commands of type IncreaseDecreaseType,
   * the current value (polled from TV( for volume will be
   * incremented/decremented
   *
   * @param host
   * @param command
   */
  private void sendVolume(String host, Command command) {

    logger.debug("Sending volume to host {} for command {}", host, command.toString());
    volumeConfig conf = getTVVolume(host);
    String url = "http://" + host + "/1/audio/volume";

    int newvalue = conf.volume;

    if (command instanceof DecimalType) {
      logger.debug("Setting volume to decimal type");
      newvalue = ((DecimalType) command).intValue();
    } else if (command instanceof IncreaseDecreaseType) {
      if ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE) {
        logger.debug("Increased volume");
        newvalue++;
      } else {
        logger.debug("Decreased volume");
        newvalue--;
      }
    } else {
      logger.warn("Unitl now only DecimalType and IncreaseDecreaseType commands are supported vor volume command");
      return;
    }
    // ensure that we are in the valid range for this device
    newvalue = Math.min(newvalue, conf.max);
    newvalue = Math.max(newvalue, conf.min);
    String content = "{\"muted\":\"" + conf.mute + "\", \"current\":\"" + newvalue + "\"}";
    logger.trace(content);
    HttpUtil.executeUrl("POST", url, IOUtils.toInputStream(content), CONTENT_TYPE_JSON, 1000);

  }

  /**
   * Sets the ambilight color specified in command (which must be an HSBType
   * until now) for the pixel(s) specified with @see layers.
   *
   * @param host
   * @param command
   *            HSBType command to set the color
   * @param layers
   *            pixel(s) to set the color for. null if all pixels should have
   *            the same value
   */
  private void setAmbilightColor(String host, Command command, String[] layers) {

    if (!(command instanceof HSBType)) {
      logger.warn("Until now only HSBType is allowed for ambilight commands");
      return;
    }

    logger.debug("Setting Ambilight color for host {} and layer {} to {}", host, layers, command.toString());

    HSBType hsbcommand = (HSBType) command;
    String url = "http://" + host + "/1/ambilight/cached";

    StringBuilder content = new StringBuilder();
    content.append("{");

    int count = 0;

    if (layers != null) {
      for (int i = 0; i < layers.length; i++) {
        content.append("\"" + layers[i] + "\":{");
        count++;
      }
    }

    int red = Math.round(hsbcommand.getRed().floatValue() * 2.55f);
    int green = Math.round(hsbcommand.getGreen().floatValue() * 2.55f);
    int blue = Math.round(hsbcommand.getBlue().floatValue() * 2.55f);
    content.append("\"r\":" + red + ", \"g\":" + green + ", \"b\":" + blue);

    for (int i = 0; i < count; i++) {
      content.append("}");
    }

    content.append("}");

    logger.trace("Trying to post json for ambilight: {}", content.toString());

    HttpUtil.executeUrl("POST", url, IOUtils.toInputStream(content.toString()), CONTENT_TYPE_JSON, 1000);
  }

  /**
   * Sends a key to to the host. Possible values for keys can be found here:
   * http
   * ://jointspace.sourceforge.net/projectdata/documentation/jasonApi/1/doc
   * /API-Method-input-key-POST.html
   *
   * @param key
   * @param host
   */
  private void sendTVCommand(String key, String host) {

    logger.debug("Sending Key {} to {}", key, host);
    String url = "http://" + host + "/1/input/key";

    String content = "{\"key\":\"" + key + "\"}";

    logger.trace(content);

    HttpUtil.executeUrl("POST", url, IOUtils.toInputStream(content), CONTENT_TYPE_JSON, 1000);

  }

  /**
   * Function to query the TV Volume
   *
   * @param host
   * @return struct containing all given information about current volume
   *         settings (volume, mute, min, max) @see volumeConfig
   */

  private volumeConfig getTVVolume(String host) {
    volumeConfig conf = new volumeConfig();
    String url = "http://" + host + "/1/audio/volume";
    String volume_json = HttpUtil.executeUrl("GET", url, IOUtils.toInputStream(""), CONTENT_TYPE_JSON, 1000);
    if (volume_json != null) {
      try {
        Object obj = JSONValue.parse(volume_json);
        JSONObject array = (JSONObject) obj;

        conf.mute = Boolean.parseBoolean(array.get("muted").toString());
        conf.volume = Integer.parseInt(array.get("current").toString());
        conf.min = Integer.parseInt(array.get("min").toString());
        conf.max = Integer.parseInt(array.get("max").toString());
      } catch (NumberFormatException ex) {
        logger.warn("Exception while interpreting volume json return");
      } catch (Throwable t) {
        logger.warn("Could not parse JSON String for volume value. Error: {}", t.toString());
      }

    }
    return conf;
  }

  /**
   * Sets the mode of the ambilight processing mode. Manipulation the pixel
   * values cannot be done in "internal" mode
   *
   * For more details see:
   * http://jointspace.sourceforge.net/projectdata/documentation
   * /jasonApi/1/doc/API-Method-ambilight-mode-POST.html
   *
   * @param mode
   *            possible modes are: "internal", "manual", "expert".
   * @param host
   */

  private void setAmbilightMode(String mode, String host) {
    String url = "http://" + host + "/1/ambilight/mode";

    String content = "{\"current\":\"" + mode + "\"}";

    logger.trace(content);
    HttpUtil.executeUrl("POST", url, IOUtils.toInputStream(content), CONTENT_TYPE_JSON, 1000);
  }

  /**
   * @{inheritDoc
   */
  @Override
  public void updated(Dictionary<String, ?> config) throws ConfigurationException {
    if (config != null) {

      // to override the default refresh interval one has to add a
      // parameter to openhab.cfg like
      // <bindingName>:refresh=<intervalInMs>
      String refreshIntervalString = (String) config.get("refreshinterval");
      if (StringUtils.isNotBlank(refreshIntervalString)) {
        refreshInterval = Long.parseLong(refreshIntervalString);
      }
      String ipString = (String) config.get("ip");
      if (StringUtils.isNotBlank(ipString)) {
        ip = ipString;
        setProperlyConfigured(true);
      }
      String portString = (String) config.get("port");
      if (StringUtils.isNotBlank(portString)) {
        port = portString;
      }

    }
  }
}
TOP

Related Classes of org.openhab.binding.jointspace.internal.JointSpaceBinding

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.