org.openhab.binding.plclogo.internal.PLCLogoBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.plclogo.internal.PLCLogoBinding.java

Source

/**
 * Copyright (c) 2010-2019 by the respective copyright holders.
 *
 * 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.plclogo.internal;

import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Objects;

import org.apache.commons.lang.StringUtils;
import org.openhab.binding.plclogo.PLCLogoBindingConfig;
import org.openhab.binding.plclogo.PLCLogoBindingProvider;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.NumberItem;
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.OpenClosedType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.model.item.binding.BindingConfigParseException;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import Moka7.S7;
import Moka7.S7Client;

/**
 * Implement this class if you are going create an actively polling service
 * like querying a Website/Device.
 *
 * @author Lehane Kellett
 * @author Vladimir Grebenschikov
 * @author Alexander Falkenstern
 * @since 1.9.0
 */
public class PLCLogoBinding extends AbstractActiveBinding<PLCLogoBindingProvider> implements ManagedService {
    private final ReentrantLock lock = new ReentrantLock();
    private static final Logger logger = LoggerFactory.getLogger(PLCLogoBinding.class);
    private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.(.*?)$");

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

    /**
     * Buffer for read/write operations
     */
    private byte data[] = new byte[2048];

    private static Map<String, PLCLogoConfig> controllers = new HashMap<String, PLCLogoConfig>();

    private int ReadLogoDBArea(S7Client client, int size) {
        int result = 0;
        int offset = 0;
        final int bufSize = 1024;

        // read first portion directly to data, to avoid extra copy
        result = client.ReadArea(S7.S7AreaDB, 1, 0, bufSize, data);
        offset = offset + bufSize;

        while ((result == 0) && (offset < size)) {
            byte buffer[] = new byte[Math.min(size - offset, bufSize)];
            result = client.ReadArea(S7.S7AreaDB, 1, offset, buffer.length, buffer);
            System.arraycopy(buffer, 0, data, offset, buffer.length);
            offset = offset + buffer.length;
        }

        return result;
    }

    private void ReconnectLogo(S7Client client) {
        // try and reconnect
        client.Disconnect();
        client.Connect();
        if (client.Connected) {
            logger.warn("Reconnect successful");
        }
    }

    public PLCLogoBinding() {
        logger.info("PLCLogoBinding constuctor");
    }

    @Override
    public void activate() {
    }

    @Override
    public void deactivate() {
        for (PLCLogoBindingProvider provider : providers) {
            provider.removeBindingChangeListener(this);
        }

        providers.clear();
        Iterator<Entry<String, PLCLogoConfig>> entries = controllers.entrySet().iterator();
        while (entries.hasNext()) {
            Entry<String, PLCLogoConfig> thisEntry = entries.next();
            PLCLogoConfig logoConfig = thisEntry.getValue();
            S7Client LogoS7Client = logoConfig.getS7Client();
            if (LogoS7Client != null) {
                LogoS7Client.Disconnect();
            }
        }
        controllers.clear();
    }

    protected void addBindingProvider(PLCLogoBindingProvider bindingProvider) {
        super.addBindingProvider(bindingProvider);
    }

    protected void removeBindingProvider(PLCLogoBindingProvider bindingProvider) {
        super.removeBindingProvider(bindingProvider);
    }

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

    @Override
    protected String getName() {
        return "PLCLogo Polling Service";
    }

    @Override
    protected void execute() {
        if (!bindingsExist()) {
            logger.debug("There is no existing plclogo binding configuration => refresh cycle aborted!");
            return;
        }

        Iterator<Entry<String, PLCLogoConfig>> entries = controllers.entrySet().iterator();
        while (entries.hasNext()) {
            Entry<String, PLCLogoConfig> thisEntry = entries.next();
            String controller = thisEntry.getKey();
            PLCLogoConfig logoConfig = thisEntry.getValue();
            S7Client LogoS7Client = logoConfig.getS7Client();
            if (LogoS7Client == null) {
                logger.debug("No S7client for {} found", controller);
            } else {
                lock.lock();
                int result = ReadLogoDBArea(LogoS7Client, logoConfig.getMemorySize());
                lock.unlock();

                if (result != 0) {
                    logger.warn("Failed to read memory: {}. Reconnecting...", S7Client.ErrorText(result));
                    ReconnectLogo(LogoS7Client);
                    return;
                }

                // Now have the LOGO! memory (note: not suitable for S7) - more efficient than multiple reads (test
                // shows <14mS to read all)
                // iterate through bindings to see what has changed - this approach assumes a small number (< 100)of
                // bindings
                // otherwise might see what has changed in memory and map to binding
            }
            for (PLCLogoBindingProvider provider : providers) {
                for (String itemName : provider.getItemNames()) {
                    PLCLogoBindingConfig config = provider.getBindingConfig(itemName);
                    if (config.getController().equals(controller)) {
                        // it is for our currently selected controller
                        PLCLogoMemoryConfig rd = config.getRD();
                        int address = -1;
                        try {
                            address = rd.getAddress(logoConfig.getModel());
                        } catch (BindingConfigParseException exception) {
                            logger.error("Invalid address for block {} on {}", rd.getBlockName(), controller);
                            continue;
                        }

                        int currentValue;
                        if (rd.isDigital()) {
                            int bit = -1;
                            try {
                                bit = rd.getBit(logoConfig.getModel());
                            } catch (BindingConfigParseException exception) {
                                logger.error("Invalid bit for block {} on {}", rd.getBlockName(), controller);
                                continue;
                            }
                            currentValue = S7.GetBitAt(data, address, bit) ? 1 : 0;
                        } else {
                            /*
                             * After the data transfer from a LOGO! Base Module to LOGO!Soft Comfort,
                             * you can view only analog values within the range of -32768 to 32767 on LOGO!Soft Comfort.
                             * If an analog value exceeds the value range,
                             * then only the nearest upper limit (32767) or lower limit (-32768) can be displayed.
                             */
                            currentValue = S7.GetShortAt(data, address);
                        }

                        if (config.isSet()) {
                            if (currentValue == config.getLastValue()) {
                                continue;
                            }

                            int delta = Math.abs(config.getLastValue() - currentValue);
                            if (!rd.isDigital() && (delta < config.getAnalogDelta())) {
                                continue;
                            }
                        }

                        boolean isValid = false;
                        Item item = provider.getItem(itemName);
                        switch (rd.getKind()) {
                        case I:
                        case NI: {
                            isValid = item instanceof ContactItem;
                            break;
                        }
                        case Q:
                        case NQ: {
                            isValid = item instanceof SwitchItem;
                            break;
                        }
                        case M:
                        case VB:
                        case VW: {
                            isValid = item instanceof ContactItem || item instanceof SwitchItem;
                            break;
                        }
                        default: {
                            break;
                        }
                        }

                        if (item instanceof NumberItem || isValid) {
                            eventPublisher.postUpdate(itemName, createState(item, currentValue));
                            config.setLastValue(currentValue);
                        } else {
                            String block = rd.getBlockName();
                            logger.warn("Block {} is incompatible with item {} on {}", block, item.getName(),
                                    controller);
                        }
                    }
                }

            }
        }
    }

    @Override
    protected void internalReceiveCommand(String itemName, Command command) {
        // the code being executed when a command was sent on the openHAB
        // event bus goes here. This method is only called if one of the
        // BindingProviders provide a binding for the given 'itemName'.
        // Note itemname is the item name not the controller name/instance!
        //
        super.internalReceiveCommand(itemName, command);
        logger.debug("internalReceiveCommand() is called!");
        for (PLCLogoBindingProvider provider : providers) {
            if (!provider.providesBindingFor(itemName)) {
                continue;
            }

            PLCLogoBindingConfig config = provider.getBindingConfig(itemName);
            if (!controllers.containsKey(config.getController())) {
                logger.warn("Invalid write requested for controller {}", config.getController());
                continue;
            }

            PLCLogoConfig controller = controllers.get(config.getController());

            PLCLogoMemoryConfig wr = config.getWR();
            int address = -1;
            try {
                address = wr.getAddress(controller.getModel());
            } catch (BindingConfigParseException exception) {
                logger.error("Invalid address for block {} on {}", wr.getBlockName(), controller);
                continue;
            }

            int bit = -1;
            try {
                bit = wr.getBit(controller.getModel());
            } catch (BindingConfigParseException exception) {
                logger.error("Invalid bit for block {} on {}", wr.getBlockName(), controller);
                continue;
            }

            if (!wr.isInRange(controller.getModel())) {
                logger.warn("Invalid write request for block {} at address {}", wr.getBlockName(), address);
                continue;
            }

            // Send command to the LOGO! controller memory
            S7Client LogoS7Client = controller.getS7Client();
            if (LogoS7Client == null) {
                logger.debug("No S7client for controller {} found", config.getController());
                continue;
            }

            final byte buffer[] = new byte[2];
            int size = wr.isDigital() ? 1 : 2;

            lock.lock();
            int result = LogoS7Client.ReadArea(S7.S7AreaDB, 1, address, size, buffer);
            logger.debug("Read word from logo memory: at {} {} bytes, result = {}", address, size, result);
            if (result == 0) {
                Item item = config.getItem();
                if (item instanceof NumberItem && !wr.isDigital()) {
                    if (command instanceof DecimalType) {
                        int oldValue = S7.GetShortAt(buffer, 0);
                        int newValue = ((DecimalType) command).intValue();
                        S7.SetWordAt(buffer, 0, newValue);
                        logger.debug("Changed word at {} from {} to {}", address, oldValue, newValue);

                        result = LogoS7Client.WriteArea(S7.S7AreaDB, 1, address, size, buffer);
                        logger.debug("Wrote to memory at {} two bytes: [{}, {}]", address, buffer[0], buffer[1]);
                    }
                } else if (item instanceof SwitchItem && wr.isDigital()) {
                    if (command instanceof OnOffType) {
                        boolean oldValue = S7.GetBitAt(buffer, 0, bit) ? true : false;
                        boolean newValue = command == OnOffType.ON ? true : false;
                        S7.SetBitAt(buffer, 0, bit, newValue);
                        logger.debug("Changed bit {}.{} from {} to {}", address, bit, oldValue, newValue);

                        result = LogoS7Client.WriteArea(S7.S7AreaDB, 1, address, size, buffer);
                        logger.debug("Wrote to memory at {} one byte: [{}]", address, buffer[0]);
                    }
                }

                // If nothing was written and read was ok, nothing will happen here
                if (result != 0) {
                    logger.warn("Failed to write memory: {}. Reconnecting...", S7Client.ErrorText(result));
                    ReconnectLogo(LogoS7Client);
                }
            } else {
                logger.warn("Failed to read memory: {}. Reconnecting...", S7Client.ErrorText(result));
                ReconnectLogo(LogoS7Client);
            }
            lock.unlock();
        }
    }

    @Override
    public void updated(Dictionary<String, ?> config) throws ConfigurationException {
        Boolean configured = false;
        if (config != null) {
            String refreshIntervalString = Objects.toString(config.get("refresh"), null);
            if (StringUtils.isNotBlank(refreshIntervalString)) {
                refreshInterval = Long.parseLong(refreshIntervalString);
            }

            if (controllers == null) {
                controllers = new HashMap<String, PLCLogoConfig>();
            }

            Enumeration<String> keys = config.keys();
            while (keys.hasMoreElements()) {
                String key = keys.nextElement();

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

                Matcher matcher = EXTRACT_CONFIG_PATTERN.matcher(key);
                if (!matcher.matches()) {
                    continue;
                }

                matcher.reset();
                matcher.find();
                String controllerName = matcher.group(1);
                PLCLogoConfig deviceConfig = controllers.get(controllerName);

                if (deviceConfig == null) {
                    deviceConfig = new PLCLogoConfig();
                    controllers.put(controllerName, deviceConfig);
                    logger.info("Create new config for {}", controllerName);
                }
                if (matcher.group(2).equals("host")) {
                    String ip = config.get(key).toString();
                    deviceConfig.setIP(ip);
                    logger.info("Set host of {}: {}", controllerName, ip);
                    configured = true;
                }
                if (matcher.group(2).equals("remoteTSAP")) {
                    String tsap = config.get(key).toString();
                    deviceConfig.setRemoteTSAP(Integer.decode(tsap));
                    logger.info("Set remote TSAP for {}: {}", controllerName, tsap);
                }
                if (matcher.group(2).equals("localTSAP")) {
                    String tsap = config.get(key).toString();
                    deviceConfig.setLocalTSAP(Integer.decode(tsap));
                    logger.info("Set local TSAP for {}: {}", controllerName, tsap);
                }
                if (matcher.group(2).equals("model")) {
                    PLCLogoModel model = null;
                    String modelName = config.get(key).toString();
                    if (modelName.equalsIgnoreCase("0BA7")) {
                        model = PLCLogoModel.LOGO_MODEL_0BA7;
                    } else if (modelName.equalsIgnoreCase("0BA8")) {
                        model = PLCLogoModel.LOGO_MODEL_0BA8;
                    } else {
                        logger.info("Found unknown model for {}: {}", controllerName, modelName);
                    }

                    if (model != null) {
                        deviceConfig.setModel(model);
                        logger.info("Set model for {}: {}", controllerName, modelName);
                    }
                }
            } // while

            Iterator<Entry<String, PLCLogoConfig>> entries = controllers.entrySet().iterator();
            while (entries.hasNext()) {
                Entry<String, PLCLogoConfig> thisEntry = entries.next();
                String controllerName = thisEntry.getKey();
                PLCLogoConfig deviceConfig = thisEntry.getValue();
                S7Client LogoS7Client = deviceConfig.getS7Client();
                if (LogoS7Client == null) {
                    LogoS7Client = new Moka7.S7Client();
                } else {
                    LogoS7Client.Disconnect();
                }
                LogoS7Client.SetConnectionParams(deviceConfig.getlogoIP(), deviceConfig.getlocalTSAP(),
                        deviceConfig.getremoteTSAP());
                logger.info("About to connect to {}", controllerName);

                if ((LogoS7Client.Connect() == 0) && LogoS7Client.Connected) {
                    logger.info("Connected to PLC LOGO! device {}", controllerName);
                } else {
                    logger.error("Could not connect to PLC LOGO! device {} : {}", controllerName,
                            S7Client.ErrorText(LogoS7Client.LastError));
                    throw new ConfigurationException("Could not connect to PLC LOGO! device ",
                            controllerName + " " + deviceConfig.getlogoIP());
                }
                deviceConfig.setS7Client(LogoS7Client);
            }

            setProperlyConfigured(configured);
        } else {
            logger.info("No configuration for PLCLogoBinding");
        }
    }

    private State createState(Item item, Object value) {
        DecimalType number = null;
        if (value instanceof Number) {
            number = new DecimalType(value.toString());
        }

        State state = null;
        if (item instanceof StringType) {
            state = new StringType((String) value);
        } else if (item instanceof NumberItem) {
            if (number != null) {
                return number;
            } else if (value instanceof String) {
                state = new DecimalType(((String) value).replaceAll("[^\\d|.]", ""));
            }
        } else if (item instanceof SwitchItem && (number != null)) {
            state = (number.intValue() > 0) ? OnOffType.ON : OnOffType.OFF;
        } else if (item instanceof ContactItem && (number != null)) {
            state = (number.intValue() > 0) ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
        }
        return state;
    }

    /**
     * Class which represents a LOGO! online controller/PLC connection parameters
     * and current instance - there may be multiple PLC's
     *
     * @author Lehane Kellett
     * @since 1.9.0
     */
    private class PLCLogoConfig {
        private String logoIP;
        private PLCLogoModel logoModel = PLCLogoModel.LOGO_MODEL_0BA7;
        private int localTSAP = 0x0300;
        private int remoteTSAP = 0x0200;
        private S7Client LogoS7Client;

        public PLCLogoConfig() {
        }

        public void setIP(String logoIP) {
            this.logoIP = logoIP;
        }

        public void setLocalTSAP(int localTSAP) {
            this.localTSAP = localTSAP;
        }

        public void setRemoteTSAP(int remoteTSAP) {
            this.remoteTSAP = remoteTSAP;
        }

        public void setS7Client(S7Client LogoS7Client) {
            this.LogoS7Client = LogoS7Client;
        }

        public String getlogoIP() {
            return logoIP;
        }

        public int getlocalTSAP() {
            return localTSAP;
        }

        public int getremoteTSAP() {
            return remoteTSAP;
        }

        public void setModel(PLCLogoModel model) {
            this.logoModel = model;
        }

        public PLCLogoModel getModel() {
            return logoModel;
        }

        public int getMemorySize() {
            return (logoModel == PLCLogoModel.LOGO_MODEL_0BA8) ? 1470 : 1024;
        }

        public S7Client getS7Client() {
            return LogoS7Client;
        }
    }

}