001package jmri.jmrix.can.cbus.node; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.GridLayout; 006import java.awt.Toolkit; 007import java.util.TimerTask; 008 009import javax.swing.JFormattedTextField; 010import javax.swing.JLabel; 011import javax.swing.JPanel; 012import javax.swing.JSpinner; 013import javax.swing.SpinnerNumberModel; 014import javax.swing.event.ChangeEvent; 015import javax.swing.text.DefaultFormatter; 016 017import jmri.jmrix.can.CanListener; 018import jmri.jmrix.can.CanMessage; 019import jmri.jmrix.can.CanSystemConnectionMemo; 020import jmri.jmrix.can.CanReply; 021import jmri.jmrix.can.cbus.CbusConstants; 022import jmri.jmrix.can.cbus.CbusMessage; 023import jmri.jmrix.can.cbus.CbusPreferences; 024import jmri.jmrix.can.cbus.CbusSend; 025import jmri.util.TimerUtil; 026import jmri.util.swing.JmriJOptionPane; 027 028public class CbusAllocateNodeNumber implements CanListener { 029 030 private final CbusNodeTableDataModel nodeModel; 031 private final CanSystemConnectionMemo _memo; 032 private final CbusSend send; 033 034 private JLabel rqNNtext; 035 private int baseNodeNum; 036 private boolean WAITINGRESPONSE_RQNN_PARAMS; 037 private boolean NODE_NUM_DIALOGUE_OPEN; 038 private boolean WAITING_RESPONSE_NAME; 039 private int[] _paramsArr; 040 private String _tempNodeName; 041 private JLabel rqNNspinnerlabel; 042 private int _timeout; 043 044 public CbusAllocateNodeNumber(CanSystemConnectionMemo memo, CbusNodeTableDataModel model) { 045 046 nodeModel = model; 047 // connect to the CanInterface 048 _memo = memo; 049 addTc(memo); 050 send = new CbusSend(memo); 051 052 baseNodeNum = 256; 053 _paramsArr = null; 054 WAITINGRESPONSE_RQNN_PARAMS = false; 055 NODE_NUM_DIALOGUE_OPEN = false; 056 WAITING_RESPONSE_NAME = false; 057 _timeout = CbusNodeTimerManager.SINGLE_MESSAGE_TIMEOUT_TIME; 058 _tempNodeName=""; 059 } 060 061 062 /** 063 * 064 * @param nn -1 if already in setup from unknown, 0 if entering from SLiM, 065 * else previous node number 066 * @param nodeText 067 */ 068 private void startnodeallocation(int nn, String nodeText) { 069 070 if (NODE_NUM_DIALOGUE_OPEN) { 071 return; 072 } 073 074 NODE_NUM_DIALOGUE_OPEN=true; 075 _tempNodeName=""; 076 077 JPanel rqNNpane = new JPanel(); 078 JPanel bottomrqNNpane = new JPanel(); 079 rqNNspinnerlabel = new JLabel(Bundle.getMessage("NdRqNnSelect")); 080 081 bottomrqNNpane.setLayout(new GridLayout(2, 1)); 082 rqNNpane.setLayout(new BorderLayout()); 083 rqNNtext = new JLabel(Bundle.getMessage("NdRqNdDetails")); 084 085 String popuplabel; 086 087 baseNodeNum = nodeModel.getNextAvailableNodeNumber(baseNodeNum); 088 089 switch (nn) { 090 case 0: 091 popuplabel=Bundle.getMessage("NdEntrSlimTitle"); 092 _paramsArr = null; // reset just in case 093 break; 094 case -1: 095 popuplabel="Node found in Setup Mode"; 096 // not resetting _paramsArr as may be set from found in setup 097 if ( nodeText != null ) { 098 rqNNtext.setText(nodeText); 099 } 100 break; 101 default: 102 popuplabel=Bundle.getMessage("NdEntrNumTitle",String.valueOf(nn)); 103 _paramsArr = null; // reset just in case 104 baseNodeNum = nn; 105 break; 106 } 107 108 JSpinner rqnnSpinner = getNewRqnnSpinner(); 109 rqnnSpinner.firePropertyChange("open", false, true); // reset node text 110 rqNNpane.add(rqNNtext, BorderLayout.CENTER); 111 bottomrqNNpane.add(rqNNspinnerlabel); 112 bottomrqNNpane.add(rqnnSpinner); 113 114 rqNNpane.add(bottomrqNNpane, BorderLayout.PAGE_END); 115 116 Toolkit.getDefaultToolkit().beep(); 117 118 if ( _paramsArr==null ) { 119 WAITINGRESPONSE_RQNN_PARAMS=true; 120 send.nodeRequestParamSetup(); 121 } 122 123 int option = JmriJOptionPane.showConfirmDialog(null, 124 rqNNpane, 125 popuplabel, 126 JmriJOptionPane.OK_CANCEL_OPTION); 127 128 if (option == JmriJOptionPane.OK_OPTION) { 129 int newval = (Integer) rqnnSpinner.getValue(); 130 baseNodeNum = newval; 131 setSendSNNTimeout(); 132 send.nodeSetNodeNumber(newval); 133 } 134 NODE_NUM_DIALOGUE_OPEN=false; 135 WAITINGRESPONSE_RQNN_PARAMS=false; 136 } 137 138 private JSpinner getNewRqnnSpinner() { 139 140 JSpinner rqnnSpinner = new JSpinner(new SpinnerNumberModel(baseNodeNum, 1, 65535, 1)); 141 rqnnSpinner.setToolTipText((Bundle.getMessage("ToolTipNodeNumber"))); 142 143 JSpinner.NumberEditor editor = new JSpinner.NumberEditor(rqnnSpinner, "#"); 144 rqnnSpinner.setEditor(editor); 145 146 JFormattedTextField rqfield = (JFormattedTextField) editor.getComponent(0); 147 DefaultFormatter rqformatter = (DefaultFormatter) rqfield.getFormatter(); 148 rqformatter.setCommitsOnValidEdit(true); 149 rqfield.setBackground(Color.white); 150 rqnnSpinner.addChangeListener((ChangeEvent e) -> { 151 int newval = (Integer) rqnnSpinner.getValue(); 152 153 if (!CbusNodeConstants.getReservedModule(newval).isEmpty()) { 154 rqNNspinnerlabel.setText(CbusNodeConstants.getReservedModule(newval)); 155 rqfield.setBackground(Color.yellow); 156 } 157 else { 158 rqNNspinnerlabel.setText(Bundle.getMessage("NdRqNnSelect")); 159 rqfield.setBackground(Color.white); 160 } 161 if ( !nodeModel.getNodeNumberName(newval).isEmpty() ) { 162 rqNNspinnerlabel.setText(Bundle.getMessage("NdNumInUse",nodeModel.getNodeNumberName(newval))); 163 rqfield.setBackground(Color.red); 164 } 165 }); 166 return rqnnSpinner; 167 } 168 169 protected TimerTask sendSNNTask; 170 171 private void clearSendSNNTimeout(){ 172 if (sendSNNTask != null ) { 173 sendSNNTask.cancel(); 174 sendSNNTask = null; 175 } 176 } 177 178 private void setSendSNNTimeout() { 179 sendSNNTask = new TimerTask() { 180 @Override 181 public void run() { 182 sendSNNTask = null; 183 log.error("No confirmation from node when setting node number {}", baseNodeNum ); 184 JmriJOptionPane.showMessageDialog(null, 185 Bundle.getMessage("NnAllocError",baseNodeNum), Bundle.getMessage("WarningTitle"), 186 JmriJOptionPane.ERROR_MESSAGE); 187 clearSendSNNTimeout(); 188 } 189 }; 190 TimerUtil.schedule(sendSNNTask, _timeout); 191 } 192 193 /** 194 * Set the SNN timeout, for Testing purposes 195 * @param newVal Timeout value in ms 196 */ 197 protected void setTimeout( int newVal){ 198 _timeout = newVal; 199 } 200 201 /** 202 * If popup not open send a setup param request to try and catch nodes awaiting number allocation 203 * when an all node respond message is sent. 204 * @param m Outgoing CanMessage 205 */ 206 @Override 207 public void message(CanMessage m) { // outgoing cbus message 208 if ( m.extendedOrRtr() ) { 209 return; 210 } 211 if (CbusMessage.getOpcode(m) == CbusConstants.CBUS_QNN) { 212 if (!NODE_NUM_DIALOGUE_OPEN) { 213 send.nodeRequestParamSetup(); 214 } 215 } 216 } 217 218 /** 219 * Capture CBUS_RQNN, CBUS_PARAMS, CBUS_NNACK, CBUS_NAME 220 * @param m incoming CanReply 221 */ 222 @Override 223 public void reply(CanReply m) { 224 if ( m.extendedOrRtr() ) { 225 return; 226 } 227 // run on GUI not Layout thread as pretty much all of this is GUI based. 228 // and could be awaiting from response from JDialog. 229 jmri.util.ThreadingUtil.runOnGUIEventually( ()->{ 230 processAllocateFrame(m); 231 }); 232 } 233 234 private void processAllocateFrame(CanReply m){ 235 switch (CbusMessage.getOpcode(m)) { 236 case CbusConstants.CBUS_RQNN: 237 // node requesting a number, nn is existing number 238 startnodeallocation( ( m.getElement(1) * 256 ) + m.getElement(2), null ); 239 break; 240 case CbusConstants.CBUS_PARAMS: 241 processNodeParams(m); 242 break; 243 case CbusConstants.CBUS_NNACK: // node number acknowledge 244 clearSendSNNTimeout(); 245 // if nodes are allowed to be added to node table, add. 246 // this is done here so any known parameters can be passed directly rather than re-requested 247 if ( _memo.get(CbusPreferences.class).getAddNodes() ) { 248 int nodeNum = m.getElement(1) * 256 + m.getElement(2); 249 nodeModel.setRequestNodeDisplay(nodeNum); 250 // provide will add to table 251 CbusNode nd = nodeModel.provideNodeByNodeNum( nodeNum ); 252 nd.getCanListener().setParamsFromSetup(_paramsArr); 253 nd.setNodeNameFromName(_tempNodeName); 254 nd.resetNodeAll(); 255 nodeModel.startUrgentFetch(); 256 nodeModel.setRequestNodeDisplay(-1); 257 send.searchForCommandStations(); 258 } 259 _paramsArr = null; 260 break; 261 case CbusConstants.CBUS_NAME: 262 processNodeName(m); 263 break; 264 default: 265 break; 266 } 267 } 268 269 private void processNodeParams(CanReply m) { 270 _paramsArr = new int[] { m.getElement(1),m.getElement(2), 271 m.getElement(3),m.getElement(4), m.getElement(5), 272 m.getElement(6),m.getElement(7) }; 273 274 StringBuilder nodepropbuilder = new StringBuilder(40); 275 nodepropbuilder.append (CbusNodeConstants.getManu( _paramsArr[0] )); 276 nodepropbuilder.append (" "); 277 nodepropbuilder.append( CbusNodeConstants.getModuleType( _paramsArr[0] , _paramsArr[2] )); 278 279 if (WAITINGRESPONSE_RQNN_PARAMS) { 280 rqNNtext.setText(nodepropbuilder.toString()); 281 WAITINGRESPONSE_RQNN_PARAMS=false; 282 } 283 else if (!NODE_NUM_DIALOGUE_OPEN) { 284 startnodeallocation( -1, nodepropbuilder.toString() ); 285 } 286 287 if ( CbusNodeConstants.getModuleType( _paramsArr[0] , _paramsArr[2] ).isEmpty() ) { 288 WAITING_RESPONSE_NAME = true; 289 send.rQmn(); // request node type name if not recognised 290 } 291 } 292 293 private void processNodeName(CanReply m){ 294 if (WAITING_RESPONSE_NAME) { 295 WAITING_RESPONSE_NAME = false; 296 StringBuilder rval = new StringBuilder(10); 297 rval.append("CAN"); 298 rval.append(String.format("%c", (char) m.getElement(1) )); 299 rval.append(String.format("%c", (char) m.getElement(2) )); 300 rval.append(String.format("%c", (char) m.getElement(3) )); 301 rval.append(String.format("%c", (char) m.getElement(4) )); 302 rval.append(String.format("%c", (char) m.getElement(5) )); 303 rval.append(String.format("%c", (char) m.getElement(6) )); 304 rval.append(String.format("%c", (char) m.getElement(7) )); 305 _tempNodeName = rval.toString().trim(); 306 307 StringBuilder nodepropbuilder = new StringBuilder(40); 308 nodepropbuilder.append (CbusNodeConstants.getManu( _paramsArr[0] )); 309 nodepropbuilder.append (" "); 310 nodepropbuilder.append (_tempNodeName); 311 312 rqNNtext.setText(nodepropbuilder.toString()); 313 } 314 } 315 316 public void dispose(){ 317 clearSendSNNTimeout(); 318 removeTc(_memo); 319 } 320 321 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusAllocateNodeNumber.class); 322 323}