001/** 002 * SerialDCCppPacketizer.java 003 */ 004package jmri.jmrix.dccpp.serial; 005 006import java.util.concurrent.DelayQueue; 007 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import jmri.jmrix.dccpp.DCCppCommandStation; 012import jmri.jmrix.dccpp.DCCppListener; 013import jmri.jmrix.dccpp.DCCppMessage; 014import jmri.jmrix.dccpp.DCCppPacketizer; 015import jmri.util.ThreadingUtil; 016 017/** 018 * This is an extension of the DCCppPacketizer to handle the device specific 019 * requirements of the DCC++. 020 * <p> 021 * In particular, SerialDCCppPacketizer adds functions to add and remove the 022 * {@literal "<" and ">"} bytes that appear around any message read in. 023 * 024 * Note that the bracket-adding could be pushed up to DCCppPacketizer, as it is 025 * a protocol thing, not an interface implementation thing. We'll come back to 026 * that later. 027 * 028 * What is however interface specific is the background refresh of functions. 029 * DCC++ sends the DCC commands exactly once. A background thread will 030 * repeat the last seen function commands to compensate for any momentary 031 * power loss or to recover from power off / power on events. It only makes 032 * sense to do this on the actual serial interface as it will be transparent for 033 * the 034 * network clients. 035 * 036 * @author Paul Bender Copyright (C) 2005 037 * @author Mark Underwood Copyright (C) 2015 038 * @author Costin Grigoras Copyright (C) 2018 039 * 040 * Based on LIUSBXNetPacketizer by Paul Bender 041 */ 042public class SerialDCCppPacketizer extends DCCppPacketizer { 043 044 final DelayQueue<DCCppMessage> resendFunctions = new DelayQueue<>(); 045 046 boolean activeBackgroundRefresh = true; 047 private DCCppCommandStation cs; 048 049 public SerialDCCppPacketizer(final DCCppCommandStation pCommandStation) { 050 super(pCommandStation); 051 log.debug("Loading Serial Extention to DCCppPacketizer"); 052 cs = pCommandStation; //remember this for later 053 } 054 055 /** 056 * Determine how many bytes the entire message will take, including 057 * space for header and trailer 058 * 059 * @param m The message to be sent 060 * @return Number of bytes 061 */ 062 @Override 063 protected int lengthOfByteStream(final jmri.jmrix.AbstractMRMessage m) { 064 return m.getNumDataElements() + 2; 065 } 066 067 /** 068 * <code>true</code> when the self-rescheduling function refresh action was 069 * initially queued, to avoid duplicate actions 070 */ 071 private boolean backgroundRefreshStarted = false; 072 073 final class RefreshAction implements ThreadingUtil.ThreadAction { 074 @Override 075 public void run() { 076 try { 077 if (activeBackgroundRefresh) { 078 final DCCppMessage message = resendFunctions.poll(); 079 080 if (message != null) { 081 message.setRetries(0); 082 sendDCCppMessage(message, null); 083 } 084 } 085 } finally { 086 ThreadingUtil.runOnLayoutDelayed(this, 250); 087 } 088 } 089 } 090 091 private void enqueueFunction(final DCCppMessage m) { 092 /** 093 * Set again the same group function value 250ms later (or more, 094 * depending on the queue depth) 095 */ 096 m.delayFor(250); 097 resendFunctions.offer(m); 098 099 synchronized (this) { 100 if (!backgroundRefreshStarted) { 101 ThreadingUtil.runOnLayoutDelayed(new RefreshAction(), 250); 102 backgroundRefreshStarted = true; 103 } 104 } 105 } 106 107 @Override 108 public void sendDCCppMessage(final DCCppMessage m, final DCCppListener reply) { 109 final boolean isFunction = m.isFunctionMessage(); 110 111 /** 112 * Remove a previous value for the same function (DCC address + function 113 * group) based on 114 * {@link jmri.jmrix.dccpp.DCCppMessage#equals(DCCppMessage)} 115 */ 116 if (isFunction) 117 resendFunctions.remove(m); 118 119 super.sendDCCppMessage(m, reply); 120 121 if (isFunction) { //repeat the message if the command station needs JMRI to send the refresh 122 if (cs.isFunctionRefreshRequired()) { 123 enqueueFunction(m); 124 } 125 } 126 } 127 128 /** 129 * Clear the background refresh queue. The state is still kept in JMRI. 130 */ 131 public void clearRefreshQueue() { 132 resendFunctions.clear(); 133 } 134 135 /** 136 * Check how many entries are in the background refresh queue 137 * 138 * @return number of queued function groups 139 */ 140 public int getQueueLength() { 141 return resendFunctions.size(); 142 } 143 144 /** 145 * Enable or disable the background refresh thread 146 * 147 * @param activeState <code>true</code> to keep refreshing the functions, 148 * <code>false</code> to disable this functionality. 149 * @return the previous active state of the background refresh thread 150 */ 151 public boolean setActiveRefresh(final boolean activeState) { 152 final boolean oldActiveState = activeBackgroundRefresh; 153 154 activeBackgroundRefresh = activeState; 155 156 return oldActiveState; 157 } 158 159 /** 160 * Check if the background function refresh thread is active or not 161 * 162 * @return the background refresh status, <code>true</code> for active, 163 * <code>false</code> if disabled. 164 */ 165 public boolean isActiveRefresh() { 166 return activeBackgroundRefresh; 167 } 168 169 private static final Logger log = LoggerFactory.getLogger(SerialDCCppPacketizer.class); 170}