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