org.openhab.binding.ebus.internal.parser.EBusTelegramParser.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.ebus.internal.parser.EBusTelegramParser.java

Source

/**
 * Copyright (c) 2010-2015, 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.ebus.internal.parser;

import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptException;

import org.apache.commons.lang.ObjectUtils;
import org.openhab.binding.ebus.internal.EBusTelegram;
import org.openhab.binding.ebus.internal.utils.EBusUtils;
import org.openhab.binding.ebus.internal.utils.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class parses received eBus telegrams from eBus and convert them to
 * a result map with keys and resolved values.
 * 
 * @author Christian Sowada
 * @since 1.7.0
 */
public class EBusTelegramParser {

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

    private static final Logger loggerAnalyses = LoggerFactory
            .getLogger(EBusTelegramParser.class.getPackage().getName() + ".Analyses");

    private static final Logger loggerBrutforce = LoggerFactory
            .getLogger(EBusTelegramParser.class.getPackage().getName() + ".BruteForce");

    //   private Map<String, Object> settings;

    // The configuration provider to parse the ebus telegram
    private EBusConfigurationProvider configurationProvider;

    /**
     * Constructor
     * @param configurationProvider
     */
    public EBusTelegramParser(EBusConfigurationProvider configurationProvider) {
        this.configurationProvider = configurationProvider;
    }

    /**
     * @param byteBuffer
     * @param settings 
     * @param type
     * @param pos
     * @return
     */
    private Object getValue(ByteBuffer byteBuffer, Map<String, Object> settings) {

        String type = ((String) settings.get("type")).toLowerCase();
        int pos = settings.containsKey("pos") ? ((Integer) settings.get("pos")).intValue() : -1;

        // load possible min, max, replace values and a factor from configuration
        BigDecimal valueMin = NumberUtils.toBigDecimal(settings.get("min"));
        BigDecimal valueMax = NumberUtils.toBigDecimal(settings.get("max"));
        BigDecimal replaceValue = NumberUtils.toBigDecimal(settings.get("replaceValue"));
        BigDecimal factor = NumberUtils.toBigDecimal(settings.get("factor"));

        Object value = null;
        byte hByte = 0;
        byte lByte = 0;

        BigDecimal repVal = null;

        // requested pos is greater as whole buffer
        if (pos > byteBuffer.position()) {
            //FIXME: Do something
            logger.warn("eBus buffer pos error! Can happen ...");
        }

        if (type.equals("data2b")) {
            hByte = byteBuffer.get(pos);
            lByte = byteBuffer.get(pos - 1);
            repVal = BigDecimal.valueOf(-128);
            value = new BigDecimal(EBusUtils.decodeDATA2b(hByte, lByte));

        } else if (type.equals("data2c")) {
            hByte = byteBuffer.get(pos);
            lByte = byteBuffer.get(pos - 1);
            repVal = BigDecimal.valueOf(-2048);
            value = new BigDecimal(EBusUtils.decodeDATA2c(hByte, lByte));

        } else if (type.equals("data1c")) {
            lByte = byteBuffer.get(pos - 1);
            repVal = BigDecimal.valueOf(255);
            value = new BigDecimal(EBusUtils.decodeDATA1c(lByte));

        } else if (type.equals("data1b")) {
            lByte = byteBuffer.get(pos - 1);
            repVal = BigDecimal.valueOf(-128);
            value = new BigDecimal(EBusUtils.decodeDATA1b(lByte));

        } else if (type.equals("bcd")) {
            lByte = byteBuffer.get(pos - 1);
            repVal = BigDecimal.valueOf(266);
            value = new BigDecimal(EBusUtils.decodeBCD(lByte));

        } else if (type.equals("word")) {
            hByte = byteBuffer.get(pos);
            lByte = byteBuffer.get(pos - 1);
            repVal = BigDecimal.valueOf(65535);
            value = new BigDecimal(EBusUtils.decodeWORD(hByte, lByte));

        } else if (type.equals("uchar") || type.equals("byte")) {
            repVal = BigDecimal.valueOf(255);
            value = new BigDecimal(byteBuffer.get(pos - 1) & 0xFF);

        } else if (type.equals("char")) {
            repVal = BigDecimal.valueOf(255);
            value = new BigDecimal(byteBuffer.get(pos - 1));

        } else if (type.equals("bit")) {
            int bit = ((Integer) settings.get("bit"));
            value = byteBuffer.get(pos - 1);

            boolean isSet = ((Byte) value >> bit & 0x1) == 1;
            value = isSet;

        } else {
            logger.warn("Configuration Error: Unknown command type! {}", type);

        }

        // if replace value paramter set
        if (replaceValue != null) {
            repVal = replaceValue;
        }

        // if BigDecimal check for min, max and replace value
        if (value instanceof BigDecimal) {
            BigDecimal b = (BigDecimal) value;

            // equals replace value, than return null
            if (repVal != null && b.compareTo(repVal) == 0) {
                logger.trace("Replace value found, skip value ...");
                value = b = null;
            }

            // multiply before check min and max
            if (b != null && factor != null) {
                logger.trace("Value multiplied ...");
                value = b = b.multiply(factor);
            }

            // value is below min value, return null
            if (valueMin != null && b != null && b.compareTo(valueMin) == -1) {
                logger.trace("Minimal value reached, skip value ...");
                value = b = null;

                // value is above max value, return null
            } else if (valueMax != null && b != null && b.compareTo(valueMax) == 1) {
                logger.trace("Maximal value reached, skip value ...");
                value = b = null;
            }

        }

        return value;
    }

    /**
     * Evaluates the compiled script of a entry.
     * @param entry The configuration entry to evaluate
     * @param scopeValues All known values for script scope
     * @return The computed value
     * @throws ScriptException
     */
    private Object evaluateScript(Entry<String, Map<String, Object>> entry, Map<String, Object> scopeValues)
            throws ScriptException {

        Object value = null;

        // executes compiled script
        if (entry.getValue().containsKey("cscript")) {
            CompiledScript cscript = (CompiledScript) entry.getValue().get("cscript");

            // Add global variables thisValue and keyName to JavaScript context
            Bindings bindings = cscript.getEngine().createBindings();
            bindings.putAll(scopeValues);
            value = cscript.eval(bindings);
        }

        // try to convert the returned value to BigDecimal
        value = ObjectUtils.defaultIfNull(NumberUtils.toBigDecimal(value), value);

        // round to two digits, maybe not optimal for any result
        if (value instanceof BigDecimal) {
            ((BigDecimal) value).setScale(2, BigDecimal.ROUND_HALF_UP);
        }

        return value;
    }

    /**
     * A function show data from unknown telegrams
     * @param telegram
     */
    private void bruteforceEBusTelegram(EBusTelegram telegram) {

        byte[] data = telegram.getData();

        String format = String.format("%-4s%-13s%-13s%-13s%-13s%-13s%-13s", "Pos", "WORD", "UInt", "DATA2B",
                "DATA2C", "DATA1c", "BCD");
        loggerBrutforce.trace("    " + format);
        loggerBrutforce.trace("    -----------------------------------------------------------------------------");

        // Check all possible positions with known data types
        for (int i = 0; i < data.length; i++) {

            Object word = i == data.length - 1 ? "---" : EBusUtils.decodeWORD(data[i + 1], data[i]);
            Object data2b = i == data.length - 1 ? "---" : EBusUtils.decodeDATA2b(data[i + 1], data[i]);
            Object data2c = i == data.length - 1 ? "---" : EBusUtils.decodeDATA2c(data[i + 1], data[i]);
            Object data1c = i == data.length - 1 ? "---" : EBusUtils.decodeDATA1c(data[i + 1]);

            int bcd = EBusUtils.decodeBCD(data[i]);
            int uint = data[i] & 0xFF;

            format = String.format("%-4s%-13s%-13s%-13s%-13s%-13s%-13s", i + 6, word, uint, data2b, data2c, data1c,
                    bcd);
            loggerBrutforce.trace("    " + format);
        }

        // Parse slave data
        if (telegram.getType() == EBusTelegram.MASTER_SLAVE) {
            data = telegram.getSlaveData();

            loggerBrutforce
                    .trace("    ---------------------------------- Answer ----------------------------------");

            for (int i = 0; i < data.length; i++) {

                Object word = i == data.length - 1 ? "---" : EBusUtils.decodeWORD(data[i + 1], data[i]);
                Object data2b = i == data.length - 1 ? "---" : EBusUtils.decodeDATA2b(data[i + 1], data[i]);
                Object data2c = i == data.length - 1 ? "---" : EBusUtils.decodeDATA2c(data[i + 1], data[i]);
                Object data1c = i == data.length - 1 ? "---" : EBusUtils.decodeDATA1c(data[i + 1]);

                int bcd = EBusUtils.decodeBCD(data[i]);
                int uint = data[i] & 0xFF;

                format = String.format("%-4s%-13s%-13s%-13s%-13s%-13s%-13s", i + 6, word, uint, data2b, data2c,
                        data1c, bcd);
                loggerBrutforce.trace("    " + format);
            }

        }
    }

    /**
     * Parses a valid eBus telegram and returns a map with key/values based on
     * configuration registry.
     * @param telegram The eBus telegram
     * @return A Map with parsed key/values
     */
    public Map<String, Object> parse(EBusTelegram telegram) {

        // Check if a configuration provider is set
        if (configurationProvider == null) {
            logger.error("Configuration not loaded, can't parse telegram!");
            // FIXME: Return empty map
            return null;
        }

        // Secure null check
        if (telegram == null) {
            // FIXME: Return empty map
            return null;
        }

        // All parsed values
        final Map<String, Object> valueRegistry = new HashMap<String, Object>();

        // All parsed values with short keys, used for script evaluation
        final Map<String, Object> valueRegistryShortKeys = new HashMap<String, Object>();

        // Get as byte buffer
        final ByteBuffer byteBuffer = telegram.getBuffer();

        // Get hex string for debugging
        final String bufferString = EBusUtils.toHexDumpString(byteBuffer).toString();

        // queries the configuration provider for matching registry entries
        final List<Map<String, Object>> matchedTelegramRegistry = configurationProvider
                .getCommandsByFilter(bufferString);

        loggerAnalyses.debug(bufferString);

        // No registry entries found, so this is a unknown telegram
        if (matchedTelegramRegistry.isEmpty()) {
            loggerAnalyses.debug("  >>> Unknown ----------------------------------------");
            if (loggerBrutforce.isTraceEnabled()) {
                loggerBrutforce.trace(bufferString);
                bruteforceEBusTelegram(telegram);
            }

            // FIXME: Return empty map
            return null;
        }

        // loop thru all matching telegrams from registry
        for (Map<String, Object> registryEntry : matchedTelegramRegistry) {

            int debugLevel = 0;

            // get class key if used
            String classKey = registryEntry.containsKey("class") ? (String) registryEntry.get("class") : "";

            // load debug level for this configuration entry if available
            if (registryEntry.containsKey("debug")) {
                debugLevel = ((Integer) registryEntry.get("debug"));
            }

            // get values block of configuration
            @SuppressWarnings("unchecked")
            Map<String, Map<String, Object>> values = (Map<String, Map<String, Object>>) registryEntry
                    .get("values");

            // debug
            loggerAnalyses.debug("  >>> {}",
                    registryEntry.containsKey("comment") ? registryEntry.get("comment") : "<No comment available>");

            Map<String, Object> settings = null;

            // loop over all entries
            for (Entry<String, Map<String, Object>> entry : values.entrySet()) {

                String uniqueKey = (classKey != "" ? classKey + "." : "") + entry.getKey();
                settings = entry.getValue();

                // Extract the value from byte buffer
                Object value = getValue(byteBuffer, entry.getValue());

                // If compiled script available for this key, execute it now
                if (settings.containsKey("cscript")) {
                    try {

                        // Add global variables thisValue and keyName to JavaScript context
                        HashMap<String, Object> bindings = new HashMap<String, Object>();
                        bindings.put(entry.getKey(), value); // short key
                        bindings.put(uniqueKey, value); // full key
                        bindings.put("thisValue", value); // alias thisValue

                        // Evaluates script
                        value = evaluateScript(entry, bindings);

                    } catch (ScriptException e) {
                        logger.error("Error on evaluating JavaScript!", e);
                        break;
                    }
                }

                // debug
                String label = (String) (settings.containsKey("label") ? settings.get("label") : "");
                String format = String.format("%-35s%-10s%s", uniqueKey, value, label);
                if (debugLevel >= 2) {
                    loggerAnalyses.debug("    >>> " + format);
                } else {
                    loggerAnalyses.trace("    >>> " + format);
                }

                // Add result to registry
                valueRegistry.put(uniqueKey, value);

                // Add result to temp. short key registry, used for scripts
                valueRegistryShortKeys.put(entry.getKey(), value);
            }

            // computes values available? if not exit here
            if (!registryEntry.containsKey("computed_values"))
                continue;

            // post execute the computes_values block
            @SuppressWarnings("unchecked")
            Map<String, Map<String, Object>> cvalues = (Map<String, Map<String, Object>>) registryEntry
                    .get("computed_values");
            for (Entry<String, Map<String, Object>> entry : cvalues.entrySet()) {

                String uniqueKey = (classKey != "" ? classKey + "." : "") + entry.getKey();

                // Add all values to script scope
                HashMap<String, Object> bindings = new HashMap<String, Object>();
                bindings.putAll(valueRegistryShortKeys);
                bindings.putAll(valueRegistry);

                Object value;
                try {
                    // Evaluates script
                    value = evaluateScript(entry, bindings);

                    // Add result to registry
                    valueRegistry.put(entry.getKey(), value);

                    if (debugLevel >= 2) {
                        String label = (String) (settings.containsKey("label") ? settings.get("label") : "");
                        String format = String.format("%-35s%-10s%s", uniqueKey, value, label);
                        loggerAnalyses.debug("    >>> " + format);
                    }

                } catch (ScriptException e) {
                    logger.error("Error on evaluating JavaScript!", e);
                }
            }
        }

        return valueRegistry;
    }

}