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