org.openhab.binding.paradox.internal.ParadoxBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.paradox.internal.ParadoxBinding.java

Source

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

import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.DatatypeConverter;

import org.apache.commons.lang.StringUtils;
import org.openhab.binding.paradox.ParadoxBindingProvider;
import org.openhab.binding.paradox.connector.ParadoxConnector;
import org.openhab.binding.paradox.connector.ParadoxInterface;
import org.openhab.binding.paradox.connector.ParadoxSerialConnector;
import org.openhab.binding.paradox.connector.ParadoxSerialReader;
import org.openhab.binding.paradox.connector.ParadoxSerialSimulator;
import org.openhab.binding.paradox.connector.ParadoxInterface.ZoneStatus;
import org.openhab.binding.paradox.protocol.ParadoxCommandType;
import org.openhab.binding.paradox.protocol.ParadoxDataParser;
import org.openhab.binding.paradox.protocol.ParadoxDataParserRule;

import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingConfig;

import org.openhab.core.items.Item;
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.transform.TransformationHelper;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;

import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Binding which communicates with Paradox alarm systems.
 * 
 * 
 * @author Pauli Anttila
 * @author Maciej Piliski
 * @since 1.5.0
 */
public class ParadoxBinding extends AbstractActiveBinding<ParadoxBindingProvider> implements ManagedService {

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

    /**
     * configuration for communication:
     * PTR - Paradox printer port, older (PTR1) version with messaging only or 
     * newer (PTR3) with full home automation support
     * serial port - port to which PTR is attached to (eg. COM1 on Windows, /dev/ttyS0 on Linux, etc.)  
     */
    public enum PTRtype {
        PTR1, PTR3, SIMULATE;
    }

    private final static PTRtype DEFAULT_PTR = PTRtype.SIMULATE;
    private PTRtype deviceType = DEFAULT_PTR;
    private String serialPort = null;
    /**
     * granularity - the interval to find new refresh candidates (defaults to 6000 milliseconds)
     */
    private final static int DEFAULT_GRAN = 6000;
    private int granularity = DEFAULT_GRAN;

    /** Thread to parse data from Paradox devices */
    private ParadoxDataParser dataParser = null;

    /** Thread to handle messages from Paradox devices */
    private MessageListener messageListener = null;

    private Map<String, Item> paradoxMap = new HashMap<String, Item>();
    private Map<String, Long> lastUpdateMap = new HashMap<String, Long>();

    protected Map<String, DeviceConfig> deviceConfigCache = null;

    /**
     * RegEx to validate a config
     * <code>'^(.*?)\\.(type|serialPort|refresh)$'</code>
     */
    private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.(type|serialPort|refresh)$");

    public void activate() {
        logger.debug("Activate");
    }

    public void deactivate() {
        logger.debug("Deactivate");
        messageListener.setInterrupted(true);
        //      closeConnection();
    }

    /*   private void closeConnection() {
          if (deviceConfigCache != null) {
     // close all connections
     for (Entry<String, DeviceConfig> entry : deviceConfigCache.entrySet()) {
        DeviceConfig deviceCfg = entry.getValue();
        if (deviceCfg != null) {
           ParadoxInterface device = deviceCfg.getConnection();
        
           if (device != null) {
              try {
                 logger.debug("Closing connection to device '{}' ", deviceCfg.deviceId);
                 device.disconnect();
              } catch (ParadoxException e) {
                 logger.error(
                       "Error occured when closing connection to device '{}'",
                       deviceCfg.deviceId);
              }
           }
        }
     }
        
     deviceConfigCache = null;
          }
       }
    */
    /**
     * {@inheritDoc}
     */
    @Override
    protected long getRefreshInterval() {
        return granularity;
    }

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

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

        /*
         * 
         */
        for (ParadoxBindingProvider provider : providers) {
            /*
             * 
             */
            for (String itemName : provider.getInBindingItemNames()) {

                int refreshInterval = provider.getRefreshInterval(itemName);

                Long lastUpdateTimeStamp = lastUpdateMap.get(itemName);
                if (lastUpdateTimeStamp == null) {
                    lastUpdateTimeStamp = 0L;
                }

                long age = System.currentTimeMillis() - lastUpdateTimeStamp;
                boolean needsUpdate = age >= refreshInterval;

                if (needsUpdate) {
                    String deviceId = provider.getDeviceId(itemName);

                    logger.debug("item '{}' is about to be refreshed now", itemName);

                    ParadoxCommandType commmandType = provider.getCommandType(itemName);
                    Class<? extends Item> itemType = provider.getItemType(itemName);

                    State state = queryDataFromDevice(deviceId, commmandType, itemType);

                    if (state != null) {
                        eventPublisher.postUpdate(itemName, state);
                    } else {
                        logger.error("No response received from command '{}'", commmandType);
                    }

                    lastUpdateMap.put(itemName, System.currentTimeMillis());
                }
            }
        }
    }

    private State queryDataFromDevice(String deviceId, ParadoxCommandType commmandType,
            Class<? extends Item> itemType) {

        DeviceConfig device = deviceConfigCache.get(deviceId);

        if (device == null) {
            logger.error("Could not find device '{}'", deviceId);
            return null;
        }

        ParadoxInterface remoteController = device.getConnection();

        if (remoteController == null) {
            logger.error("Could not find device '{}'", deviceId);
            return null;
        }

        try {
            if (remoteController.isConnected() == false)
                remoteController.connect();

            switch (commmandType) {
            case ZONE1:
                ZoneStatus zoneState1 = remoteController.getZoneStatus(1);
                return new DecimalType(zoneState1.toInt());
            case ZONE2:
                ZoneStatus zoneState2 = remoteController.getZoneStatus(2);
                return new DecimalType(zoneState2.toInt());
            case ZONE3:
                ZoneStatus zoneState3 = remoteController.getZoneStatus(3);
                return new DecimalType(zoneState3.toInt());
            case ZONE10:
                ZoneStatus zoneState10 = remoteController.getZoneStatus(10);
                return new DecimalType(zoneState10.toInt());
            /*         case USER1:
                        ZoneLabel userState1 = remoteController.getUserStatus();
                        return new StringType(userState1.toString());
                     case USER2:
                        ZoneLabel userState2 = remoteController.getUserStatus();
                        return new StringType(userState2.toString());
            */
            case ERR_CODE:
                int err = remoteController.getError();
                logger.warn("Get '{}' not implemented!", commmandType.toString());
                return new DecimalType(err);
            case ERR_MSG:
                String errString = remoteController.getErrorString();
                logger.warn("Get '{}' not implemented!", commmandType.toString());
                return new StringType(errString);
            case POWER_STATE:
                int pwr = remoteController.getPowerState();
                logger.warn("Get '{}' not implemented!", commmandType.toString());
                return new DecimalType(pwr);
            default:
                logger.warn("Unknown '{}' command!", commmandType);
                return null;
            }

        } catch (ParadoxException e) {
            logger.warn("Couldn't execute command '{}', {}", commmandType.toString(), e);

        } catch (Exception e) {
            logger.warn("Couldn't create state of type '{}'", itemType.toString());
            return null;
        }

        return null;
    }

    /**
     * @{inheritDoc
     */
    /*
    @Override
    public void internalReceiveCommand(String itemName, Command command) {
       ParadoxBindingProvider provider = findFirstMatchingBindingProvider( itemName, command);
        
       if (provider == null) {
     logger.warn(
           "doesn't find matching binding provider [itemName={}, command={}]",
           itemName, command);
     return;
       }
        
       if (provider.isOutBinding(itemName)) {
     ParadoxCommandType commmandType = provider.getCommandType(itemName);
     String deviceId = provider.getDeviceId(itemName);
     if (commmandType != null) {
        sendDataToDevice(deviceId, commmandType, command);
     }
       } else {
     logger.warn("itemName={} is not out binding", itemName);
       }
    }
        
    private void sendDataToDevice(String deviceId, ParadoxCommandType commmandType, Command command) {
       DeviceConfig device = deviceConfigCache.get(deviceId);
        
       if (device == null) {
     logger.error("Could not find device '{}'", deviceId);
     return;
       }
        
       ParadoxInterface remoteController = device.getConnection();
        
       if (remoteController == null) {
     logger.error("Could not find device '{}'", deviceId);
     return;
       }
        
       try {
        
     if (remoteController.isConnected() == false)
        remoteController.connect();
        
     switch (commmandType) {
    /*      case ZONE3:
        remoteController.setZoneStatus(((DecimalType) command).intValue());
        break;
     case ZONE10:
        remoteController.setZoneStatus(10);
        break;
     case USER1:
        remoteController.setUserStatus(((DecimalType) command).intValue());
        break;
     case USER2:
        remoteController.setUserStatus(((DecimalType) command).intValue());
        break;
    *      case ERR_CODE:
        logger.error("'{}' is read only parameter", commmandType);
        break;
     case ERR_MSG:
        logger.error("'{}' is read only parameter", commmandType);
        break;
     case POWER_STATE:
        remoteController.setPowerState((command == OnOffType.ON ? 0 : 1));
        logger.error("'{}' is read only parameter", commmandType);
        break;
     default:
        logger.warn("Unknown '{}' command!", commmandType);
        break;
     }
        
       } catch (ParadoxException e) {
     logger.error("Couldn't execute command '{}', {}", commmandType, e);
        
       }
    }
        
    /**
     * Find the first matching {@link ExecBindingProvider} according to
     * <code>itemName</code> and <code>command</code>. If no direct match is
     * found, a second match is issued with wilcard-command '*'.
     * 
     * @param itemName
     * @param command
     * 
     * @return the matching binding provider or <code>null</code> if no binding
     *         provider could be found
     *
    private ParadoxBindingProvider findFirstMatchingBindingProvider(
     String itemName, Command command) {
        
       ParadoxBindingProvider firstMatchingProvider = null;
        
       for (ParadoxBindingProvider provider : this.providers) {
     ParadoxCommandType commmandType = provider.getCommandType(itemName);
        
     if (commmandType != null) {
        firstMatchingProvider = provider;
        break;
     }
       }
        
       return firstMatchingProvider;
    }
    */
    /**
     * @{inheritDoc
     */
    @Override
    public void updated(Dictionary<String, ?> config) throws ConfigurationException {

        logger.debug("Configuration updated, config {}", config != null ? true : false);

        if (config != null) {
            /*         if (deviceConfigCache == null) {
                        deviceConfigCache = new HashMap<String, DeviceConfig>();
                     }
            */
            HashMap<String, ParadoxDataParserRule> parsingRules = new HashMap<String, ParadoxDataParserRule>();

            String granularityString = (String) config.get("refresh");
            if (StringUtils.isNotBlank(granularityString)) {
                granularity = Integer.parseInt(granularityString);
            }

            Enumeration<String> keys = config.keys();

            while (keys.hasMoreElements()) {
                String key = (String) keys.nextElement();

                // the config-key enumeration contains additional keys that we
                // don't want to process here ...
                if ("service.pid".equals(key)) {
                    continue;
                }

                // better - more Paradox devices...
                /*            Matcher matcher = EXTRACT_CONFIG_PATTERN.matcher(key);
                    
                            if (!matcher.matches()) {
                               logger.warn("given config key '"
                 + key
                 + "' does not follow the expected pattern '<deviceId>.<type|serialPort|refresh>'");
                               continue;
                            }
                    
                            matcher.reset();
                            matcher.find();
                    
                            String deviceId = matcher.group(1);
                    
                            DeviceConfig deviceConfig = deviceConfigCache.get(deviceId);
                    
                            if (deviceConfig == null) {
                               logger.debug("Added new device {}", deviceId);
                               deviceConfig = new DeviceConfig(deviceId);
                               deviceConfigCache.put(deviceId, deviceConfig);
                            }
                    
                            String configKey = matcher.group(2);
                            String value = (String) config.get(key);
                    
                            /**
                             * parsing of valid config:
                             * 
                ############################## Paradox Alarm Binding ##################################
                #
                # Serial port of the first Paradox alarm system to control: 
                # paradox:<devId1>.serialPort=
                #
                # Type of the Paradox printer port, to which we are connecting via serial cable
                # valid options are: PTR1, PTR3, simulate (required)
                # paradox:<devId1>.type=
                #
                # Refresh rate in miliseconds of the in-directional items (optional, defaults to 6000)
                # valid for PTR3 only
                # paradox:<devId1>.refresh=
                    
                # example of valid settings:
                paradox:homealarm.serialPort=COM10
                paradox:homealarm.type=simulate
                paradox:homealarm.refresh=6000
                     
                             *
                            if ("serialPort".equals(configKey)) {
                               deviceConfig.serialPort = value;
                                   
                            } else if ("type".equals(configKey)) {
                               if ("PTR1".equals(value))     deviceConfig.type = PTRtype.PTR1;
                               else if("PTR3".equals(value)) deviceConfig.type = PTRtype.PTR3;
                               else deviceConfig.type = PTRtype.SIMULATE;
                                   
                            } else if ("simulate".equals(configKey)) {
                               if (StringUtils.isNotBlank(value)) {
                                  deviceConfig.type = PTRtype.SIMULATE;
                               }
                            } else if ("refresh".equals(configKey)) {
                               deviceConfig.granularity = Integer.parseInt(value);
                            }
                              else
                               throw new ConfigurationException(configKey,
                                  "the given configKey '" + configKey + "' is unknown");
                            }
                             
                                
                         setProperlyConfigured(true);
                    
                */
                /**
                            ############################## Paradox Alarm Binding ##################################
                            #
                            # Serial port of the Paradox alarm system to control (required): 
                            # paradox.serialPort=
                            #
                            # Type of the Paradox printer port, to which we are connecting via serial cable
                            # valid options are: PTR1, PTR3 (required)  // use 'simulate' for tests only
                            # paradox.type=
                            #
                            # Refresh rate in milliseconds of the in-directional items (optional, defaults to 6000)
                            # valid for PTR3 only
                            # paradox.refresh=
                    
                            # example of valid settings:
                            paradox.serialPort=COM10
                            paradox.type=PTR1
                            paradox.refresh=6000
                */
                String value = (String) config.get(key);

                if ("serialPort".equals(key)) {
                    serialPort = value;

                } else if ("type".equals(key)) {
                    if ("PTR1".equals(value))
                        deviceType = PTRtype.PTR1;
                    else if ("PTR3".equals(value))
                        deviceType = PTRtype.PTR3;
                    else
                        deviceType = PTRtype.SIMULATE;

                } else if ("simulate".equals(key)) {
                    if (StringUtils.isNotBlank(value)) {
                        deviceType = PTRtype.SIMULATE;
                    }
                } else if ("refresh".equals(key)) {
                    granularity = Integer.parseInt(value);
                } else
                    throw new ConfigurationException(key, "the given configKey '" + key + "' is unknown");
            }

            if (parsingRules != null) {

                dataParser = new ParadoxDataParser(parsingRules);
            }

            messageListener = new MessageListener();
            messageListener.start();
        }
    }

    /**
     * Internal data structure which carries the connection details of Paradox device
     */
    static class DeviceConfig {

        String deviceId;
        String serialPort = null;
        PTRtype type = null;
        int granularity = DEFAULT_GRAN;

        ParadoxInterface device = null;

        public DeviceConfig(String deviceId) {
            this.deviceId = deviceId;
        }

        @Override
        public String toString() {
            return "Device [id=" + deviceId + ", type=" + type + "]";
        }

        ParadoxInterface getConnection() {
            if (device == null) {
                if (serialPort != null) {
                    device = new ParadoxInterface(serialPort);
                }
            }
            return device;
        }

    }

    /**
     * The MessageListener runs as a separate thread.
     * 
     * Thread listening message from Paradox devices and send
     * updates to openHAB bus.
     * 
     */
    private class MessageListener extends Thread {

        private boolean interrupted = false;

        MessageListener() {
        }

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

        @Override
        public void run() {

            logger.debug("Paradox message listener started");

            // how to get deviceConfig?????
            //         ParadoxInterface deviceConfig = deviceConfigCache.get("paradox");
            ParadoxConnector connector = null;

            if (serialPort != null && deviceType != null) {

                switch (deviceType) {
                case SIMULATE:
                    connector = new ParadoxSerialSimulator(serialPort);
                    break;
                case PTR1:
                    connector = new ParadoxSerialReader(serialPort);
                    break;
                case PTR3:
                    connector = new ParadoxSerialConnector(serialPort);
                    break;
                }

                try {
                    connector.connect();
                } catch (ParadoxException e) {
                    logger.error("Error occured when connecting to Paradox device", e);
                }

                // as long as no interrupt is requested, continue running
                while (!interrupted) {

                    try {
                        // Wait a packet (blocking)
                        String data = connector.receiveData();
                        //               byte[] bdata = data.getBytes();

                        logger.debug("Received data (len={}): {}", data.length(), data);
                        logger.trace("Received data (len={}): {}", data.length(), data);

                        HashMap<String, Number> vals = dataParser.parseData(data);

                        for (ParadoxBindingProvider provider : providers) {
                            for (String itemName : provider.getItemNames()) {

                                for (Entry<String, Number> entry : vals.entrySet()) {
                                    String key = entry.getKey();
                                    Number value = entry.getValue();

                                    if (key != null && value != null) {

                                        boolean found = false;

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

                                        String variable = provider.getVariable(itemName);

                                        if (variable.equals(key)) {
                                            state = new DecimalType(value.doubleValue());
                                            found = true;

                                        } else if (variable.contains(key) && variable.matches(".*[+-/*^%].*")) {
                                            logger.debug("Eval key={}, variable={}", key, variable);

                                            /*                              String tmp = replaceVariables(vals, variable);
                                                                              
                                                                          try {
                                                                             double result = new DoubleEvaluator().evaluate(tmp);
                                                                             logger.debug("Eval '{}={}={}'", variable, tmp, result);
                                                                             state = new DecimalType(result);
                                                                             found = true;
                                                
                                                                          } catch (Exception e) {
                                                                             logger.error(
                                                                                   "Error occured during data evaluation",
                                                                                   e);
                                                                          }
                                            */ }

                                        if (found) {

                                            state = transformData(provider.getTransformationType(itemName),
                                                    provider.getTransformationFunction(itemName), state);

                                            if (state != null) {
                                                eventPublisher.postUpdate(itemName, state);
                                                break;
                                            }

                                        }

                                    }
                                }
                            }

                        }

                    } catch (ParadoxException e) {

                        logger.error("Error occured when received data from Paradox device", e);
                    }
                }

                try {
                    connector.disconnect();
                } catch (ParadoxException e) {
                    logger.error("Error occured when disconnecting form Paradox device", e);
                }

            }
        }

        public void end() {

            logger.debug("Paradox message listener stopped");

        }
    }

    private String replaceVariables(HashMap<String, Number> vals, String variable) {
        for (Entry<String, Number> entry : vals.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            variable = variable.replace(key, String.valueOf(value));

        }

        return variable;
    }

    /**
     * Transform received data by Transformation service.
     * 
     */
    protected org.openhab.core.types.State transformData(String transformationType, String transformationFunction,
            org.openhab.core.types.State data) {

        if (transformationType != null && transformationFunction != null) {
            String transformedResponse = null;

            /* try { */
            TransformationService transformationService = TransformationHelper
                    .getTransformationService(ParadoxActivator.getContext(), transformationType);
            if (transformationService != null) {
                //               transformedResponse = transformationService.transform(
                //                     transformationFunction, String.valueOf(data));
            } else {
                logger.warn("couldn't transform response because transformationService of type '{}' is unavailable",
                        transformationType);
            }

            /* } catch (ParadoxException e) {
               logger.error(
              "transformation throws exception [transformation type="
                    + transformationType
                    + ", transformation function="
                    + transformationFunction + ", response=" + data
                    + "]", e);
            } */

            logger.debug("transformed response is '{}'", transformedResponse);

            if (transformedResponse != null) {
                return new DecimalType(transformedResponse);
            }
        }

        return data;
    }

    public void addParadoxConfig(Item item, String elementName) {
        paradoxMap.put(elementName, item);

    }

}