Package org.openhab.binding.fritzbox.internal

Source Code of org.openhab.binding.fritzbox.internal.FritzboxBinding$MonitorThread$MonitorEvent

/**
* 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.fritzbox.internal;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.JobKey.jobKey;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.TriggerKey.triggerKey;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashMap;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.telnet.TelnetClient;
import org.openhab.binding.fritzbox.FritzboxBindingProvider;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.library.tel.types.CallType;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* The FritzBox binding connects to a AVM FritzBox on the monitor port 1012 and
* listens to event notifications from this box. There are event for incoming
* and outgoing calls, as well as for connections and disconnections.
*
* @author Kai Kreuzer
* @since 0.7.0
*/
public class FritzboxBinding extends
    AbstractActiveBinding<FritzboxBindingProvider> implements
    ManagedService {

  private static HashMap<String, String> commandMap = new HashMap<String, String>();
  private static HashMap<String, String> queryMap = new HashMap<String, String>();
 
  // TODO: configurable?
  // daily cron schedule
  private final String cronSchedule = "0 0 0 * * ?";

  static {
    commandMap.put(FritzboxBindingProvider.TYPE_DECT,
        "ctlmgr_ctl w dect settings/enabled");
    commandMap.put(FritzboxBindingProvider.TYPE_WLAN,
        "ctlmgr_ctl w wlan settings/ap_enabled");

    queryMap.put(FritzboxBindingProvider.TYPE_DECT,
        "ctlmgr_ctl r dect settings/enabled");
    queryMap.put(FritzboxBindingProvider.TYPE_WLAN,
        "ctlmgr_ctl r wlan settings/ap_enabled");
  }

  @Override
  public void bindingChanged(BindingProvider provider, String itemName) {
    super.bindingChanged(provider, itemName);

    conditionalDeActivate();

  }

  private void conditionalDeActivate() {
    logger.info("Fritzbox conditional deActivate: {}", bindingsExist());

    if (bindingsExist()) {
      activate();
    } else {
      deactivate();
    }
  }

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

  protected static final int MONITOR_PORT = 1012;

  /** the current thread instance that is listening to the FritzBox */
  protected static MonitorThread monitorThread = null;

  /* The IP address to connect to */
  protected static String ip;

  /* The password of the FritzBox to access via Telnet */
  protected static String password;

  /**
   * Reference to this instance to be used with the reconnection job which is
   * static.
   */
  private static FritzboxBinding INSTANCE;

  public FritzboxBinding() {
    INSTANCE = this;
  }

  public void activate() {
    super.activate();
    setProperlyConfigured(true);
    // if bundle is already configured, launch the monitor thread right away
    if (ip != null) {
      reconnect();
    }
  }

  public void deactivate() {
    if (monitorThread != null) {
      monitorThread.interrupt();
    }
    monitorThread = null;
  }

  @Override
  public void internalReceiveCommand(String itemName, Command command) {

    if (password != null && !password.isEmpty()) {
      String type = null;
      for (FritzboxBindingProvider provider : providers) {
        type = provider.getType(itemName);
        if (type != null) {
          break;
        }
      }

      logger.info("Fritzbox type: {}", type);

      if (type == null)
        return;

      TelnetCommandThread thread = new TelnetCommandThread(type, command);
      thread.start();
     
    }
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("rawtypes")
  public void updated(Dictionary config) throws ConfigurationException {

    if (config != null) {
      String ip = (String) config.get("ip");
      if (StringUtils.isNotBlank(ip)) {
        if (!ip.equals(FritzboxBinding.ip)) {
          // only do something if the ip has changed
          FritzboxBinding.ip = ip;
          conditionalDeActivate();
                   
          // schedule a daily reconnection as sometimes the FritzBox
          // stops sending data
          // and thus blocks the monitor thread
          try {
            Scheduler sched = StdSchedulerFactory
                .getDefaultScheduler();
                               
                        JobKey jobKey = jobKey("Reconnect", "FritzBox");
                        TriggerKey triggerKey = triggerKey("Reconnect", "FritzBox");
                       
                        if (sched.checkExists(jobKey)) {
                            logger.debug("Daily reconnection job already exists");
                        } else {
                            CronScheduleBuilder scheduleBuilder =
                                CronScheduleBuilder.cronSchedule(cronSchedule);
                           
                            JobDetail job = newJob(ReconnectJob.class)
                                    .withIdentity(jobKey)
                                    .build();

                            CronTrigger trigger = newTrigger()
                                    .withIdentity(triggerKey)
                                    .withSchedule(scheduleBuilder)
                                    .build();

                            sched.scheduleJob(job, trigger);
                            logger.debug("Scheduled a daily reconnection to FritzBox on {}:{}", ip, MONITOR_PORT);
                        }
          } catch (SchedulerException e) {
            logger.warn("Could not create daily reconnection job", e);
          }
        }
      }
      String password = (String) config.get("password");
      if (StringUtils.isNotBlank(password)) {
        FritzboxBinding.password = password;
      }
    }
  }

  protected void reconnect() {
    if (monitorThread != null) {
      // let's end the old thread
      monitorThread.interrupt();
      monitorThread = null;
    }
    // create a new thread for listening to the FritzBox
    monitorThread = new MonitorThread(this.eventPublisher, this.providers);
    monitorThread.start();
  }

  private static class TelnetCommandThread extends Thread {

    private static HashMap<String, String> commandMap = new HashMap<String, String>();

    static {
      commandMap.put(FritzboxBindingProvider.TYPE_DECT,
          "ctlmgr_ctl w dect settings/enabled");
      commandMap.put(FritzboxBindingProvider.TYPE_WLAN,
          "ctlmgr_ctl w wlan settings/ap_enabled");
    }

    public TelnetCommandThread(String type, Command command) {
      super();
      this.type = type;
      this.command = command;
    }

    private String type;

    private Command command;

    @Override
    public void run() {
      try {
        TelnetClient client = new TelnetClient();
        client.connect(ip);

        int state = 0;
        if (command == OnOffType.ON)
          state = 1;

        String cmdString = null;
        if (commandMap.containsKey(type)) {
          cmdString = commandMap.get(type) + " " + state;
        } else if (type.startsWith("tam")) {
          cmdString = "ctlmgr_ctl w tam settings/"
              + type.toUpperCase() + "/Active " + state;
        } else if (type.startsWith("cmd")) {
          int on = type.indexOf("ON=");
          int off = type.indexOf("OFF=");
          if (state == 0) {
            cmdString = type.substring(off + 4,
                on < off ? type.length() : on);
          } else {
            cmdString = type.substring(on + 3,
                off < on ? type.length() : off);
          }
          cmdString = cmdString.trim();
        }

        /*
         * This is a approach with receive/send in serial way. This
         * could be done via a sperate thread but for just sending one
         * command it is not necessary
         */
        receive(client); // password:
        send(client, password);
        receive(client); // welcome text
        send(client, cmdString);
        Thread.sleep(1000L); // response not needed - may be interesting
                    // for reading status
        client.disconnect();

      } catch (Exception e) {
        logger.warn("Error processing command", e);
      }
    }

    private void send(TelnetClient client, String data) {
      logger.trace("Sending data ({})...", data);
      try {
        data += "\r\n";
        client.getOutputStream().write(data.getBytes());
        client.getOutputStream().flush();
      } catch (IOException e) {
        logger.warn("Error sending data", e);
      }
    }

    private String receive(TelnetClient client) {
      StringBuffer strBuffer;
      try {
        strBuffer = new StringBuffer();

        byte[] buf = new byte[4096];
        int len = 0;

        Thread.sleep(750L);

        while ((len = client.getInputStream().read(buf)) != 0) {
          strBuffer.append(new String(buf, 0, len));

          Thread.sleep(750L);

          if (client.getInputStream().available() == 0)
            break;
        }

        return strBuffer.toString();
       
      } catch (Exception e) {
        logger.warn("Error receiving data", e);
      }

      return null;
    }
  }
 
  /**
   * This is the thread that does the real work
   *
   * @author Kai Kreuzer
   *
   */
  private static class MonitorThread extends Thread {

    /** the active TCP connection */
    private Socket connection;

    /** flag to notify the thread to terminate */
    private boolean interrupted = false;

    /** retry interval in ms, if connection fails */
    private long waitBeforeRetry = 60000L;

    private EventPublisher eventPublisher;
    private Collection<FritzboxBindingProvider> providers;

    public MonitorThread(EventPublisher eventPublisher,
        Collection<FritzboxBindingProvider> providers) {
      this.eventPublisher = eventPublisher;
      this.providers = providers;
    }

    /**
     * Notifies the thread to terminate itself. The current connection will
     * be closed.
     */
    public void interrupt() {
      this.interrupted = true;
      if (connection != null) {
        try {
          connection.close();
        } catch (IOException e) {
          logger.warn("Existing connection to FritzBox cannot be closed", e);
        }
      }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void run() {

      while (!interrupted) {
        while (ip == null) {
          // if we don't have an IP, let's wait
          try {
            sleep(1000L);
          } catch (InterruptedException e) {
            interrupted = true;
            break;
          }
        }
        if (ip != null) {
          BufferedReader reader = null;
          try {
            logger.info("Attempting connection to FritzBox on {}:{}...", ip, MONITOR_PORT);
            connection = new Socket(ip, MONITOR_PORT);
            reader = new BufferedReader(new InputStreamReader(
                connection.getInputStream()));
            // reset the retry interval
            waitBeforeRetry = 60000L;
          } catch (Exception e) {
            logger.warn("Error attempting to connect to FritzBox. Retrying in " + waitBeforeRetry / 1000L + "s.", e);
            try {
              Thread.sleep(waitBeforeRetry);
            } catch (InterruptedException ex) {
              interrupted = true;
            }
            // wait another more minute the next time
            waitBeforeRetry += 60000L;
          }
          if (reader != null) {
            logger.info("Connected to FritzBox on {}:{}", ip, MONITOR_PORT);
            while (!interrupted) {
              try {
                String line = reader.readLine();
                if (line != null) {
                  MonitorEvent event = parseMonitorEvent(line);
                  processMonitorEvent(event);
                  try {
                    // wait a moment, so that rules can be
                    // processed
                    // see
                    // http://knx-user-forum.de/openhab/25024-bug-im-fritzbox-binding.html
                    sleep(100L);
                  } catch (InterruptedException e) {
                  }
                }
              } catch (IOException e) {
                logger.error("Lost connection to FritzBox", e);
                break;
              }
            }
          }
        }
      }
    }

    /**
     * Parses the string that was received from the FritzBox
     *
     * @param line
     *            the received string
     * @return the parse result
     */
    private MonitorEvent parseMonitorEvent(String line) {
      String[] sections = line.split(";");
      MonitorEvent event = new MonitorEvent();
      event.timestamp = sections[0];
      event.eventType = sections[1];
      event.connectionId = sections[2];

      if (event.eventType.equals("RING")) {
        event.externalNo = sections[3];
        event.internalNo = sections[4];
        event.connectionType = sections[5];
      } else if (event.eventType.equals("CONNECT")) {
        event.line = sections[3];
        event.externalNo = sections[4];
      } else if (event.eventType.equals("CALL")) {
        event.line = sections[3];
        event.internalNo = sections[4];
        event.externalNo = sections[5];
        event.connectionType = sections[6];
      }
      return event;
    }

    /**
     * Processes a monitor event.
     *
     * @param event
     *            the event to process
     */
    private void processMonitorEvent(MonitorEvent event) {
      if (event.eventType.equals("RING")) {
        handleEventType(event, FritzboxBindingProvider.TYPE_INBOUND);
      }
      if (event.eventType.equals("CALL")) {
        handleEventType(event, FritzboxBindingProvider.TYPE_OUTBOUND);
      }
      if (event.eventType.equals("CONNECT")
          || event.eventType.equals("DISCONNECT")) {
        handleEventType(event, FritzboxBindingProvider.TYPE_INBOUND);
        handleEventType(event, FritzboxBindingProvider.TYPE_ACTIVE);
        handleEventType(event, FritzboxBindingProvider.TYPE_OUTBOUND);
      }
    }

    /**
     * Processes a monitor event for a given binding type
     *
     * @param event
     *            the monitor event to process
     * @param bindingType
     *            the binding type of the items to process
     */
    private void handleEventType(MonitorEvent event, String bindingType) {
      for (FritzboxBindingProvider provider : providers) {
        for (String itemName : provider
            .getItemNamesForType(bindingType)) {
          Class<? extends Item> itemType = provider
              .getItemType(itemName);

          org.openhab.core.types.State state = null;
          if (event.eventType.equals("DISCONNECT")) {
            state = itemType.isAssignableFrom(SwitchItem.class) ? OnOffType.OFF
                : CallType.EMPTY;
          } else if (event.eventType.equals("CONNECT")) {
            if (bindingType
                .equals(FritzboxBindingProvider.TYPE_ACTIVE)) {
              state = itemType.isAssignableFrom(SwitchItem.class) ? OnOffType.ON
                  : new CallType(event.externalNo, event.line);
            } else {
              state = itemType.isAssignableFrom(SwitchItem.class) ? OnOffType.OFF
                  : CallType.EMPTY;
            }
          } else if (event.eventType.equals("RING")
              && bindingType
                  .equals(FritzboxBindingProvider.TYPE_INBOUND)) {
            state = itemType.isAssignableFrom(SwitchItem.class) ? OnOffType.ON
                : new CallType(event.externalNo,
                    event.internalNo);
          } else if (event.eventType.equals("CALL")
              && bindingType
                  .equals(FritzboxBindingProvider.TYPE_OUTBOUND)) {
            state = itemType.isAssignableFrom(SwitchItem.class) ? OnOffType.ON
                : new CallType(event.internalNo,
                    event.externalNo);
          }
          if (state != null) {
            eventPublisher.postUpdate(itemName, state);
          }
        }
      }
    }

    /**
     * Class representing a monitor event received from the FritzBox. Not
     * all attributes are used for the moment, but might be useful for
     * future extensions.
     *
     * @author Kai Kreuzer
     *
     */
    @SuppressWarnings("unused")
    private static class MonitorEvent {
      public String timestamp;
      public String eventType;
      public String connectionId;
      public String externalNo;
      public String internalNo;
      public String connectionType;
      public String line;
    }
  }

  /**
   * A quartz scheduler job to simply do a reconnection to the FritzBox.
   */
  public static class ReconnectJob implements Job {

    public void execute(JobExecutionContext arg0)
        throws JobExecutionException {
      INSTANCE.conditionalDeActivate();
    }

  }

  @Override
  protected void execute() {

    if (password == null)
      return;
    else if (password.trim().isEmpty())
      return;
   
    try {
      TelnetClient client = null ;
     

      for (FritzboxBindingProvider provider : providers) {
        for (String item : provider.getItemNames()) {
          String query = null;

          String type = provider.getType(item);
          if (queryMap.containsKey(type)) {
            query = queryMap.get(type);
          } else if (type.startsWith("tam")) {
            query = "ctlmgr_ctl r tam settings/"
                + type.toUpperCase() + "/Active";
          } else if (type.startsWith("query")) {
            query = type.substring(type.indexOf(":") + 1).trim();
          }else
            continue;

          if (client == null){
            client = new TelnetClient();
            client.connect(ip);
            receive(client);
            send(client, password);
            receive(client);
          }
         
          send(client, query);

          String answer = receive(client);
          String[] lines = answer.split("\r\n");

          if (lines.length >= 2) {
            answer = lines[1].trim();
          }

          Class<? extends Item> itemType = provider.getItemType(item);

          org.openhab.core.types.State state = null;

          if (itemType.isAssignableFrom(SwitchItem.class)) {
            if (answer.equals("1"))
              state = OnOffType.ON;
            else
              state = OnOffType.OFF;
          } else if (itemType.isAssignableFrom(NumberItem.class)) {
            state = new DecimalType(answer);
          } else if (itemType.isAssignableFrom(StringItem.class)) {
            state = new StringType(answer);
          }

          if (state != null)
            eventPublisher.postUpdate(item, state);

        }
      }
      if (client != null)
        client.disconnect();
    } catch (Exception e) {
      logger.warn("Could not get item state ", e);
    }

  }

  @Override
  protected long getRefreshInterval() {
    return 60000L;
  }

  @Override
  protected String getName() {
    return "FritzBox refresh Service";
  }

  /**
   * Send line via Telnet to FritzBox
   *
   * @param client
   *            the telnet client
   * @param data
   *            the data to send
   */
  private static void send(TelnetClient client, String data) {
    try {
      data += "\r\n";
      client.getOutputStream().write(data.getBytes());
      client.getOutputStream().flush();
    } catch (IOException e) {
      logger.warn("Error sending data", e);
    }
  }

  /**
   * Receive answer from FritzBox - careful! This blocks if there is no answer
   * from FritzBox
   *
   * @param client
   *            the telnet client
   * @return
   */
  private static String receive(TelnetClient client) {

    StringBuffer strBuffer;
    try {
      strBuffer = new StringBuffer();

      byte[] buf = new byte[4096];
      int len = 0;

      Thread.sleep(750L);

      while ((len = client.getInputStream().read(buf)) != 0) {
        strBuffer.append(new String(buf, 0, len));

        Thread.sleep(750L);

        if (client.getInputStream().available() == 0)
          break;

      }

      return strBuffer.toString();

    } catch (Exception e) {
      logger.warn("Error receiving data", e);
    }

    return null;
  }

}
TOP

Related Classes of org.openhab.binding.fritzbox.internal.FritzboxBinding$MonitorThread$MonitorEvent

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.