Java tutorial
/* ******************************************************************************* * 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.controller.netty; import com.whizzosoftware.wzwave.channel.*; import com.whizzosoftware.wzwave.channel.event.TransactionCompletedEvent; import com.whizzosoftware.wzwave.channel.event.TransactionFailedEvent; import com.whizzosoftware.wzwave.channel.event.TransactionStartedEvent; import com.whizzosoftware.wzwave.channel.inbound.ACKInboundHandler; import com.whizzosoftware.wzwave.channel.inbound.ZWaveChannelInboundHandler; import com.whizzosoftware.wzwave.channel.inbound.TransactionInboundHandler; import com.whizzosoftware.wzwave.channel.outbound.FrameQueueHandler; import com.whizzosoftware.wzwave.codec.ZWaveFrameDecoder; import com.whizzosoftware.wzwave.codec.ZWaveFrameEncoder; import com.whizzosoftware.wzwave.commandclass.WakeUpCommandClass; import com.whizzosoftware.wzwave.controller.ZWaveController; import com.whizzosoftware.wzwave.controller.ZWaveControllerContext; import com.whizzosoftware.wzwave.controller.ZWaveControllerListener; import com.whizzosoftware.wzwave.frame.*; import com.whizzosoftware.wzwave.node.*; import com.whizzosoftware.wzwave.persist.PersistentStore; import com.whizzosoftware.wzwave.persist.mapdb.MapDbPersistentStore; import com.whizzosoftware.wzwave.util.ByteUtil; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelInitializer; import io.netty.channel.oio.OioEventLoopGroup; import io.netty.channel.rxtx.RxtxChannel; import io.netty.channel.rxtx.RxtxChannelConfig; import io.netty.channel.rxtx.RxtxDeviceAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.util.*; /** * A Netty implementation of a ZWaveController. * * The pipelines look like this: * * I/O Request via Channel or * ChannelHandlerContext * | * +----------------------------------------------------------------+-------------------+ * | ChannelPipeline | | * | | | * | +--------------------------------+ | | * | | ZWaveChannelInboundHandler | | | * | +---------------+----------------+ | | * | /|\ | | * | | | | * | +---------------+----------------+ | | * | | TransactionInboundHandler | | | * | +---------------+----------------+ | | * | /|\ | | * | | \|/ | * | +---------------+-------------------------------------------+--------------+ | * | | FrameQueueHandler | | * | +---------------+-------------------------------------------+--------------+ | * | /|\ | | * | | | | * | +---------------+----------------+ | | * | | ACKInboundHandler | | | * | +---------------+----------------+ | | * | /|\ | | * | | | | * | +---------------+----------------+ +-------------+--------------+ | * | | ZWaveFrameDecoder | | ZWaveFrameEncoder | | * | +---------------+----------------+ +-------------+--------------+ | * | /|\ | | * +--------------------+-------------------------------------------+-------------------+ * | | \|/ | * +--------------------+-------------------------------------------+-------------------+ * | | | | * | [ Socket.read() ] [ Socket.write() ] | * +------------------------------------------------------------------------------------+ * * @author Dan Noguerol */ public class NettyZWaveController implements ZWaveController, ZWaveControllerContext, ZWaveControllerListener, ZWaveChannelListener, NodeListener { private static final Logger logger = LoggerFactory.getLogger(NettyZWaveController.class); private String serialPort; private PersistentStore store; private Bootstrap bootstrap; private Channel channel; private String libraryVersion; private Integer homeId; private Byte nodeId; private ZWaveChannelInboundHandler inboundHandler; private ZWaveControllerListener listener; private final List<ZWaveNode> nodes = new ArrayList<>(); private final Map<Byte, ZWaveNode> nodeMap = new HashMap<>(); /** * Constructor. * * @param serialPort the serial port the Z-Wave controller is accessible from * @param dataDirectory a directory in which to store persistent data */ public NettyZWaveController(String serialPort, File dataDirectory) { this(serialPort, new MapDbPersistentStore(dataDirectory)); } /** * Constructor. * * @param serialPort the serial port for Z-Wave controller is accessible from * @param store the persistent store to use for storing/retrieving node information */ public NettyZWaveController(String serialPort, PersistentStore store) { this.serialPort = serialPort; this.store = store; this.inboundHandler = new ZWaveChannelInboundHandler(this); } public void setChannel(Channel channel) { this.channel = channel; } /* * ZWaveController methods */ @Override public void setListener(ZWaveControllerListener listener) { this.listener = listener; } public void start() { if (channel == null) { // set up Netty bootstrap bootstrap = new Bootstrap(); bootstrap.group(new OioEventLoopGroup()); bootstrap.channel(RxtxChannel.class); bootstrap.handler(new ChannelInitializer<RxtxChannel>() { @Override protected void initChannel(RxtxChannel channel) throws Exception { NettyZWaveController.this.channel = channel; channel.config().setBaudrate(115200); channel.config().setDatabits(RxtxChannelConfig.Databits.DATABITS_8); channel.config().setParitybit(RxtxChannelConfig.Paritybit.NONE); channel.config().setStopbits(RxtxChannelConfig.Stopbits.STOPBITS_1); channel.pipeline().addLast("decoder", new ZWaveFrameDecoder()); channel.pipeline().addLast("ack", new ACKInboundHandler()); channel.pipeline().addLast("encoder", new ZWaveFrameEncoder()); channel.pipeline().addLast("writeQueue", new FrameQueueHandler()); channel.pipeline().addLast("transaction", new TransactionInboundHandler()); channel.pipeline().addLast("handler", inboundHandler); } }); bootstrap.connect(new RxtxDeviceAddress(serialPort)).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { sendDataFrame(new Version()); sendDataFrame(new MemoryGetId()); sendDataFrame(new InitData()); } else { onZWaveConnectionFailure(future.cause()); } } }); } } @Override public void stop() { } @Override public int getHomeId() { return homeId; } @Override public byte getNodeId() { return nodeId; } @Override public String getLibraryVersion() { return libraryVersion; } @Override public Collection<ZWaveNode> getNodes() { return nodes; } @Override public ZWaveNode getNode(byte nodeId) { return nodeMap.get(nodeId); } @Override public void sendDataFrame(DataFrame dataFrame) { channel.write(new OutboundDataFrame(dataFrame, true)); } /* * ZWaveControllerListener methods */ @Override public void onZWaveNodeAdded(ZWaveEndpoint node) { if (listener != null) { listener.onZWaveNodeAdded(node); } } @Override public void onZWaveNodeUpdated(ZWaveEndpoint node) { if (listener != null) { listener.onZWaveNodeUpdated(node); } } @Override public void onZWaveConnectionFailure(Throwable t) { if (listener != null) { listener.onZWaveConnectionFailure(t); } else { logger.error("Connection failure and no listener was set", t); } } @Override public void onZWaveControllerInfo(String libraryVersion, Integer homeId, Byte nodeId) { if (listener != null && libraryVersion != null && homeId != null && nodeId != null) { listener.onZWaveControllerInfo(libraryVersion, homeId, nodeId); } } @Override public void onZWaveInclusionStarted() { if (listener != null) { listener.onZWaveInclusionStarted(); } } @Override public void onZWaveInclusion(NodeInfo nodeInfo, boolean success) { try { logger.trace("Inclusion of new node {}", ByteUtil.createString(nodeInfo.getNodeId())); ZWaveNode node = ZWaveNodeFactory.createNode(nodeInfo, !nodeInfo.hasCommandClass(WakeUpCommandClass.ID), this); logger.trace("Created new node [{}]: {}", node.getNodeId(), node); addNode(node, true); if (listener != null) { listener.onZWaveInclusion(nodeInfo, success); } } catch (NodeCreationException e) { logger.error("Unable to create node", e); } } @Override public void onZWaveInclusionStopped() { if (listener != null) { listener.onZWaveInclusionStopped(); } } @Override public void onZWaveExclusionStarted() { if (listener != null) { listener.onZWaveExclusionStarted(); } } @Override public void onZWaveExclusion(NodeInfo nodeInfo, boolean success) { if (listener != null) { listener.onZWaveExclusion(nodeInfo, success); } } @Override public void onZWaveExclusionStopped() { if (listener != null) { listener.onZWaveExclusionStopped(); } } /* * ZWaveChannelListener methods */ @Override public void onLibraryInfo(String libraryVersion) { this.libraryVersion = libraryVersion; onZWaveControllerInfo(libraryVersion, homeId, nodeId); } @Override public void onControllerInfo(int homeId, byte nodeId) { this.homeId = homeId; this.nodeId = nodeId; onZWaveControllerInfo(libraryVersion, homeId, nodeId); } @Override public void onNodeProtocolInfo(byte nodeId, NodeProtocolInfo npi) { try { logger.trace("Received protocol info for node {}", nodeId); ZWaveNode node = store.getNode(nodeId, this); if (node == null || !node.matchesNodeProtocolInfo(npi)) { node = ZWaveNodeFactory.createNode(new NodeInfo(nodeId, npi.getBasicDeviceClass(), npi.getGenericDeviceClass(), npi.getSpecificDeviceClass()), npi.isListening(), this); logger.trace("Created new node: {}: {}", nodeId, node); } else { logger.debug("Node[{}] matches persistent node information; no need to interview", nodeId); } addNode(node, false); } catch (NodeCreationException e) { logger.error("Unable to create node", e); } } private void addNode(ZWaveNode node, boolean newlyIncluded) { ZWaveNode n = nodeMap.get(node.getNodeId()); if (n != null) { nodes.remove(n); nodeMap.remove(node.getNodeId()); } nodes.add(node); nodeMap.put(node.getNodeId(), node); node.startInterview(this, newlyIncluded); } @Override public void onSendData(SendData sendData) { byte nodeId = sendData.getNodeId(); ZWaveNode node = nodeMap.get(nodeId); if (node != null) { node.onDataFrameReceived(this, sendData); } else { logger.error("Unable to find node " + nodeId); } } @Override public void onApplicationCommand(ApplicationCommand applicationCommand) { ZWaveNode node = nodeMap.get(applicationCommand.getNodeId()); if (node != null) { onNodeUpdate(node, applicationCommand); } else { logger.error("Unable to find node: {}", nodeId); } } @Override public void onApplicationUpdate(ApplicationUpdate applicationUpdate) { Byte nodeId = applicationUpdate.getNodeId(); if (applicationUpdate.didInfoRequestFail()) { logger.trace("UPDATE_STATE_NODE_INFO_REQ_FAILED received"); } if (nodeId != null) { ZWaveNode node = nodeMap.get(nodeId); if (node != null) { onNodeUpdate(node, applicationUpdate); } else { logger.error("Unable to find node: {}", nodeId); } } else { logger.error("Unable to determine node to route ApplicationUpdate to"); } } @Override public void onTransactionStarted(TransactionStartedEvent evt) { logger.trace("Detected start of new transaction: {}", evt.getId()); channel.write(evt); } @Override public void onTransactionComplete(TransactionCompletedEvent evt) { logger.trace("Detected end of transaction: {}", evt.getId()); channel.write(evt); } @Override public void onTransactionFailed(TransactionFailedEvent evt) { logger.trace("Detected transaction failure: {}", evt.getId()); channel.write(evt); // TODO: improve this logic } private void onNodeUpdate(ZWaveNode node, DataFrame df) { node.onDataFrameReceived(this, df); if (node.isStarted()) { onZWaveNodeUpdated(node); } } @Override public void onAddNodeToNetwork(AddNodeToNetwork update) { if (listener != null) { switch (update.getStatus()) { case AddNodeToNetwork.ADD_NODE_STATUS_LEARN_READY: onZWaveInclusionStarted(); break; case AddNodeToNetwork.ADD_NODE_STATUS_DONE: onZWaveInclusionStopped(); break; case AddNodeToNetwork.ADD_NODE_STATUS_ADDING_CONTROLLER: case AddNodeToNetwork.ADD_NODE_STATUS_ADDING_SLAVE: onZWaveInclusion(update.getNodeInfo(), true); break; case AddNodeToNetwork.ADD_NODE_STATUS_FAILED: onZWaveInclusion(update.getNodeInfo(), false); break; default: logger.debug("Received unexpected status from AddNodeToNetwork frame: {}", update.getStatus()); } } } @Override public void onRemoveNodeFromNetwork(RemoveNodeFromNetwork update) { if (listener != null) { switch (update.getStatus()) { case RemoveNodeFromNetwork.REMOVE_NODE_STATUS_LEARN_READY: onZWaveExclusionStarted(); break; case RemoveNodeFromNetwork.REMOVE_NODE_STATUS_DONE: onZWaveExclusionStopped(); break; case RemoveNodeFromNetwork.REMOVE_NODE_STATUS_NODE_FOUND: logger.debug("A node has been found that wants to be excluded: {}", ByteUtil.createString(update.getSource())); break; case RemoveNodeFromNetwork.REMOVE_NODE_STATUS_REMOVING_CONTROLLER: case RemoveNodeFromNetwork.REMOVE_NODE_STATUS_REMOVING_SLAVE: onZWaveExclusion(update.getNodeInfo(), true); break; case RemoveNodeFromNetwork.REMOVE_NODE_STATUS_FAILED: onZWaveExclusion(update.getNodeInfo(), false); break; default: logger.debug("Received unexpected status from RemoveNodeFromNetwork frame: {}", update.getStatus()); } } } @Override public void onSetDefault() { logger.info("Z-Wave controller has been reset to factory default"); } /* * NodeListener methods */ @Override public void onNodeStarted(ZWaveNode node) { // save the newly started node logger.debug("Saving information for node {}", node.getNodeId()); store.saveNode(node); // when a node moves to the "started" state, alert listeners that it's ready to be added onZWaveNodeAdded(node); } }