org.openhab.binding.digiplex.internal.handler.DigiplexBridgeHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.digiplex.internal.handler.DigiplexBridgeHandler.java

Source

/**
 * Copyright (c) 2010-2019 Contributors to the openHAB project
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package org.openhab.binding.digiplex.internal.handler;

import static org.openhab.binding.digiplex.internal.DigiplexBindingConstants.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.TooManyListenersException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.io.IOUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.library.types.DecimalType;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
import org.eclipse.smarthome.core.thing.binding.ThingHandlerService;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.RefreshType;
import org.openhab.binding.digiplex.internal.DigiplexBridgeConfiguration;
import org.openhab.binding.digiplex.internal.communication.CommunicationStatus;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
import org.openhab.binding.digiplex.internal.communication.DigiplexResponse;
import org.openhab.binding.digiplex.internal.communication.DigiplexResponseResolver;
import org.openhab.binding.digiplex.internal.communication.events.AbstractEvent;
import org.openhab.binding.digiplex.internal.discovery.DigiplexDiscoveryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.RXTXPort;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

/**
 * The {@link DigiplexBridgeHandler} is responsible for handling communication with PRT3 module
 *
 * @author Robert Michalak - Initial contribution
 */
@NonNullByDefault
public class DigiplexBridgeHandler extends BaseBridgeHandler implements SerialPortEventListener {

    private static final int REINITIALIZE_DELAY = 1; // in minutes
    private static final int STALLED_MESSAGES_THRESHOLD = 5;
    private static final int END_OF_MESSAGE = '\r';
    private static final int END_OF_STREAM = -1;

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

    @Nullable
    private DigiplexBridgeConfiguration config;
    @Nullable
    private RXTXPort serialPort;
    @Nullable
    private DigiplexReceiverThread receiverThread;
    @Nullable
    private DigiplexSenderThread senderThread;
    private BlockingQueue<DigiplexRequest> sendQueue = new LinkedBlockingQueue<>();
    private Set<DigiplexMessageHandler> handlers = ConcurrentHashMap.newKeySet();

    @Nullable
    private ScheduledFuture<?> reinitializeTask;

    private AtomicLong messagesSent = new AtomicLong(0);
    private AtomicLong responsesReceived = new AtomicLong(0);
    private AtomicLong eventsReceived = new AtomicLong(0);

    public DigiplexBridgeHandler(Bridge bridge) {
        super(bridge);
    }

    @SuppressWarnings("null")
    @Override
    public void initialize() {
        config = getConfigAs(DigiplexBridgeConfiguration.class);
        if (config.port == null) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!");
            return;
        }

        try {
            serialPort = initializeSerialPort(config.port);

            registerMessageHandler(new BridgeMessageHandler());

            messagesSent.set(0);
            responsesReceived.set(0);
            eventsReceived.set(0);

            receiverThread = new DigiplexReceiverThread(serialPort.getInputStream());
            senderThread = new DigiplexSenderThread(serialPort.getOutputStream());

            receiverThread.start();
            senderThread.start();

            updateStatus(ThingStatus.ONLINE);
        } catch (PortInUseException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Port is in use!");
        } catch (Exception e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
                    "Communication error!");
        }

    }

    @SuppressWarnings("null")
    private @Nullable RXTXPort initializeSerialPort(String port) throws PortInUseException, NoSuchPortException,
            TooManyListenersException, UnsupportedCommOperationException {
        CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier(port);

        RXTXPort serialPort = portId.open(getThing().getUID().toString(), 2000);
        serialPort.setSerialPortParams(config.baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
                SerialPort.PARITY_NONE);
        serialPort.disableReceiveThreshold();
        serialPort.enableReceiveTimeout(1000);

        // RXTX serial port library causes high CPU load
        // Start event listener, which will just sleep and slow down event loop
        serialPort.addEventListener(this);
        serialPort.notifyOnDataAvailable(true);

        return serialPort;
    }

    @Override
    public void handleCommand(ChannelUID channelUID, Command command) {
        if (command == RefreshType.REFRESH && isLinked(channelUID.getId())) {
            switch (channelUID.getId()) {
            case BRIDGE_MESSAGES_SENT:
                updateState(BRIDGE_MESSAGES_SENT, new DecimalType(messagesSent.get()));
                break;
            case BRIDGE_RESPONSES_RECEIVED:
                updateState(BRIDGE_RESPONSES_RECEIVED, new DecimalType(responsesReceived.get()));
                break;
            case BRIDGE_EVENTS_RECEIVED:
                updateState(BRIDGE_EVENTS_RECEIVED, new DecimalType(eventsReceived.get()));
                break;
            }
        }
    }

    public void sendRequest(DigiplexRequest request) {
        sendQueue.add(request);
    }

    public void handleResponse(String message) {
        DigiplexResponse response = DigiplexResponseResolver.resolveResponse(message);
        handlers.forEach(visitor -> response.accept(visitor));
        if (response instanceof AbstractEvent) {
            updateState(BRIDGE_EVENTS_RECEIVED, new DecimalType(eventsReceived.incrementAndGet()));
        } else {
            updateState(BRIDGE_RESPONSES_RECEIVED, new DecimalType(responsesReceived.incrementAndGet()));
        }
    }

    public void registerMessageHandler(DigiplexMessageHandler handler) {
        handlers.add(handler);
    }

    public void unregisterMessageHandler(DigiplexMessageHandler handler) {
        handlers.remove(handler);
    }

    /**
     * Closes the connection to the PRT3 module.
     */
    @SuppressWarnings("null")
    @Override
    public void dispose() {
        stopThread(senderThread);
        stopThread(receiverThread);
        senderThread = null;
        receiverThread = null;
        if (serialPort != null) {
            IOUtils.closeQuietly(serialPort.getInputStream());
            IOUtils.closeQuietly(serialPort.getOutputStream());
            serialPort.close();
            serialPort = null;
        }
        logger.info("Stopped Digiplex serial handler");

        super.dispose();
    }

    private void stopThread(@Nullable Thread thread) {
        if (thread != null) {
            thread.interrupt();
            try {
                thread.join(1000);
            } catch (InterruptedException e) {
            }
        }
    }

    public void handleCommunicationError() {
        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
        if (reinitializeTask == null) {
            reinitializeTask = scheduler.schedule(() -> {
                logger.info("Reconnecting to PRT3 device...");
                thingUpdated(getThing());
                reinitializeTask = null;
            }, REINITIALIZE_DELAY, TimeUnit.MINUTES);
        }
    }

    @Override
    public void serialEvent(@Nullable SerialPortEvent arg0) {
        try {
            logger.trace("RXTX library CPU load workaround, sleep forever");
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException ignored) {
        }
    }

    @Override
    public Collection<Class<? extends ThingHandlerService>> getServices() {
        return Collections.singletonList(DigiplexDiscoveryService.class);
    }

    private class BridgeMessageHandler implements DigiplexMessageHandler {

        @Override
        public void handleCommunicationStatus(CommunicationStatus response) {
            if (response.success) {
                updateStatus(ThingStatus.ONLINE);
            } else {
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
            }
        }
    }

    private class DigiplexReceiverThread extends Thread {

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

        private final InputStream stream;

        DigiplexReceiverThread(InputStream stream) {
            super("DigiplexReceiveThread");
            this.stream = stream;
        }

        @Override
        public void run() {
            logger.debug("Receiver thread started");
            while (!interrupted()) {
                try {
                    Optional<String> message = readLineBlocking();
                    message.ifPresent(m -> {
                        logger.debug("message received: '{}'", m);
                        handleResponse(m);
                    });
                    if (messagesSent.get() - responsesReceived.get() > STALLED_MESSAGES_THRESHOLD) {
                        throw new IOException("PRT3 module is not responding!");
                    }

                } catch (IOException e) {
                    handleCommunicationError();
                    break;
                }
            }
            logger.debug("Receiver thread finished");
        }

        private Optional<String> readLineBlocking() throws IOException {
            StringBuilder s = new StringBuilder();
            while (true) {
                int c = stream.read();
                if (c == END_OF_STREAM) {
                    return Optional.empty();
                }
                if (c == END_OF_MESSAGE) {
                    break;
                }
                s.append((char) c);
            }
            return Optional.of(s.toString());
        }
    }

    private class DigiplexSenderThread extends Thread {

        private static final int SLEEP_TIME = 150;

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

        private OutputStream stream;

        public DigiplexSenderThread(OutputStream stream) {
            super("DigiplexSenderThread");
            this.stream = stream;
        }

        @Override
        public void run() {
            logger.debug("Sender thread started");
            while (!interrupted()) {
                try {
                    DigiplexRequest request = sendQueue.take();
                    stream.write(request.getSerialMessage().getBytes());
                    stream.flush();
                    updateState(BRIDGE_MESSAGES_SENT, new DecimalType(messagesSent.incrementAndGet()));
                    logger.debug("message sent: '{}'", request.getSerialMessage().replaceAll("\r", ""));
                    Thread.sleep(SLEEP_TIME); // do not flood PRT3 with messages as it creates unpredictable responses
                } catch (IOException e) {
                    handleCommunicationError();
                    break;
                } catch (InterruptedException e) {
                    break;
                }
            }
            logger.debug("Sender thread finished");
        }
    }
}