001package jmri.jmrix.loconet.uhlenbrock; 002 003import jmri.jmrix.loconet.LnConstants; 004import jmri.jmrix.loconet.LocoNetMessage; 005import org.slf4j.Logger; 006import org.slf4j.LoggerFactory; 007 008import java.util.Locale; 009import java.util.Objects; 010 011/** 012 * Supporting class for Uhlenbrock LocoNet LNCV Programming and Direct Format messaging. 013 * Structure adapted from {@link jmri.jmrix.loconet.lnsvf2.LnSv2MessageContents} 014 * 015 * Some of the message formats used in this class are Copyright Uhlenbrock.de 016 * and used with permission as part of the JMRI project. That permission does 017 * not extend to uses in other software products. If you wish to use this code, 018 * algorithm or these message formats outside of JMRI, please contact Uhlenbrock. 019 * 020 * @author Egbert Broerse Copyright (C) 2020, 2021 021 */ 022public class LncvMessageContents { 023 private final int opc; 024 private final int src; 025 private final int dst_l; 026 private final int dst_h; 027 private final int dst; 028 private final int cmd; 029 private final int art_l; // D1 030 private final int art_h; // D2 031 private final int art; 032 private final String sArt; 033 private final int cvn_l; // D3 034 private final int cvn_h; // D4 035 private final int cvn; 036 private final String sCvn; 037 private final int mod_l; // D5 038 private final int mod_h; // D6 039 private final int mod; 040 private final String sMod; 041 private final int cmd_data; // D7 042 private final LncvCommand command; 043 044 // LocoNet "LNCV format" helper definitions: length byte value for LNCV message 045 public final static int LNCV_LENGTH_ELEMENT_VALUE = 0x0f; 046 public final static int LNCV_LNMODULE_VALUE = 0x05; 047 public final static int LNCV_CS_SRC_VALUE = 0x01; 048 public final static int LNCV_PC_SRC_VALUE = 0x08; 049 public final static int LNCV_CSDEST_VALUE = 0x4b49; 050 public final static int LNCV_ALL = 0xffff; // decimal 65535 051 public final static int LNCV_ALL_MASK = 0xff00; // decimal 65535 052 // the valid range for module addresses (CV0) as per the LNCV spec. 053 public final static int LNCV_MIN_MODULEADDR = 0; 054 public final static int LNCV_MAX_MODULEADDR = 65534; 055 056 // LocoNet "LNCV format" helper definitions: indexes into the LocoNet message 057 public final static int LNCV_LENGTH_ELEMENT_INDEX = 1; 058 public final static int LNCV_SRC_ELEMENT_INDEX = 2; 059 public final static int LNCV_DST_L_ELEMENT_INDEX = 3; 060 public final static int LNCV_DST_H_ELEMENT_INDEX = 4; 061 public final static int LNCV_CMD_ELEMENT_INDEX = 5; 062 public final static int PXCT1_ELEMENT_INDEX = 6; 063 public final static int LNCV_ART_L_ELEMENT_INDEX = 7; 064 public final static int LNCV_ART_H_ELEMENT_INDEX = 8; 065 public final static int LNCV_CVN_L_ELEMENT_INDEX = 9; 066 public final static int LNCV_CVN_H_ELEMENT_INDEX = 10; 067 public final static int LNCV_MOD_L_ELEMENT_INDEX = 11; // val_l reply is in same positions as mod_l read 068 public final static int LNCV_MOD_H_ELEMENT_INDEX = 12; // val_h reply is in same positions as mod_h read 069 public final static int LNCV_CMDDATA_ELEMENT_INDEX = 13; 070 // Checksum = index 14 071 072 // helpers for decoding CV format 2 messages (no other OCP_PEER_XFER messages with length 0x0f) 073 public final static int LNCV_SRC_ELEMENT_MASK = 0x7f; 074 public final static int PXCT1_ELEMENT_VALIDITY_CHECK_MASK = 0x70; 075 public final static int LNCV_ART_L_ARTL7_CHECK_MASK = 0x01; 076 public final static int LNCV_ART_H_ARTH7_CHECK_MASK = 0x02; 077 public final static int LNCV_CVN_L_CVNL7_CHECK_MASK = 0x04; 078 public final static int LNCV_CVN_H_CVNH7_CHECK_MASK = 0x08; 079 public final static int LNCV_MOD_L_MODL7_CHECK_MASK = 0x10; 080 public final static int LNCV_MOD_H_MODH7_CHECK_MASK = 0x20; 081 public final static int LNCV_CMDDATA_DAT7_CHECK_MASK = 0x40; 082 083 // LocoNet "LNCV format" helper definitions for data 084 // public final static int LNCV_DATA_START = 0x00; 085 // public final static int LNCV_DATA_END = 0x40; 086 public final static int LNCV_DATA_PROFF_MASK = 0x40; 087 public final static int LNCV_DATA_PRON_MASK = 0x80; 088 public final static int LNCV_DATA_LED1_MASK = 0xff; 089 public final static int LNCV_DATA_LED2_MASK = 0xfe; 090 public final static int LNCV_DATA_RO_MASK = 0x01; 091 092 // helpers for decoding LNCV_CMD 093 public final static int LNCV_CMD_WRITE = 0x20; 094 public final static int LNCV_CMD_READ = 0x21; 095 public final static int LNCV_CMD_READ_REPLY = 0x1f; // reply to both LNCV_CMD_READ and ENTER_PROG_MOD (in which case CV0 VAL = MOD) 096 // reply to LNCV_CMD_WRITE = LACK, already defined as general LocoNet message type 097 098 099 /** 100 * Create a new LncvMessageContents object from a LocoNet message. 101 * 102 * @param m LocoNet message containing an LNCV Programming Format message 103 * @throws IllegalArgumentException if the LocoNet message is not a valid, supported LNCV Programming Format 104 * message 105 */ 106 public LncvMessageContents(LocoNetMessage m) throws IllegalArgumentException { 107 108 //log.debug("interpreting a LocoNet message - may be an LNCV message"); // NOI18N 109 if (!isSupportedLncvMessage(m)) { 110 //log.debug("interpreting a LocoNet message - is NOT an LNCV message"); // NOI18N 111 throw new IllegalArgumentException("LocoNet message is not an LNCV message"); // NOI18N 112 } 113 this.command = extractMessageType(m); 114 115 opc = m.getOpCode(); 116 src = m.getElement(LNCV_SRC_ELEMENT_INDEX); 117 118 dst_l = m.getElement(LNCV_DST_L_ELEMENT_INDEX); 119 dst_h = m.getElement(LNCV_DST_H_ELEMENT_INDEX); 120 dst = dst_l + (256 * dst_h); 121 log.debug("src={}, dst={}{}", src, dst, (dst == 19273 ? "=IK" : "")); // must use vars for CI 122 123 cmd = m.getElement(LNCV_CMD_ELEMENT_INDEX); 124 125 int pxct1 = m.getElement(PXCT1_ELEMENT_INDEX); 126 String svx1bin = String.format("%8s", Integer.toBinaryString(pxct1)).replace(' ', '0'); 127 log.debug("PXCT1 HIBITS = {}", svx1bin); 128 129 art_l = m.getElement(LNCV_ART_L_ELEMENT_INDEX) + (((pxct1 & LNCV_ART_L_ARTL7_CHECK_MASK) == LNCV_ART_L_ARTL7_CHECK_MASK) ? 0x80 : 0); 130 art_h = m.getElement(LNCV_ART_H_ELEMENT_INDEX) + (((pxct1 & LNCV_ART_H_ARTH7_CHECK_MASK) == LNCV_ART_H_ARTH7_CHECK_MASK) ? 0x80 : 0); 131 art = art_l + (256 * art_h); 132 sArt = art + ""; 133 134 cvn_l = m.getElement(LNCV_CVN_L_ELEMENT_INDEX) + (((pxct1 & LNCV_CVN_L_CVNL7_CHECK_MASK) == LNCV_CVN_L_CVNL7_CHECK_MASK) ? 0x80 : 0); 135 cvn_h = m.getElement(LNCV_CVN_H_ELEMENT_INDEX) + (((pxct1 & LNCV_CVN_H_CVNH7_CHECK_MASK) == LNCV_CVN_H_CVNH7_CHECK_MASK) ? 0x80 : 0); 136 cvn = cvn_l + (256 * cvn_h); 137 sCvn = cvn + ""; 138 139 mod_l = m.getElement(LNCV_MOD_L_ELEMENT_INDEX) + (((pxct1 & LNCV_MOD_L_MODL7_CHECK_MASK) == LNCV_MOD_L_MODL7_CHECK_MASK) ? 0x80 : 0); 140 mod_h = m.getElement(LNCV_MOD_H_ELEMENT_INDEX) + (((pxct1 & LNCV_MOD_H_MODH7_CHECK_MASK) == LNCV_MOD_H_MODH7_CHECK_MASK) ? 0x80 : 0); 141 mod = mod_l + (256 * mod_h); 142 sMod = mod + ""; 143 144 cmd_data = m.getElement(LNCV_CMDDATA_ELEMENT_INDEX) + (((pxct1 & LNCV_CMDDATA_DAT7_CHECK_MASK) == LNCV_CMDDATA_DAT7_CHECK_MASK) ? 0x80 : 0); 145 } 146 147 /** 148 * Check a LocoNet message to determine if it is a valid LNCV Programming Format message. 149 * 150 * @param m LocoNet message to check 151 * @return true if LocoNet message m is a supported LNCV Programming Format message, else false. 152 */ 153 public static boolean isSupportedLncvMessage(LocoNetMessage m) { 154 // must be OPC_PEER_XFER or OPC_IMM_PACKET opcode 155 if ((m.getOpCode() != LnConstants.OPC_PEER_XFER) && (m.getOpCode() != LnConstants.OPC_IMM_PACKET)) { 156 //log.debug("cannot be LNCV message because not OPC_PEER_XFER (0xe5) or OPC_IMM_PACKET (0xed)"); // NOI18N 157 return false; 158 } 159 160 // length must be 0x0f 161 if (m.getElement(1) != LNCV_LENGTH_ELEMENT_VALUE) { 162 //log.debug("cannot be LNCV message because not length 0x0f"); // NOI18N 163 return false; 164 } 165 166 // <SRC_ELEMENT> must be correct 167 if ((m.getElement(LNCV_SRC_ELEMENT_INDEX) != LNCV_CS_SRC_VALUE) && (m.getElement(LNCV_SRC_ELEMENT_INDEX) != LNCV_LNMODULE_VALUE) && (m.getElement(LNCV_SRC_ELEMENT_INDEX) != LNCV_PC_SRC_VALUE)) { 168 //log.debug("cannot be LNCV message because Source not correct"); // NOI18N 169 return false; 170 } 171 172 // "command_data" identifier must be correct. handled via Enum 173 // check the (compound) command element 174 int msgData = m.getElement(LNCV_CMDDATA_ELEMENT_INDEX) | (((m.getElement(PXCT1_ELEMENT_INDEX) & LNCV_CMDDATA_DAT7_CHECK_MASK) == LNCV_CMDDATA_DAT7_CHECK_MASK) ? 0x80 : 0); 175 return isSupportedLncvCommand(m.getElement(LNCV_CMD_ELEMENT_INDEX), m.getOpCode(), msgData); 176 } 177 178 /** 179 * Compare reply message against a specific LNCV Programming Format message type. 180 * 181 * @param m LocoNet message to be verified as an LNCV Programming Format message with the specified 182 * <LNCV_CMD> value 183 * @param lncvCmd LNCV Programming Format command to check against 184 * @return true if message is an LNCV Programming Format message of the specified <LNCV_CMD>, else false. 185 */ 186 public static boolean isLnMessageASpecificLncvCommand(LocoNetMessage m, LncvCommand lncvCmd) { 187 if (!isSupportedLncvMessage(m)) { 188 log.debug("rejected in isLnMessageASpecificLncvCommand"); 189 return false; 190 } 191 // compare the <LNCV_CMD> value against cvCmd 192 return Objects.equals(extractMessageType(m), lncvCmd); 193 } 194 195 /** 196 * Interpret a LocoNet message to determine its LNCV compound Programming Format. 197 * If the message is not an LNCV Programming/Direct Format message, returns null. 198 * 199 * @param m LocoNet message containing LNCV Programming Format message 200 * @return LncvCommand found in the LNCV Programming Format message or null if not found 201 */ 202 public static LncvCommand extractMessageType(LocoNetMessage m) { 203 if (isSupportedLncvMessage(m)) { 204 int msgCmd = m.getElement(LNCV_CMD_ELEMENT_INDEX); 205 int msgData = m.getElement(LNCV_CMDDATA_ELEMENT_INDEX) | (((m.getElement(PXCT1_ELEMENT_INDEX) & LNCV_CMDDATA_DAT7_CHECK_MASK) == LNCV_CMDDATA_DAT7_CHECK_MASK) ? 0x80 : 0); 206 //log.debug("msgData = {}", msgData); 207 for (LncvCommand c : LncvCommand.values()) { 208 if (c.matches(msgCmd, m.getOpCode(), msgData)) { 209 //log.debug("LncvCommand match found"); // NOI18N 210 return c; 211 } 212 } 213 } 214 return null; 215 } 216 217 /** 218 * Interpret the LNCV Programming Format message into a human-readable string. 219 * 220 * @return String containing a human-readable version of the LNCV Programming Format message 221 */ 222 @Override 223 public String toString() { 224 Locale l = Locale.getDefault(); 225 return LncvMessageContents.this.toString(l); 226 } 227 228 /** 229 * Interpret the LNCV Programming Format message into a human-readable string. 230 * 231 * @param locale locale to use for the human-readable string 232 * @return String containing a human-readable version of the LNCV Programming Format message, in the language 233 * specified by the Locale, if the properties have been translated to that Locale, else in the default English 234 * language. 235 */ 236 public String toString(Locale locale) { 237 String returnString; 238 //log.debug("interpreting an LNCV message - simple cmd is {}", cmd); // NOI18N 239 240 switch (this.command) { 241 case LNCV_PROG_START: 242 if ((art & LNCV_ALL_MASK) == LNCV_ALL_MASK) { 243 returnString = Bundle.getMessage(locale, "LNCV_ALL_PROG_START_INTERPRETED"); 244 } else if ((mod & LNCV_ALL_MASK) == LNCV_ALL_MASK) { 245 returnString = Bundle.getMessage(locale, "LNCV_ART_PROG_START_INTERPRETED", sArt); 246 } else { 247 returnString = Bundle.getMessage(locale, "LNCV_MOD_PROG_START_INTERPRETED", sArt, sMod); 248 } 249 break; 250 case LNCV_PROG_END: 251 if ((art & LNCV_ALL_MASK) == LNCV_ALL_MASK) { 252 returnString = Bundle.getMessage(locale, "LNCV_ALL_PROG_END_INTERPRETED"); 253 } else if ((mod & LNCV_ALL_MASK) == LNCV_ALL_MASK) { 254 returnString = Bundle.getMessage(locale, "LNCV_ART_PROG_END_INTERPRETED", sArt); 255 } else { 256 returnString = Bundle.getMessage(locale, "LNCV_MOD_PROG_END_INTERPRETED", sArt, sMod); 257 } 258 break; 259 case LNCV_WRITE: // mod positions store CV value in ReadReply 260 returnString = Bundle.getMessage(locale, "LNCV_WRITE_INTERPRETED", sArt, sCvn, sMod); 261 break; 262 case LNCV_READ: 263 // read = module prog start 264 returnString = Bundle.getMessage(locale, "LNCV_READ_INTERPRETED", sArt, sMod, sCvn); 265 break; 266 case LNCV_READ_REPLY: // mod positions store CV value in ReadReply 267 case LNCV_READ_REPLY2: // for Digikeijs DK5088RC not following specs? experimental EBR 268 returnString = Bundle.getMessage(locale, "LNCV_READ_REPLY_INTERPRETED", sArt, sCvn, sMod); 269 break; 270 case LNCV_DIRECT_LED1: // CV position contains module address, Value position contains LED 0-15 on/off 271 returnString = Bundle.getMessage(locale, "LNCV_DIRECT_INTERPRETED", "1", toBinString(mod), sCvn); 272 break; 273 case LNCV_DIRECT_LED2: // CV position contains module address, Value position contains LED 16-31 on/off 274 returnString = Bundle.getMessage(locale, "LNCV_DIRECT_INTERPRETED", "2", toBinString(mod), sCvn); 275 //to16Bits(cvn, true)); 276 break; 277 case LNCV_DIRECT_REPLY: // CV position contains module address, value position = Button on/off message 278 returnString = Bundle.getMessage(locale, "LNCV_DIRECT_REPLY_INTERPRETED", sCvn, sMod); 279 break; 280 default: 281 return Bundle.getMessage(locale, "LNCV_UNDEFINED_MESSAGE") + "\n"; 282 } 283 284 return returnString + "\n"; // NOI18N 285 } 286 287 /** 288 * Convert binary integer to "1010" representation string. 289 * 290 * @param bin integer to convert to binary display string 291 */ 292 private String toBinString(int bin) { 293 return String.format("%8s", Integer.toBinaryString(bin)).replace(' ', '0'); 294 } 295 296 297 /** 298 * Check set of parameters against compound {@link LncvCommand} enum set. 299 * 300 * @param command LNCV CMD value 301 * @param opc OPC value 302 * @param cmdData LNCV cmdData value 303 * @return true if the possibleCmd value is one of the supported (simple) LNCV Programming Format commands 304 */ 305 public static boolean isSupportedLncvCommand(int command, int opc, int cmdData) { 306 //log.debug("CMD = {}-{}-{}", command, opc, cmdData); 307 for (LncvCommand commandToCheck : LncvCommand.values()) { 308 if (commandToCheck.matches(command, opc, cmdData)) { 309 return true; 310 } 311 } 312 return false; 313 } 314 315 /** 316 * Confirm a message corresponds with a valid (known) LNCV Programming Format command. 317 * 318 * @return true if the LNCV message specifies a valid (known) LNCV Programming Format command. 319 */ 320 public boolean isSupportedLncvCommand() { 321 return isSupportedLncvCommand(cmd, opc, cmd_data); 322 } 323 324 /** 325 * @return true if the LNCV message is an LNCV ReadReply message 326 */ 327 public boolean isSupportedLncvReadReply() { 328 return (cmd == LNCV_CMD_READ_REPLY); 329 } 330 331 /** 332 * Create a LocoNet message containing an LNCV Programming Format message. 333 * 334 * @param opc Opcode (<OPC>), see LnConstants 335 * @param source source device (<SRC>) 336 * @param destination destination address (for <DST_L> and <DST_H>) 337 * @param command LNCV Programming simple command (for <LNCV_CMD>), part of 338 * complex command {@link LncvCommand} 339 * @param articleNum manufacturer's hardware class/article code as per specs (4 decimal digits) 340 * @param cvNum CV number (for <LNCV_CVN_L> and <LNCV_CVN_H>) 341 * @param moduleNum module address (for <LNCV_MOD_L> and <LNCV_MOD_H>), 342 * same field is used for CV Value in WRITE to and READ_REPLY from Module 343 * @param cmdData signals programming start/stop: LNCV_DATA_PRON/LNCV_DATA_PROFF 344 * @return LocoNet message for the requested instruction 345 * @throws IllegalArgumentException of command is not a valid LNCV Programming Format <LNCV_CMD> value 346 */ 347 public static LocoNetMessage createLncvMessage(int opc, 348 int source, 349 int destination, 350 int command, 351 int articleNum, 352 int cvNum, 353 int moduleNum, 354 int cmdData) throws IllegalArgumentException { 355 356 if (!isSupportedLncvCommand(command, opc, cmdData)) { 357 throw new IllegalArgumentException("Command is not a supported LNCV command"); // NOI18N 358 } 359 LocoNetMessage m = new LocoNetMessage(LNCV_LENGTH_ELEMENT_VALUE); 360 361 m.setOpCode(opc); 362 m.setElement(LNCV_LENGTH_ELEMENT_INDEX, LNCV_LENGTH_ELEMENT_VALUE); 363 m.setElement(LNCV_SRC_ELEMENT_INDEX, source); 364 m.setElement(LNCV_DST_L_ELEMENT_INDEX, (destination & 0xff)); 365 m.setElement(LNCV_DST_H_ELEMENT_INDEX, (destination >> 8)); 366 //log.debug("element {} = command = {}", LNCV_CMD_ELEMENT_INDEX, command); 367 m.setElement(LNCV_CMD_ELEMENT_INDEX, command); 368 369 int svx1 = 0x0; 370 svx1 = svx1 + (((articleNum & 0x80) == 0x80) ? LNCV_ART_L_ARTL7_CHECK_MASK : 0); 371 svx1 = svx1 + (((articleNum & 0x8000) == 0x8000) ? LNCV_ART_H_ARTH7_CHECK_MASK : 0); 372 svx1 = svx1 + (((cvNum & 0x80) == 0x80) ? LNCV_CVN_L_CVNL7_CHECK_MASK : 0); 373 svx1 = svx1 + (((cvNum & 0x8000) == 0x8000) ? LNCV_CVN_H_CVNH7_CHECK_MASK : 0); 374 svx1 = svx1 + (((moduleNum & 0x80) == 0x80) ? LNCV_MOD_L_MODL7_CHECK_MASK : 0); 375 svx1 = svx1 + (((moduleNum & 0x8000) == 0x8000) ? LNCV_MOD_H_MODH7_CHECK_MASK : 0); 376 //("Fetching hi bit {} of cmdData, value = {}", ((cmdData & 0x80) == 0x80), cmdData); 377 svx1 = svx1 + (((cmdData & 0x80) == 0x80) ? LNCV_CMDDATA_DAT7_CHECK_MASK : 0); 378 // bit 7 always 0 379 m.setElement(PXCT1_ELEMENT_INDEX, svx1); 380 381 m.setElement(LNCV_ART_L_ELEMENT_INDEX, (articleNum & 0x7f)); 382 m.setElement(LNCV_ART_H_ELEMENT_INDEX, ((articleNum >> 8) & 0x7f)); 383 m.setElement(LNCV_CVN_L_ELEMENT_INDEX, (cvNum & 0x7f)); 384 m.setElement(LNCV_CVN_H_ELEMENT_INDEX, ((cvNum >> 8) & 0x7f)); 385 m.setElement(LNCV_MOD_L_ELEMENT_INDEX, (moduleNum & 0x7f)); 386 //log.debug("LNCV MOD_L = {}", m.getElement(LNCV_MOD_L_ELEMENT_INDEX)); 387 m.setElement(LNCV_MOD_H_ELEMENT_INDEX, ((moduleNum >> 8) & 0x7f)); 388 //log.debug("LNCV MOD_H = {}", m.getElement(LNCV_MOD_H_ELEMENT_INDEX)); 389 m.setElement(LNCV_CMDDATA_ELEMENT_INDEX, (cmdData & 0x7f)); 390 391 //log.debug("LocoNet Message ready, cmd = {}", m.getElement(LNCV_CMD_ELEMENT_INDEX)); 392 return m; 393 } 394 395 /** 396 * Create LNCV message from {@link LncvCommand} enum plus specific parameter values. 397 * 398 * @param source source device (<SRC>) 399 * @param destination destination address (for <DST_L> and <DST_H>) 400 * @param command one of the composite LncvCommand's 401 * @param articleNum manufacturer's hardware class/article code as per specs 402 * @param cvNum 16-bit CV number (for <LNCV_CVN_L> and <LNCV_CVN_H>) 403 * @param moduleNum module address (for <LNCV_MOD_L> and <LNCV_MOD_H>), 404 * same field is used for CV Value in WRITE to and READ_REPLY from Module 405 * @return LocoNet message for the requested instruction 406 */ 407 public static LocoNetMessage createLncvMessage(int source, int destination, LncvCommand command, int articleNum, int cvNum, int moduleNum) { 408 return createLncvMessage(command.getOpc(), source, destination, command.getCmd(), articleNum, cvNum, moduleNum, command.getCmdData()); 409 } 410 411 public int getCmd() { 412 return cmd; 413 } 414 415 public int getCvNum() { 416 if ((cmd == LncvCommand.LNCV_READ.cmd) || 417 (cmd == LncvCommand.LNCV_WRITE.cmd) || 418 (cmd == LncvCommand.LNCV_READ_REPLY.cmd) || 419 (cmd == LncvCommand.LNCV_READ_REPLY2.cmd)) { 420 return cvn; 421 } 422 return -1; 423 } 424 425 public int getCvValue() { 426 if ((cmd == LncvCommand.LNCV_READ_REPLY.cmd) || 427 (cmd == LncvCommand.LNCV_READ_REPLY2.cmd) || 428 (cmd == LncvCommand.LNCV_WRITE.cmd)) { 429 return mod; 430 } 431 return -1; 432 } 433 434 public int getLncvArticleNum() { 435 if ((cmd == LncvCommand.LNCV_READ.cmd) || 436 (cmd == LncvCommand.LNCV_WRITE.cmd) || 437 (cmd == LncvCommand.LNCV_READ_REPLY.cmd) || 438 (cmd == LncvCommand.LNCV_READ_REPLY2.cmd) || 439 (cmd == LncvCommand.LNCV_PROG_START.cmd && art != LNCV_ALL) || 440 (cmd == LncvCommand.LNCV_PROG_END.cmd && art != LNCV_ALL)) { 441 return art; 442 } 443 return -1; 444 } 445 446 public int getLncvModuleNum() { 447 if (cmd == LncvCommand.LNCV_READ.cmd || 448 (cmd == LncvCommand.LNCV_PROG_START.cmd && art != LNCV_ALL)|| 449 (cmd == LncvCommand.LNCV_PROG_END.cmd && art != LNCV_ALL)) { 450 return mod; 451 } 452 return -1; 453 } 454 455 /** 456 * Create LocoNet broadcast message to start LNCV programming. 457 * 458 * @param articleNum LNCV device type number used as filter to respond. Leave this out to 'broadcast' to 459 * all connected devices (which works for discovery purpose only) 460 * @return LocoNet message 461 */ 462 public static LocoNetMessage createAllProgStartRequest(int articleNum) { 463 return createLncvMessage( 464 0x1, 465 0x5, 466 LncvCommand.LNCV_PROG_START, 467 (articleNum > -1 ? articleNum : LNCV_ALL), 468 0x0, 469 LNCV_ALL); 470 } 471 472 /** 473 * Create LocoNet broadcast message to end LNCV programming. 474 * (expect no reply from module) 475 * 476 * @param articleNum LNCV device type number used as filter to respond. Leave out to 'broadcast' to 477 * all connected devices (which works for discovery purpose only). Best to use same 478 * value as used while opening the session. 479 * @return LocoNet message 480 */ 481 public static LocoNetMessage createAllProgEndRequest(int articleNum) { 482 return createLncvMessage( 483 0x1, 484 0x5, 485 LncvCommand.LNCV_PROG_END, 486 (articleNum > -1 ? articleNum : LNCV_ALL), 487 0x0, 488 LNCV_ALL); // replaces 0x1 from KD notes 489 } 490 491 /** 492 * Create LocoNet message for first query of a CV of this module. 493 * 494 * @param articleNum address of the module 495 * @param moduleAddress address of the module 496 * @return LocoNet message 497 */ 498 public static LocoNetMessage createModProgStartRequest(int articleNum, int moduleAddress) { 499 return createLncvMessage( 500 0x1, 501 0x5, 502 LncvCommand.LNCV_PROG_START, 503 articleNum, 504 0x0, 505 moduleAddress); // effectively reads first CV0 = module address 506 } 507 508 /** 509 * Create LocoNet message to leave programming of this module. 510 * (expect no reply from module) 511 * 512 * @param articleNum address of the module 513 * @param moduleAddress address of the module 514 * @return LocoNet message 515 */ 516 public static LocoNetMessage createModProgEndRequest(int articleNum, int moduleAddress) { 517 //log.debug("MODPROG_END {} message created", moduleAddress); 518 return createLncvMessage( 519 0x1, 520 0x5, 521 LncvCommand.LNCV_PROG_END, 522 articleNum, 523 0x0, 524 moduleAddress); 525 } 526 527 /** 528 * Create LocoNet message for a write to a CV of this object. 529 * 530 * @param articleNum address of the module 531 * @param cvNum CV number to query 532 * @param newValue new value to store in CV 533 * @return LocoNet message 534 */ 535 public static LocoNetMessage createCvWriteRequest(int articleNum, int cvNum, int newValue) { 536 return createLncvMessage( 537 0x1, 538 0x5, 539 LncvCommand.LNCV_WRITE, 540 articleNum, 541 cvNum, 542 newValue); 543 } 544 545 /** 546 * Create LocoNet message for a query of a CV of this object. 547 * 548 * @param articleNum address of the module 549 * @param cvNum CV number to query 550 * @param moduleAddress address of the module 551 * @return LocoNet message 552 */ 553 public static LocoNetMessage createCvReadRequest(int articleNum, int moduleAddress, int cvNum) { 554 return createLncvMessage( 555 0x1, 556 0x5, 557 LncvCommand.LNCV_READ, 558 articleNum, 559 cvNum, 560 moduleAddress); 561 } 562 563 /* These 2 static methods are used to mock replies to requests from JMRI */ 564 565 /** 566 * In Hexfile simulation mode, mock a ReadReply message back to the CS (when simulate replies is ON). 567 * 568 * @param m the LocoNet message to respond to 569 * @return LocoNet message containing the reply, or null if preceding 570 * message isn't a query 571 */ 572 public static LocoNetMessage createLncvReadReply(LocoNetMessage m) { 573 if (!isLnMessageASpecificLncvCommand(m, LncvCommand.LNCV_READ)) { 574 return null; 575 } 576 LocoNetMessage reply = new LocoNetMessage(m); 577 reply.setOpCode(LnConstants.OPC_PEER_XFER); 578 reply.setElement(LNCV_LENGTH_ELEMENT_INDEX, LNCV_LENGTH_ELEMENT_VALUE); 579 580 reply.setElement(LNCV_DST_L_ELEMENT_INDEX, (reply.getElement(LNCV_SRC_ELEMENT_INDEX) == LNCV_CS_SRC_VALUE ? 0x49 : reply.getElement(LNCV_SRC_ELEMENT_INDEX))); 581 reply.setElement(LNCV_DST_H_ELEMENT_INDEX, (reply.getElement(LNCV_SRC_ELEMENT_INDEX) == LNCV_CS_SRC_VALUE ? 0x4b : 0x00)); 582 583 // set SRC after reading old value to determine DST above 584 reply.setElement(LNCV_SRC_ELEMENT_INDEX, LNCV_LNMODULE_VALUE); 585 reply.setElement(5, LNCV_CMD_READ_REPLY); 586 // HIBITS handled last 587 reply.setElement(7, reply.getElement(7)); 588 reply.setElement(8, reply.getElement(8)); 589 reply.setElement(9, reply.getElement(9)); 590 reply.setElement(10, reply.getElement(10)); 591 if (reply.getElement(9) != 0 || reply.getElement(10) != 0) { // if CV=0, keep cv value as is, it was passed in as the module address 592 reply.setElement(LNCV_MOD_L_ELEMENT_INDEX, 0x8); // random cv value_low 593 reply.setElement(LNCV_MOD_H_ELEMENT_INDEX, 0x1); // random cv value_hi 594 reply.setElement(PXCT1_ELEMENT_INDEX, reply.getElement(PXCT1_ELEMENT_INDEX)^0x60); // HIBITS recalculate (only elements 11-12 have changed = HIBITS bits 5 & 6) 595 } 596 reply.setElement(13, 0x0); 597 598 return reply; 599 } 600 601 /** 602 * In Hexfile simulation mode, mock a ProgStart reply message back to the CS. 603 * 604 * @param m the LocoNet message to respond to 605 * @return LocoNet message containing the reply, or null if preceding 606 * message isn't a query 607 */ 608 public static LocoNetMessage createLncvProgStartReply(LocoNetMessage m) { 609 if (!isLnMessageASpecificLncvCommand(m, LncvCommand.LNCV_PROG_START)) { 610 return null; 611 } 612 LncvMessageContents lmc = new LncvMessageContents(m); 613 log.debug("request to article {}", lmc.getLncvArticleNum()); 614 LocoNetMessage forward = new LocoNetMessage(m); 615 forward.setElement(LncvMessageContents.LNCV_CMDDATA_ELEMENT_INDEX, 0x00); // correct CMDDATA for ReadRequest (0x80 also observed) 616 forward.setElement(LncvMessageContents.PXCT1_ELEMENT_INDEX, m.getElement(PXCT1_ELEMENT_INDEX)^0x40); // together with this HIBIT 617 if (lmc.getLncvArticleNum() == LNCV_ALL) { // mock a certain device 618 log.debug("art ALL"); 619 forward.setElement(LncvMessageContents.LNCV_ART_L_ELEMENT_INDEX, 0x29); // article number 5033 620 forward.setElement(LncvMessageContents.LNCV_ART_H_ELEMENT_INDEX, 0x13); 621 forward.setElement(LncvMessageContents.PXCT1_ELEMENT_INDEX, 0x01); // hibits to go with 5033 622 } 623 if (lmc.getLncvModuleNum() == LNCV_ALL) { // mock a certain address 624 log.debug("mod ALL"); 625 forward.setElement(LncvMessageContents.LNCV_MOD_L_ELEMENT_INDEX, 0x3); // address value 3 626 forward.setElement(LncvMessageContents.LNCV_MOD_H_ELEMENT_INDEX, 0x0); 627 } 628 return LncvMessageContents.createLncvReadReply(forward); 629 } 630 631 /** 632 * Create LocoNet message to set aseries of Track-Control module display LEDs. 633 * 634 * @param moduleAddress address of the module 635 * @param ledValue CV number to query 636 * @param range2 true if intended for LED2 Command (leds 16-31), fasle for LED1 (0-15) 637 * @return LocoNet message 638 */ 639 public static LocoNetMessage createDirectWriteRequest(int moduleAddress, int ledValue, boolean range2) { 640 return createLncvMessage( 641 LNCV_PC_SRC_VALUE, 642 0x5, 643 (range2 ? LncvCommand.LNCV_DIRECT_LED2 : LncvCommand.LNCV_DIRECT_LED1), 644 6900, 645 moduleAddress, // special: CV position [D3-D4] contains the module address 646 ledValue); 647 } 648 649 /** 650 * LNCV Commands mapped to unique sets of 3 parts in message. LNCV knows only 3 simple <CMD> values. 651 */ 652 public enum LncvCommand { // full commands mapped to 3 values in message, LNCV knows only 3 simple CMD commands 653 LNCV_WRITE (LNCV_CMD_WRITE, LnConstants.OPC_IMM_PACKET, 0x00), // CMD=0x20, CmdData=0x0 654 // LNCV_WRITE_REPLY = LACK 655 LNCV_READ (LNCV_CMD_READ, LnConstants.OPC_IMM_PACKET, 0x00), // CMD=0x21, CmdData=0x0 656 LNCV_READ_REPLY (LNCV_CMD_READ_REPLY, LnConstants.OPC_PEER_XFER, 0x00), // CMD=0x1f, CmdData=0x0 657 LNCV_READ_REPLY2 (LNCV_CMD_READ_REPLY, LnConstants.OPC_PEER_XFER, 0x80), // CMD=0x1f, CmdData=0x0 658 LNCV_PROG_START (LNCV_CMD_READ, LnConstants.OPC_IMM_PACKET, LNCV_DATA_PRON_MASK), // CMD=0x21, CmdData=0x80 659 LNCV_PROG_END (LNCV_CMD_READ, LnConstants.OPC_PEER_XFER, LNCV_DATA_PROFF_MASK), // CMD=0x21, CmdData=0x40 660 LNCV_DIRECT_LED1 (LNCV_CMD_WRITE, LnConstants.OPC_IMM_PACKET, LNCV_DATA_LED1_MASK), // CMD=0x20, CmdData=0xff 661 LNCV_DIRECT_LED2 (LNCV_CMD_WRITE, LnConstants.OPC_IMM_PACKET, LNCV_DATA_LED2_MASK), // CMD=0x20, CmdData=0xfe 662 LNCV_DIRECT_REPLY (LNCV_CMD_READ_REPLY, LnConstants.OPC_PEER_XFER, LNCV_DATA_LED1_MASK); // CMD=0x1f, CmdData=0xff 663 664 private final int cmd; 665 private final int opc; 666 private final int cmddata; 667 668 LncvCommand(int cmd, int opc, int cmddata) { 669 this.cmd = cmd; 670 this.opc = opc; 671 this.cmddata = cmddata; 672 } 673 674 int getCmd() {return cmd;} 675 int getOpc() {return opc;} 676 int getCmdData() {return cmddata;} 677 678 public static int getCmd(LncvCommand mt) { 679 return mt.getCmd(); 680 } 681 682 public Boolean matches(int matchCommand, int matchOpc, int matchData) { 683 //log.debug("CMD ENUM command {}={}? {}", matchCommand, cmd, (matchCommand == cmd)); 684 //log.debug("CMD ENUM opc {}={}? {}", matchOpc, opc, (matchOpc == opc)); 685 //log.debug("CMD ENUM commanddata {}={}? {}", matchData, cmddata, (matchData == cmddata)); 686 return ((matchCommand == cmd) && (matchOpc == opc) && (matchData == cmddata)); 687 } 688 } 689 690 // initialize logging 691 private final static Logger log = LoggerFactory.getLogger(LncvMessageContents.class); 692 693}