com.whizzosoftware.wzwave.channel.ZWaveDataFrameTransactionInboundHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.whizzosoftware.wzwave.channel.ZWaveDataFrameTransactionInboundHandler.java

Source

/*******************************************************************************
 * Copyright (c) 2013 Whizzo Software, LLC.
 * 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 com.whizzosoftware.wzwave.channel;

import com.whizzosoftware.wzwave.frame.ApplicationUpdate;
import com.whizzosoftware.wzwave.frame.DataFrame;
import com.whizzosoftware.wzwave.frame.Frame;
import com.whizzosoftware.wzwave.frame.RequestNodeInfo;
import com.whizzosoftware.wzwave.frame.transaction.DataFrameTransaction;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.concurrent.ScheduledFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
 * Handler for all Z-Wave frame transactions. Responsible for tracking the state of the current transaction
 * including successes, failures and timeouts.
 *
 * @author Dan Noguerol
 */
public class ZWaveDataFrameTransactionInboundHandler extends ChannelInboundHandlerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(ZWaveDataFrameTransactionInboundHandler.class);

    private static final int MAX_SEND_COUNT = 2;

    private ChannelHandlerContext handlerContext;
    private DataFrameTransaction currentDataFrameTransaction;
    private boolean processingTransactionCompletion = false;
    private ScheduledFuture timeoutFuture;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.handlerContext = ctx;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof Frame) {
            Frame frame = (Frame) msg;
            if (hasCurrentRequestTransaction()) {
                logger.trace("Received frame within transaction context: {}", frame);

                if (currentDataFrameTransaction.addFrame(frame)) {
                    if (currentDataFrameTransaction.isComplete()) {
                        // cancel the timeout callback
                        if (timeoutFuture != null) {
                            timeoutFuture.cancel(true);
                            timeoutFuture = null;
                        }

                        // flag that we're in the process of completing a frame transaction so that any code that checks
                        // will know that the transaction isn't quite done yet
                        processingTransactionCompletion = true;

                        if (!currentDataFrameTransaction.hasError()) {
                            DataFrame finalFrame = currentDataFrameTransaction.getFinalFrame();
                            logger.trace("*** Data frame transaction completed with final frame: {}", finalFrame);
                            logger.trace("");

                            // if there's an ApplicationUpdate with no node ID (e.g. when there's a app update failure), attempt
                            // to set the node ID based on the request frame that triggered it
                            if (finalFrame instanceof ApplicationUpdate) {
                                ApplicationUpdate update = (ApplicationUpdate) finalFrame;
                                if ((update.getNodeId() == null || update.getNodeId() == 0)
                                        && currentDataFrameTransaction.getStartFrame() instanceof RequestNodeInfo) {
                                    update.setNodeId(((RequestNodeInfo) currentDataFrameTransaction.getStartFrame())
                                            .getNodeId());
                                }
                            }

                            // pass the final frame down the pipeline
                            if (finalFrame != null) {
                                ctx.fireChannelRead(finalFrame);
                            }

                        } else {
                            attemptResend(ctx);
                        }

                        // clear the current transaction
                        currentDataFrameTransaction = null;

                        // now the frame transaction is truly done
                        processingTransactionCompletion = false;

                        // alert the outbound pipeline that a frame transaction has been completed
                        ChannelPipeline pipeline = ctx.pipeline();
                        if (pipeline != null) {
                            ZWaveQueuedOutboundHandler writeHandler = (ZWaveQueuedOutboundHandler) ctx.pipeline()
                                    .get("writeQueue");
                            if (writeHandler != null) {
                                writeHandler.onDataFrameTransactionComplete();
                            }
                        }
                    }
                } else {
                    logger.trace("Transaction didn't consume frame so passing it along");
                    ctx.fireChannelRead(msg);
                }
            } else {
                logger.trace("Received frame outside of transaction context: {}", frame);
                ctx.fireChannelRead(msg);
            }
        } else if (msg instanceof TransactionTimeout) {
            // if a timeout is received for the current transaction, attempt to resend; otherwise ignore it
            TransactionTimeout tt = (TransactionTimeout) msg;
            if (tt.getId().equals(currentDataFrameTransaction.getId())) {
                logger.trace("Transaction timed out");
                attemptResend(ctx);
            }
        }
    }

    public void onDataFrameWrite(DataFrame frame) {
        logger.trace("Detected data frame write: {}", frame);
        if (!hasCurrentRequestTransaction()) {
            currentDataFrameTransaction = frame.createTransaction();
            if (currentDataFrameTransaction != null) {
                logger.trace("*** Data frame transaction started for {}", frame);

                // start timeout
                if (handlerContext != null && handlerContext.executor() != null) {
                    timeoutFuture = handlerContext.executor().schedule(
                            new TransactionTimeout(currentDataFrameTransaction.getId(), handlerContext, this),
                            currentDataFrameTransaction.getTimeout(), TimeUnit.MILLISECONDS);
                } else {
                    logger.warn("Unable to schedule transaction timeout callback");
                }
            }
        } else {
            logger.trace("Wrote a data frame with a current transaction: {}", frame);
        }
    }

    public boolean hasCurrentRequestTransaction() {
        return (processingTransactionCompletion
                || (currentDataFrameTransaction != null && !currentDataFrameTransaction.isComplete()));
    }

    protected void attemptResend(ChannelHandlerContext ctx) {
        DataFrame startFrame = currentDataFrameTransaction.getStartFrame();
        if (startFrame.getSendCount() < MAX_SEND_COUNT) {
            logger.debug("Transaction has failed - resending initial request");
            ctx.channel().writeAndFlush(startFrame);
        } else {
            logger.debug("Transaction has failed and has exceeded max resends - aborting");
        }
    }
}