Package org.openhab.binding.knx.internal.bus

Source Code of org.openhab.binding.knx.internal.bus.KNXBinding$DatapointInitializer

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.IllegalClassException;
import org.openhab.binding.knx.config.KNXBindingProvider;
import org.openhab.binding.knx.config.KNXTypeMapper;
import org.openhab.binding.knx.internal.connection.KNXConnectionListener;
import org.openhab.binding.knx.internal.connection.KNXConnection;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import tuwien.auto.calimero.DetachEvent;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.datapoint.Datapoint;
import tuwien.auto.calimero.exception.KNXException;
import tuwien.auto.calimero.exception.KNXIllegalArgumentException;
import tuwien.auto.calimero.process.ProcessCommunicator;
import tuwien.auto.calimero.process.ProcessEvent;
import tuwien.auto.calimero.process.ProcessListener;

/**
* This is the central class that takes care of the event exchange between openHAB and KNX.
* It is fully connected (read and write) to the openHAB event bus and also has write access
* to KNX while as well listening for incoming KNX messages.
*
* The received messages are converted into the right format for the other bus and published
* to it.
*
* @author Kai Kreuzer
* @since 0.3.0
*
*/
public class KNXBinding extends AbstractBinding<KNXBindingProvider>
  implements ProcessListener, KNXConnectionListener {

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

  /** to keep track of all KNX type mappers */
  protected Collection<KNXTypeMapper> typeMappers = new HashSet<KNXTypeMapper>();
 
  /**
   * used to store events that we have sent ourselves; we need to remember them for not reacting to them
   */
  private List<String> ignoreEventList = new ArrayList<String>();

  /**
   * keeps track of all datapoints for which we should send a read request to the KNX bus
   */
  private Map<Datapoint, Integer> datapointsToInitialize = Collections.synchronizedMap(new HashMap<Datapoint, Integer>());
 

  /** the datapoint initializer, which runs in a separate thread */
  private DatapointInitializer initializer;
 

  public void activate(ComponentContext componentContext) {
    logger.trace("KNXBinding: activating");
    KNXConnection.addConnectionEstablishedListener(this);
    initializer = new DatapointInitializer();
    initializer.start();
  }

  public void deactivate(ComponentContext componentContext) {
    logger.trace("KNXBinding: deactivating");
    KNXConnection.removeConnectionEstablishedListener(this);
    for (KNXBindingProvider provider : providers) {
      provider.removeBindingChangeListener(this);
    }
    providers.clear();
    initializer.setInterrupted(true);
    KNXConnection.disconnect();
  }
 
  public void addKNXTypeMapper(KNXTypeMapper typeMapper) {
    this.typeMappers.add(typeMapper);
  }

  public void removeKNXTypeMapper(KNXTypeMapper typeMapper) {
    this.typeMappers.remove(typeMapper);
  }

  /* (non-Javadoc)
   * @see org.openhab.core.binding.AbstractBinding#internalReceiveCommand(java.lang.String, org.openhab.core.types.Command)
   */
  @Override
  protected void internalReceiveCommand(String itemName, Command command) {
    logger.trace("Received command (item='{}', command='{}')", itemName, command.toString());
    if (!isEcho(itemName, command)) {
      writeToKNX(itemName, command);
    }
  }

  /* (non-Javadoc)
   * @see org.openhab.core.binding.AbstractBinding#internalReceiveUpdate(java.lang.String, org.openhab.core.types.State)
   */
  @Override
  protected void internalReceiveUpdate(String itemName, State newState) {
    logger.trace("Received update (item='{}', state='{}')", itemName, newState.toString());
    if (!isEcho(itemName, newState)) {
      writeToKNX(itemName, newState);
    }
  }
 
  private boolean isEcho(String itemName, Type type) {
    String ignoreEventListKey = itemName + type.toString();
    if (ignoreEventList.contains(ignoreEventListKey)) {
      ignoreEventList.remove(ignoreEventListKey);
      logger.trace("We received this event (item='{}', state='{}') from KNX, so we don't send it back again -> ignore!", itemName, type.toString());
      return true;
    }
    else {
      return false;
    }
  }

  private void writeToKNX(String itemName, Type value) {
    Iterable<Datapoint> datapoints = getDatapoints(itemName, value.getClass());
    if (datapoints != null) {
      ProcessCommunicator pc = KNXConnection.getCommunicator();
      if (pc != null) {
        for (Datapoint datapoint : datapoints) {
          try {
            pc.write(datapoint, toDPTValue(value, datapoint.getDPT()));
            logger.debug("Wrote value '{}' to datapoint '{}'", value, datapoint);
          } catch (KNXException e) {
            logger.warn("Value '{}' could not be sent to the KNX bus using datapoint '{}' - retrying one time: {}",
                new Object[]{value, datapoint, e.getMessage()});
            try {
              // do a second try, maybe the reconnection was successful
              pc = KNXConnection.getCommunicator();
              pc.write(datapoint, toDPTValue(value, datapoint.getDPT()));
              logger.debug("Wrote value '{}' to datapoint '{}' on second try", value, datapoint);
            } catch (KNXException e1) {
              logger.error("Value '{}' could not be sent to the KNX bus using datapoint '{}' - giving up after second try: {}",
                new Object[]{value, datapoint, e1.getMessage()});
            }
          }
        }
      }
    }
  }

 
  /* (non-Javadoc)
   * @see tuwien.auto.calimero.process.ProcessListener#groupWrite(tuwien.auto.calimero.process.ProcessEvent)
   */
  @Override
  public void groupWrite(ProcessEvent e) {
    readFromKNX(e);
  }
 
  /* (non-Javadoc)
   * @see tuwien.auto.calimero.process.ProcessListener#detached(tuwien.auto.calimero.DetachEvent)
   */
  @Override
  public void detached(DetachEvent e) {
    logger.error("Received detach Event.");
  }
 
 
  /**
   * Handles the given {@link ProcessEvent}. After finding the corresponding
   * Item (by iterating through all known group addresses) this Item is updated.
   * Each item is added to a special list to identify and avoid echo's in
   * the <code>receiveUpdate</code> and <code>receiveCommand</code> methods. 
   * 
   * @param e the {@link ProcessEvent} to handle.
   */
  private void readFromKNX(ProcessEvent e) {
    try {
      GroupAddress destination = e.getDestination();
      byte[] asdu = e.getASDU();
      if (asdu.length==0) {
        return;
      }
      for (String itemName : getItemNames(destination)) {
        Iterable<Datapoint> datapoints = getDatapoints(itemName, destination);
        if (datapoints != null) {
          for (Datapoint datapoint : datapoints) {
            Type type = getType(datapoint, asdu);         
            if (type!=null) {
              // we need to make sure that we won't send out this event to
              // the knx bus again, when receiving it on the openHAB bus
              ignoreEventList.add(itemName + type.toString());
              logger.trace("Added event (item='{}', type='{}') to the ignore event list", itemName, type.toString());
             
              if (type instanceof Command && isCommandGA(destination)) {
                eventPublisher.postCommand(itemName, (Command) type);
              } else if (type instanceof State) {
                eventPublisher.postUpdate(itemName, (State) type);
              } else {
                throw new IllegalClassException("Cannot process datapoint of type " + type.toString());
              }               

              logger.trace("Processed event (item='{}', type='{}', destination='{}')", itemName, type.toString(), destination.toString());
              return;
            }
            else {
              final char[] hexCode = "0123456789ABCDEF".toCharArray();
                  StringBuilder sb = new StringBuilder(2+asdu.length * 2);
                  sb.append("0x");
                  for (byte b : asdu) {
                      sb.append(hexCode[(b >> 4) & 0xF]);
                      sb.append(hexCode[(b & 0xF)]);
                  }

              logger.debug("Ignoring KNX bus data: couldn't transform to an openHAB type (not supported). Destination='{}', datapoint='{}', data='{}'",
                  new Object[] {destination.toString(), datapoint.toString(), sb.toString() });
              return;
            }
          }
        }
      }
      logger.debug("Received telegram for unknown group address {}", destination.toString());
    } catch(RuntimeException re) {
      logger.error("Error while receiving event from KNX bus: " + re.toString());
    }
  }
 

  /* (non-Javadoc)
   * @see org.openhab.core.binding.AbstractBinding#bindingChanged(org.openhab.core.binding.BindingProvider, java.lang.String)
   */
  @Override
  public void bindingChanged(BindingProvider provider, String itemName) {
    logger.trace("bindingChanged() for item {} msg received.", itemName);
    if (provider instanceof KNXBindingProvider) {
      KNXBindingProvider knxProvider = (KNXBindingProvider) provider;
      for (Datapoint datapoint : knxProvider.getReadableDatapoints()) {
        if(datapoint.getName().equals(itemName)) {
          logger.debug("Adding item {} to initialization list.", itemName);
          datapointsToInitialize.put(datapoint, 0);
        }
      }
    }
  }

  /* (non-Javadoc)
   * @see org.openhab.core.binding.AbstractBinding#allBindingsChanged(org.openhab.core.binding.BindingProvider)
   */
  @Override
  public void allBindingsChanged(BindingProvider provider) {
    logger.trace("allBindingsChanged() msg received. Initializing readable DPs.");
    if (provider instanceof KNXBindingProvider) {
      KNXBindingProvider knxProvider = (KNXBindingProvider) provider;
      for (Datapoint datapoint : knxProvider.getReadableDatapoints()) {
        datapointsToInitialize.put(datapoint, 0);
      }
    }
  }
 

  /* (non-Javadoc)
   * @see org.openhab.binding.knx.internal.connection.KNXConnectionListener#connectionEstablished()
   */
  @Override
  public void connectionEstablished() {
    logger.trace("connectionEstablished() msg received. Initializing readable DPs.");
    for (KNXBindingProvider knxProvider : providers) {
      for (Datapoint datapoint : knxProvider.getReadableDatapoints()) {
        datapointsToInitialize.put(datapoint, 0);
      }
    }
  }
 
  /**
   * Determines whether the given <code>groupAddress</code> is the address which
   * will be interpreted as the command type. This method iterates over all
   * registered KNX binding providers to find the result.

   * @param groupAddress the group address to check
   * @return true, if it is a command GA
   */
  private boolean isCommandGA(GroupAddress groupAddress) {
    for (KNXBindingProvider provider : providers) {
      if(!provider.isCommandGA(groupAddress)) {
        return false;
      }
    }
    return true;
  }
 
  /**
   * Returns all listening item names. This method iterates over all registered KNX binding providers and aggregates
   * the result.
   *
   * @param groupAddress
   *            the group address that the items are listening to
   * @return an array of all listening items
   */
  private String[] getItemNames(GroupAddress groupAddress) {
    List<String> itemNames = new ArrayList<String>();
    for (KNXBindingProvider provider : providers) {
      for (String itemName : provider.getListeningItemNames(groupAddress)) {
        itemNames.add(itemName);
      }
    }
    return itemNames.toArray(new String[itemNames.size()]);
  }

  /**
   * Returns the datapoints for a given item and group address. This method iterates over all registered KNX binding
   * providers to find the result.
   *
   * @param itemName
   *            the item name for the datapoint
   * @param groupAddress
   *            the group address associated to the datapoint
   * @return the datapoints which corresponds to the given item and group address
   */
  private Iterable<Datapoint> getDatapoints(String itemName, GroupAddress groupAddress) {
    for (KNXBindingProvider provider : providers) {
      Iterable<Datapoint> datapoints = provider.getDatapoints(itemName, groupAddress);
      if (datapoints != null)
        return datapoints;
    }
    return null;
  }

  /**
   * Transforms the raw KNX bus data of a given datapoint into an openHAB type (command or state)
   *
   * @param datapoint
   *            the datapoint to which the data belongs
   * @param asdu
   *            the byte array of the raw data from the KNX bus
   * @return the openHAB command or state that corresponds to the data
   */
  private Type getType(Datapoint datapoint, byte[] asdu) {
    for (KNXTypeMapper typeMapper : typeMappers) {
      Type type = typeMapper.toType(datapoint, asdu);
      if (type != null)
        return type;
    }
    return null;
  }

  /**
   * Returns the datapoints for a given item and type class. This method iterates over all registered KNX binding
   * providers to find the result.
   *
   * @param itemName
   *            the item name for the datapoints
   * @param typeClass
   *            the type class associated to the datapoints
   * @return the datapoints which corresponds to the given item and type class
   */
  private Iterable<Datapoint> getDatapoints(final String itemName, final Class<? extends Type> typeClass) {
    Set<Datapoint> datapoints = new HashSet<Datapoint>();
    for (KNXBindingProvider provider : providers) {
      for (Datapoint datapoint : provider.getDatapoints(itemName, typeClass)) {
        datapoints.add(datapoint);
      }
    }
    return datapoints;
  }

  /**
   * Transforms an openHAB type (command or state) into a datapoint type value for the KNX bus.
   *
   * @param type
   *            the openHAB command or state to transform
   * @param dpt
   *         the datapoint type to which should be converted
   *
   * @return the corresponding KNX datapoint type value as a string
   */
  private String toDPTValue(Type type, String dpt) {
    for (KNXTypeMapper typeMapper : typeMappers) {
      String value = typeMapper.toDPTValue(type, dpt);
      if (value != null)
        return value;
    }
    return null;
  }

  /**
   * The DatapointInitializer runs as a separate thread. Whenever new KNX bindings are added, it takes care that read
   * requests are sent to all new datapoints, which support this request. By this, the initial status can be
   * determined and one does not have to stay in an "undefined" state until the first telegram is sent on the NX bus
   * for this datapoint. As there might be hundreds of datapoints added at the same time and we do not want to flood
   * the KNX bus with read requests, we wait a configurable period of milliseconds between two requests. As a result,
   * this might be quite long running and thus is executed in its own thread.
   *
   * @author Kai Kreuzer
   * @since 0.3.0
   *
   */
  private class DatapointInitializer extends Thread {
   
    private boolean interrupted = false;
   
    public DatapointInitializer() {
      super("KNX datapoint initializer");
    }

    public void setInterrupted(boolean interrupted) {
      this.interrupted = interrupted;
    }

    /* (non-Javadoc)
     * @see java.lang.Thread#run()
     */
    @Override
    public void run() {
      // as long as no interrupt is requested, continue running
      while (!interrupted && !KNXConnection.sShutdown) {
        if (datapointsToInitialize.size() > 0) {
          // we first clone the map, so that it stays unmodified
          HashMap<Datapoint,Integer> clonedMap =
            new HashMap<Datapoint, Integer>(datapointsToInitialize);
          initializeDatapoints(clonedMap);
        }
        // just wait before looping again
        try {
          sleep(1000L);
        } catch (InterruptedException e) {
          interrupted = true;
        }
      }
    }

    private void initializeDatapoints(HashMap<Datapoint,Integer> clonedMap) {
      for (Datapoint datapoint : clonedMap.keySet()) {
        try {
          ProcessCommunicator pc = KNXConnection.getCommunicator();
          if (pc != null) {
            logger.debug("Sending read request to KNX for item {}", datapoint.getName());
            pc.read(datapoint);
            logger.debug("Removing item {} from initialization queue", datapoint.getName());
            datapointsToInitialize.remove(datapoint);
          }
        } catch (KNXException e) {
          logger.warn("Cannot read value for item '{}' from KNX bus: {}", new Object[] { datapoint.getName(), e.getMessage() });
          increaseReadLimitCounter(datapoint);
        } catch (KNXIllegalArgumentException e) {
          logger.warn("Error sending KNX read request for '{}': {}", new Object[] { datapoint.getName(), e.getMessage() });
          increaseReadLimitCounter(datapoint);
        } catch (InterruptedException e) {
          logger.warn("Cannot read value for item '{}' from KNX bus: {}", new Object[] { datapoint.getName(), e.getMessage() });
          increaseReadLimitCounter(datapoint);
        }

        int retriesLimit = KNXConnection.getReadRetriesLimit();
        // it is possible that a key contained in the clonedMap disappeared
        // from the original datapointMap!
        int retriesCounter = datapointsToInitialize.get(datapoint) != null ?
                   datapointsToInitialize.get(datapoint) : Integer.MIN_VALUE;
        if (retriesLimit < retriesCounter) {
          datapointsToInitialize.remove(datapoint);
          logger.debug("Giving up initialization of item {} - retries ({}) exeeded.", datapoint.getName(), retriesLimit);
        }

        long readingPause = KNXConnection.getReadingPause();
        if (readingPause > 0) {
          try {
            sleep(readingPause);
          } catch (InterruptedException e) {
            logger.debug("KNX reading pause has been interrupted: {}", e.getMessage());
          }
        }
        if(KNXConnection.sShutdown) {
          return;
        }
      }
    }

    private synchronized void increaseReadLimitCounter(Datapoint datapoint) {
      if (datapointsToInitialize.containsKey(datapoint)) {
        Integer counter = datapointsToInitialize.get(datapoint);
        datapointsToInitialize.put(datapoint, counter + 1);
      }
    }   
   
  }
 

}
TOP

Related Classes of org.openhab.binding.knx.internal.bus.KNXBinding$DatapointInitializer

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.