org.openhab.binding.mart.handler.martHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.mart.handler.martHandler.java

Source

/**
 * Copyright (c) 2014-2015 openHAB UG (haftungsbeschraenkt) 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.mart.handler;

import static org.openhab.binding.mart.martBindingConstants.*;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PortUnreachableException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.net.ssl.HttpsURLConnection;

import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.core.library.types.DecimalType;
import org.eclipse.smarthome.core.library.types.OnOffType;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.ThingTypeUID;
import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Sets;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;

/**
 * The {@link martHandler} is responsible for handling commands, which are
 * sent to one of the channels.
 *
 * @author Michael Kwaku Tetteh - Initial contribution
 */
public class martHandler extends BaseThingHandler {

    private Logger logger = LoggerFactory.getLogger(martHandler.class);

    public final static Set<ThingTypeUID> SUPPORTTED_THING_TYPES = Sets.newHashSet(THING_TYPE_MART_ADAPTER);

    /**
     * A Selector is a Java NIO component which can examine one or more NIO Channel's, and determine which channels are
     * ready for e.g. reading or writing. This way a single thread can manage multiple channels, and thus multiple
     * network connections. A selector is an object that can monitor multiple channels for events .
     * The advantage of using just a single thread to handle multiple channels is that you need less threads to handle
     * the channels
     */
    private Selector selector;
    /**
     * A Java NIO DatagramChannel is a channel that can send and receive UDP packets. Since UDP is a connection-less
     * network protocol, you cannot just by default read and write to a DatagramChannel like you do from other channels.
     * Instead you send and receive packets of data.
     */
    private DatagramChannel datagramChannel = null;
    protected DatagramChannel listenerChannel = null;
    /**
     * A token representing the registration of a SelectableChannel with a Selector.
     * A selection key is created each time a channel is registered with a selector.
     */
    protected SelectionKey datagramChannelKey = null;
    protected SelectionKey listenerKey = null;
    /**
     *
     */
    private final Lock lock = new ReentrantLock();
    protected JsonParser parser = new JsonParser();
    /**
     * these are to schedule a task to execute repeatedly with a fixed interval of time
     * in between each execution
     */
    private ScheduledFuture<?> listeningJob;
    private ScheduledFuture<?> pollingJob;

    private static final String IP_ADDRESS = "ipAddress";
    private static final String POLLING_REFRESH_INTERVAL = "refreshInterval";
    public static final int INITIAL_DELAY = 0;
    public static final int CONNECTION_REFRESH_INTERVAL = 100;

    public static final int LISTENER_PORT_NUMBER = 7090;
    public static final int REMOTE_PORT_NUMBER = 7090;
    public static final int PING_TIME_OUT = 3000;
    public static final int BUFFER_SIZE = 1024;

    public static final String ADDRESS = null;

    public martHandler(Thing thing) {
        super(thing);
    }

    @Override
    public void handleCommand(ChannelUID channelUID, Command command) {
        switch (channelUID.getId()) {
        case CHANNEL_STATE:
            // check if the command is an ON/OFF command
            if (command instanceof OnOffType) {
                if (command == OnOffType.ON) {
                    // send on command
                    sendMartCommand("On Adapter");
                } else if (command == OnOffType.OFF) {
                    // send off command
                    sendMartCommand("Off Adapter");
                } else {
                    return;
                }
            }
            break;
        case CHANNEL_FRIDGE:
            // check if the command is an ON/OFF command
            if (command instanceof OnOffType) {
                if (command == OnOffType.ON) {
                    // send on command
                    sendMartCommand("On Fridge");
                } else if (command == OnOffType.OFF) {
                    // send off command
                    sendMartCommand("Off Fridge");
                } else {
                    return;
                }
            }
            break;
        case CHANNEL_OUTSIDE_LIGHT:
            // check if the command is an ON/OFF command
            if (command instanceof OnOffType) {
                if (command == OnOffType.ON) {
                    // send on command
                    sendMartCommand("On Outside Light");
                } else if (command == OnOffType.OFF) {
                    // send off command
                    sendMartCommand("Off Outside Light");
                } else {
                    return;
                }
            }
            break;
        case CHANNEL_TELEVISION:
            // check if the command is an ON/OFF command
            if (command instanceof OnOffType) {
                if (command == OnOffType.ON) {
                    // send on command
                    sendMartCommand("On Television");
                } else if (command == OnOffType.OFF) {
                    // send off command
                    sendMartCommand("Off Television");
                } else {
                    return;
                }
            }
            break;

        default:
            break;
        }
    }

    @Override
    public void initialize() {
        // TODO: Initialize the thing. If done set status to ONLINE to indicate proper working.
        logger.debug("Initializing Mart Smart Adapter handler '{}'", getThing().getUID());
        // Long running initialization should be done asynchronously in background.

        try {
            // open the selector
            selector = Selector.open();
        } catch (IOException e) {
            // TODO: handle exception
            logger.error("An IOException occurred while registering the selector: '{}'", e.getMessage());
        }

        // create a listener channel given a port number
        createListenerChannel(LISTENER_PORT_NUMBER);

        // returns the configuration of the thing .get the ip address of the thing
        if (getConfig().get(IP_ADDRESS) != null && getConfig().get(IP_ADDRESS) != "") {

            // establish a connection
            establishConnection();

            if (listeningJob == null || listeningJob.isCancelled()) {

                try {
                    // Creates and executes a periodic action that becomes enabled first after the given initial delay,
                    // and subsequently with the given delay between the termination of one execution and the
                    // commencement
                    // of the next
                    // In this method, however, the period is interpreted as the delay between the end of the previous
                    // execution,
                    // until the start of the next. The delay is thus between finished executions, not between the
                    // beginning of
                    // executions
                    listeningJob = scheduler.scheduleWithFixedDelay(listeningRunnable, INITIAL_DELAY,
                            CONNECTION_REFRESH_INTERVAL, TimeUnit.MILLISECONDS);
                } catch (Exception e) {
                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
                            "An error occurred while scheduling the connection Job");
                }

            }

            if (pollingJob == null || pollingJob.isCancelled()) {
                try {
                    pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, INITIAL_DELAY,
                            ((BigDecimal) getConfig().get(POLLING_REFRESH_INTERVAL)).intValue(),
                            TimeUnit.MILLISECONDS);

                } catch (Exception e) {
                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
                            "An error occurred while scheduling the connection Job");
                }
            }

        } else {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                    "IP address or port number not set");
        }

    }

    @Override
    public void dispose() {
        try {
            selector.close();
        } catch (IOException e) {
            logger.error("An exception occurred while closing the selector: '{}'", e.getMessage());
        }

        try {
            datagramChannel.close();
        } catch (IOException e) {
            logger.warn("An exception occurred while closing the channel '{}': {}", datagramChannel,
                    e.getMessage());
        }

        try {
            listenerChannel.close();
        } catch (IOException e) {
            logger.error("An exception occurred while closing the listener channel on port number {} ({})",
                    LISTENER_PORT_NUMBER, e.getMessage());
        }

        if (listeningJob != null && !listeningJob.isCancelled()) {
            listeningJob.cancel(true);
            listeningJob = null;
        }

        if (pollingJob != null && !pollingJob.isCancelled()) {
            pollingJob.cancel(true);
            pollingJob = null;
        }

        logger.debug("Handler disposed.");
    }

    /**
     * creates a listener Channel given a port number (listenerPort)
     *
     * @param listenerPort
     */
    protected void createListenerChannel(int listenerPort) {

        // opening the listener port which can receive packets on UDP port listenerPort passed to the function
        try {
            listenerChannel = DatagramChannel.open();
            // used to establish an association between the socket and a local address
            // once the association is established the socket remains bound until the socket is closed
            listenerChannel.bind(new InetSocketAddress(listenerPort));
            // The Channel must be in non-blocking mode to be used with a Selector
            listenerChannel.configureBlocking(false);

            logger.info("Listening for incoming data on {}", listenerChannel.getLocalAddress());

            synchronized (selector) {
                selector.wakeup();

                try {
                    // Registers this channel with the given selector, returning a selection key
                    // ".validOps" returns an operation set identifying this channel's supported operations
                    listenerKey = listenerChannel.register(selector, listenerChannel.validOps());
                } catch (ClosedChannelException e1) {
                    // TODO: handle exception
                    logger.error("An error occured while registering the selector: {}", e1.getMessage());
                }

            }

        } catch (IOException e) {
            // TODO Auto-generated catch block
            logger.error("An error occured while creating the Listener Channel on port number {} ({})",
                    listenerPort, e.getMessage());
        }

    }

    /**
     * Reads a buffer from the channel and returns it
     * A buffer is essentially a block of memory into which you can write data, which
     * you can then later read again
     *
     * @param theChannel
     * @param bufferSize
     * @param permittedClientAddress
     * @return
     */
    protected ByteBuffer Reader(DatagramChannel theChannel, int bufferSize, InetAddress permittedClientAddress) {
        // The lock() method locks the Lock instance so that all threads calling lock() are blocked until unlock() is
        // executed.
        lock.lock();
        try {
            // retrieves the channel's key representing its registration with the selector
            SelectionKey theSelectionKey = theChannel.keyFor(selector);
            if (theSelectionKey != null) {
                synchronized (selector) {
                    try {
                        // it selects a set of keys whose corresponding channels are ready for I/O operations.
                        selector.selectNow();
                    } catch (IOException e) {
                        logger.error("An exception occured while selecting: {}", e.getMessage());
                    } catch (ClosedSelectorException e) {
                        logger.error("An exception occured while selecting: {}", e.getMessage());
                    }
                }

                // to iterate over the this selector's selected key set
                Iterator<SelectionKey> iterate = selector.selectedKeys().iterator();
                // if iterate has more elements
                while (iterate.hasNext()) {
                    // represents the key representing the channel's registration with the Selector (selector).
                    SelectionKey selectKey = iterate.next();
                    iterate.remove();
                    if (selectKey.isValid() && selectKey.isReadable() && selectKey == theSelectionKey) {
                        // allocate a new byte buffer with 1024 bytes capacity
                        ByteBuffer readBuffer = ByteBuffer.allocate(bufferSize);
                        int numOfBytesRead = 0;
                        boolean error = false;

                        // if the current select key is the key representing the listener's channel registration
                        // with the selector, then read the byte buffer or data from the channel
                        if (selectKey == listenerKey) {
                            try {
                                // receive a datagram via this channel
                                // the channel writes data into the the readBuffer
                                InetSocketAddress clientAddress = (InetSocketAddress) theChannel
                                        .receive(readBuffer);
                                // if the returned address given by the receive() is == permitted address
                                if (clientAddress.getAddress().equals(permittedClientAddress)) {
                                    logger.debug("Received {} on the listener port from {}",
                                            new String(readBuffer.array()), clientAddress);
                                    // returns the buffer's position to help as check whether the buffer is
                                    // full or not
                                    numOfBytesRead = readBuffer.position();
                                } else {
                                    logger.warn(
                                            "Received data from '{}' which is not the permitted remote address '{}'",
                                            clientAddress, permittedClientAddress);
                                    // since it is not a permitted remote address return nothing
                                    return null;
                                }

                            } catch (Exception e) {
                                logger.error(
                                        "An exception occurred while receiving data on the listener port: '{}'",
                                        e.getMessage());
                                error = true;
                            }
                            // if the selectKey != listenerKey
                        } else {

                            try {
                                // reads a datagram from this channel though the selectKey != listenerKey
                                // reads a data from this channel into the readBuffer or
                                // the channel writes data into the the buffer
                                numOfBytesRead = theChannel.read(readBuffer);
                            } catch (NotYetConnectedException e) {
                                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                        "The MART adapter is not yet connected");
                                error = true;
                            } catch (PortUnreachableException e) {
                                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                                        "This is probably not a MART adapter");
                                error = true;
                            } catch (IOException e) {
                                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                        "An IO exception occurred");
                                error = true;
                            }
                        }

                        // if numOfBytesRead == -1 then the channel has reached end of stream
                        if (numOfBytesRead == -1) {
                            error = true;
                        }
                        // if error == true , close the channel and re-establish connection
                        if (error) {
                            logger.debug("Disconnecting '{}' because of a socket error",
                                    getThing().getUID().toString());
                            try {
                                // close the channel
                                theChannel.close();
                            } catch (IOException e) {
                                logger.error("An exception occurred while closing the channel '{}': {}",
                                        datagramChannel, e.getMessage());
                            }

                            // re-establish connection
                            onConnectionLost();
                            // if error == false,
                        } else {
                            // switch the buffer from writing mode into reading mode and return it
                            readBuffer.flip();
                            return readBuffer;

                        }
                    }
                }
            }
            return null;

        } finally {
            lock.unlock();
        }
    }

    /**
     * this function parses the responses or data read from the datagram channel
     *
     * @param byteBuffer
     * @param datagramChannel
     */
    protected void readerHandler(ByteBuffer byteBuffer, DatagramChannel datagramChannel) {
        // Constructs a new String by decoding the specified subarray of bytes using
        // the platform's default charset. The length of the new String is a function
        // of the charset, and hence may not be equal to the length of the subarray
        String response = new String(byteBuffer.array(), 0, byteBuffer.limit());

        // Removes one newline from end of a String if it's there, otherwise leave it alone.
        response = StringUtils.chomp(response);

        // get data to update channels and to send to machine learning web-service
        try {
            JsonObject readData = parser.parse(response).getAsJsonObject();

            for (Entry<String, JsonElement> data : readData.entrySet()) {
                switch (data.getKey()) {
                case "onToday":
                    State onToday = new DecimalType(data.getValue().getAsInt());
                    if (onToday != null) {
                        logger.debug("", onToday, getThing().getUID());
                        updateState(new ChannelUID(getThing().getUID(), CHANNEL_ON_TODAY), onToday);
                    }
                    break;

                case "onTotal":
                    State onTotal = new DecimalType(data.getValue().getAsInt());
                    if (onTotal != null) {
                        logger.debug("", onTotal, getThing().getUID());
                        updateState(new ChannelUID(getThing().getUID(), CHANNEL_ON_TOTAL), onTotal);
                    }
                    break;

                default:
                    break;
                }
            }

        } catch (JsonParseException e) {
            logger.debug("Invalid JSON response: '{}'", response);
        }

    }

    /**
     * this function reads from a buffer and writes into a channel
     * A buffer is essentially a block of memory into which you can write data, which
     * you can then later read again
     *
     * @param buffer
     * @param theChannel
     */
    protected void writer(ByteBuffer buffer, DatagramChannel theChannel) {
        lock.lock();

        try {
            // represents the key representing the channel's registration with the Selector (selector).
            SelectionKey theSelectionKey = theChannel.keyFor(selector);

            // check if the key isn't null
            if (theSelectionKey != null) {
                synchronized (selector) {
                    try {
                        // selects a set of keys whose corresponding channels are ready
                        // for I/O operations
                        selector.selectNow();
                    } catch (IOException e) {
                        logger.error("An exception occured while selecting {}", e.getMessage());
                    }
                }

                // returns this selector's selected-key set
                Iterator<SelectionKey> iterate = selector.selectedKeys().iterator();
                while (iterate.hasNext()) {
                    SelectionKey selectKey = iterate.next();
                    iterate.remove();
                    // checks if the key is valid
                    // tests whether this channels is ready for writing
                    // checks whether the current select key is equal to the channel's registered key
                    // with the selector
                    if (selectKey.isValid() && selectKey.isWritable() && selectKey == theSelectionKey) {

                        boolean error = false;
                        // sets the position back to 0, so you can reread all the data in the buffer
                        buffer.rewind();

                        try {
                            logger.debug("Sending '{}' in the channel '{}'->'{}'",
                                    new Object[] { new String(buffer.array()), theChannel.getLocalAddress(),
                                            theChannel.getRemoteAddress() });
                            // www.businessdictionary.com/definition/datagram.html
                            // writes a datagram to this channel
                            theChannel.write(buffer);
                        } catch (NotYetConnectedException e) {
                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                    "The remote host is not yet connected");
                            error = true;
                        } catch (ClosedChannelException e) {
                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                    "The connection to the remote host is closed");
                            error = true;
                        } catch (IOException e) {
                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                    "An Io exception occurred");
                            error = true;
                        }

                        if (error) {
                            try {
                                // closes the channel if it hasn't being closed already
                                theChannel.close();
                            } catch (Exception e) {
                                logger.warn("An exception occured while closing the channel '{}': {}",
                                        datagramChannel, e.getMessage());
                            }
                        }

                        // re-establish connection
                        onConnectionLost();

                    }
                }
            }

        } finally {
            lock.unlock();
        }
    }

    /**
     * establishes a connection for reading and writing
     */
    private void establishConnection() {
        lock.lock();

        try {
            // returns the thing which belongs to the handler
            // gets the status information of the thing
            // gets the detail of the status and checks if there are no configuration errors
            // and checks if the ipAddress is not null or empty
            if (getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR
                    && getConfig().get(IP_ADDRESS) != null || getConfig().get(IP_ADDRESS) != "") {

                try {
                    // opens a datagram channel
                    datagramChannel = DatagramChannel.open();
                } catch (Exception e) {
                    // updates the status of the thing
                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                            "An exception occurred while creating a datagram channel");
                }

                try {
                    // The Channel must be in non-blocking mode to be used with a Selector
                    datagramChannel.configureBlocking(false);

                } catch (IOException e) {
                    // updates the status of the thing
                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                            "An error occurred while creating a datagram channel");
                }

                synchronized (selector) {
                    selector.wakeup();

                    // The interest set is the set of events you are interested in "selecting"
                    int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

                    try {
                        // registers this channel with the given selector, returning a selectionKey
                        datagramChannelKey = datagramChannel.register(selector, interestSet);

                    } catch (ClosedChannelException e) {
                        // updates the status of the thing
                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                "An exception occured while registering a channel");
                    }

                    // creating an IP Socket Address (remoteAddress) for the remote port number
                    InetSocketAddress remoteAddress = new InetSocketAddress((String) getConfig().get(IP_ADDRESS),
                            REMOTE_PORT_NUMBER);

                    try {
                        logger.trace("Connecting to the channel for {}", remoteAddress);
                        // connects this channel's socket to the given remote peer address
                        datagramChannel.connect(remoteAddress);

                        // after connection update the status of the thing to Online
                        onConnectionResumed();

                    } catch (Exception e) {
                        logger.error("An error occured while connecting connecting to '{}:{}' : {}",
                                new Object[] { (String) getConfig().get(IP_ADDRESS) }, REMOTE_PORT_NUMBER,
                                e.getMessage());

                        // update the status of the thing
                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                " An excepton ocurred while connecting");

                    }
                }

            } else {
                // update the status of the thing
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                        getThing().getStatusInfo().getDescription());

            }

        } finally {
            lock.unlock();
        }

    }

    /**
     * sends command to the MART adapter
     *
     * @param command
     */
    private void sendMartCommand(String command) {

        if (command != null) {
            // allocate a new buffer with the command's capacity
            ByteBuffer byteBuffer = ByteBuffer.allocate(command.getBytes().length);

            try {
                byteBuffer.put(command.getBytes("ASCII"));
                writer(byteBuffer, datagramChannel);
            } catch (UnsupportedEncodingException | NumberFormatException e) {
                logger.debug("Exception occurred while sending a command to the MART Adapter '{}':{}",
                        getThing().getUID(), e.getMessage());
            }
        }
    }

    /**
     * this method updates the status of the thing to online
     */
    public void onConnectionResumed() {
        updateStatus(ThingStatus.ONLINE);
    }

    /**
     * establishes a connection if connection is lost
     */
    public void onConnectionLost() {
        establishConnection();
    }

    /**
     * runnable is a task to be executed by a thread
     * the first instruction to be executed is to check if the channel's (datagramChannel) socket is connected for
     * communication
     * the second instruction to be executed is to read from the channel (datagramChannel)
     */
    private Runnable listeningRunnable = new Runnable() {

        @Override
        public void run() {
            lock.lock();

            try {
                // if there are no configuration issue of the thing. if there is an issue it prevents communication
                // with the represented thing and therefore the thing must be reconfigured
                if (getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR) {
                    // check if the channel for communication with the represented thing is established
                    // check if the channel's socket is connected
                    // otherwise update the status of the thing to make sure it is offline and re-establish
                    // the channel for the communication
                    if (datagramChannel == null || !datagramChannel.isConnected()) {
                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                "The connection is not yet established");
                    }

                    // if the channel's socket is connected
                    if (datagramChannel.isConnected()) {
                        long stamp = System.currentTimeMillis();
                        // test whether the ip address is reachable in 3 seconds if it is reachable in the timeout
                        // specified, read from the channel
                        if (!InetAddress.getByName(((String) getConfig().get(IP_ADDRESS)))
                                .isReachable(PING_TIME_OUT)) {
                            logger.debug("Ping time out after '{}' milliseconds",
                                    System.currentTimeMillis() - stamp);
                            logger.trace("Disconnecting datagram channel '{}'", datagramChannel);
                            try {
                                // close the channel
                                datagramChannel.close();

                            } catch (IOException e) {
                                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                        "An error occurred while closing the channel");
                            }

                            // if the channel was closed update the thing status
                            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                                    "Ping timeout occurred");
                            // re-establish the connection
                            onConnectionLost();

                        } else {
                            // if the datagram channel is connected, read from the datagram-channel
                            ByteBuffer buffer = Reader(datagramChannel, BUFFER_SIZE, null);
                            // if the buffer is not null and
                            // the number of elements in the buffer between the current position and
                            // the limit is greater than zero
                            if (buffer != null && buffer.remaining() > 0) {
                                // parse the data read from the datagram-channel
                                readerHandler(buffer, datagramChannel);
                            }
                        }
                    }
                    ByteBuffer buffer = Reader(listenerChannel, BUFFER_SIZE,
                            InetAddress.getByName((String) getConfig().get(IP_ADDRESS)));
                    if (buffer != null && buffer.remaining() > 0) {
                        readerHandler(buffer, listenerChannel);
                    }
                }

            } catch (Exception e) {
                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
                        "An exception occurred while receiving data from the MART adapter");

            } finally {
                lock.unlock();
            }
        }
    };

    /**
     * runnable is a task to be executed by a thread
     * the task performed here is to write to the channel
     */
    private Runnable pollingRunnable = new Runnable() {

        @Override
        public void run() {
            try {

                String requestUpdate = "Adapter Update";

                // create a byte buffer and allocate a capacity
                ByteBuffer byteBuffer = ByteBuffer.allocate(requestUpdate.getBytes().length);
                try {
                    // transfers the entire content of the byte array into the byteBuffer
                    byteBuffer.put(requestUpdate.getBytes("ASCII"));
                    writer(byteBuffer, datagramChannel);
                } catch (UnsupportedEncodingException | NumberFormatException e) {
                    logger.error("An exception occurred while polling the MART adapter for '{}': {}",
                            getThing().getUID(), e.getMessage());
                }

                requestUpdate = "Fridge Update";
                byteBuffer = ByteBuffer.allocate(requestUpdate.getBytes().length);
                try {
                    byteBuffer.put(requestUpdate.getBytes("ASCII"));
                    writer(byteBuffer, datagramChannel);

                } catch (UnsupportedEncodingException | NumberFormatException e) {

                }

                requestUpdate = "Television Update";
                byteBuffer = ByteBuffer.allocate(requestUpdate.getBytes().length);
                try {
                    byteBuffer.put(requestUpdate.getBytes("ASCII"));
                    writer(byteBuffer, datagramChannel);

                } catch (UnsupportedEncodingException | NumberFormatException e) {

                }

                requestUpdate = "Outside Update";
                byteBuffer = ByteBuffer.allocate(requestUpdate.getBytes().length);
                try {
                    byteBuffer = ByteBuffer.allocate(requestUpdate.getBytes("ASCII").length);
                    writer(byteBuffer, datagramChannel);

                } catch (UnsupportedEncodingException | NumberFormatException e) {

                }

            } catch (Exception e) {
                // TODO: handle exception
            }

        }
    };

    /**
     * post device usage data to the MART web-service
     */
    private void postman() throws Exception {
        String endpoints = "";
        URL url = new URL(endpoints);
        HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();

        // add request header

        // send post request

    }
}