001package jmri.jmrix.can.cbus; 002 003import javax.annotation.Nonnull; 004 005import jmri.*; 006import jmri.implementation.AbstractRailComReporter; 007import jmri.jmrix.can.*; 008import jmri.util.ThreadingUtil; 009 010/** 011 * Extend jmri.AbstractRailComReporter for CBUS controls. 012 * <hr> 013 * This file is part of JMRI. 014 * <p> 015 * JMRI is free software; you can redistribute it and/or modify it under the 016 * terms of version 2 of the GNU General Public License as published by the Free 017 * Software Foundation. See the "COPYING" file for a copy of this license. 018 * <p> 019 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 020 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 021 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 022 * <p> 023 * 024 * CBUS Reporters can accept 025 * 5-byte unique Classic RFID on DDES or ACDAT OPCs, 026 * CANRC522 / CANRCOM DDES OPCs. 027 * 028 * @author Mark Riddoch Copyright (C) 2015 029 * @author Steve Young Copyright (c) 2019, 2020 030 * 031 */ 032public class CbusReporter extends AbstractRailComReporter implements CanListener { 033 034 private final int _number; 035 private final CanSystemConnectionMemo _memo; 036 037 static private final RailComManager railComManager = InstanceManager.getDefault(RailComManager.class); 038 039 /** 040 * Should all CbusReporters clear themselves after a timeout? 041 * <p> 042 * Default behavior is to not timeout; this is public access 043 * so it can be updated from a script 044 */ 045 public static boolean eraseOnTimeoutAll = false; 046 047 /** 048 * Should this CbusReporter clear itself after a timeout? 049 * <p> 050 * Default behavior is to not timeout; this is public access 051 * so it can be updated from a script 052 */ 053 public boolean eraseOnTimeoutThisReporter = false; 054 055 /** 056 * Create a new CbusReporter. 057 * 058 * 059 * @param address Reporter address, currently in String number format. No system prefix or type letter. 060 * @param memo System connection. 061 */ 062 public CbusReporter(String address, CanSystemConnectionMemo memo) { // a human-readable Reporter number must be specified! 063 super(memo.getSystemPrefix() + "R" + address); // can't use prefix here, as still in construction 064 _number = Integer.parseInt( address); 065 _memo = memo; 066 // At construction, don't register for messages; they're sent via the CbusReporterManager 067 // tc = memo.getTrafficController(); // can be removed when former constructor removed 068 // addTc(memo.getTrafficController()); 069 log.debug("Added new reporter {}R{}", memo.getSystemPrefix(), address); 070 } 071 072 /** 073 * Set the CbusReporter State. 074 * 075 * May also provide / update a CBUS Sensor State, depending on property. 076 * {@inheritDoc} 077 */ 078 @Override 079 public void setState(int s) { 080 super.setState(s); 081 if ( getMaintainSensor() ) { 082 SensorManager sm = _memo.get(SensorManager.class); 083 sm.provide("+"+_number).setCommandedState( s==IdTag.SEEN ? Sensor.ACTIVE : Sensor.INACTIVE ); 084 } 085 } 086 087 /** 088 * {@inheritDoc} 089 * Resets report briefly back to null so Sensor Listeners are updated. 090 */ 091 @Override 092 public void notify(IdTag id){ 093 if ( this.getCurrentReport()!=null && id!=null ){ 094 super.notify(null); // 095 } 096 super.notify(id); 097 } 098 099 /** 100 * {@inheritDoc} 101 * CBUS Reporters can respond to ACDAT or DDES OPC's. 102 */ 103 @Override 104 public void message(CanMessage m) { 105 reply(new CanReply(m)); 106 } 107 108 /** 109 * {@inheritDoc} 110 * CBUS Reporters can respond to ACDAT or DDES OPC's 111 */ 112 @Override 113 public void reply(CanReply m) { 114 if ( m.extendedOrRtr() ) { 115 return; 116 } 117 if ( m.getOpCode() != CbusConstants.CBUS_DDES && m.getOpCode() != CbusConstants.CBUS_ACDAT) { 118 return; 119 } 120 121 if (((m.getElement(1) << 8) + m.getElement(2)) == _number) { // correct reporter number, for us 122 if (m.getOpCode() == CbusConstants.CBUS_DDES && !getCbusReporterType().equals(CbusReporterManager.CBUS_REPORTER_TYPE_CLASSIC) ) { 123 ddesReport(m); 124 } else { 125 classicRFIDReport(m); 126 } 127 } 128 } 129 130 private void ddesReport(CanReply m) { 131 int least_significant_bit = m.getElement(3) & 1; 132 if ( least_significant_bit == 0 ) { 133 canRc522Report(m); 134 } else { 135 canRcomReport(m); 136 } 137 } 138 139 private void classicRFIDReport(CanReply m) { 140 String buf = toClassicTag(m.getElement(3), m.getElement(4), m.getElement(5), m.getElement(6), m.getElement(7)); 141 log.debug("Reporter {} {} RFID tag read of tag: {}", this,getCbusReporterType(),buf); 142 IdTag tag = InstanceManager.getDefault(IdTagManager.class).provideIdTag(buf); 143 notify(tag); 144 startTimeout(tag); 145 } 146 147 // no DCC address correction to allow full 0-65535 range of tags on rolling stock 148 private void canRc522Report(CanReply m){ 149 String tagId = String.valueOf((m.getElement(4)<<8)+ m.getElement(5)); 150 log.debug("Reporter {} RFID tag read of tag: {}",this, tagId); 151 IdTag tag = InstanceManager.getDefault(IdTagManager.class).provideIdTag("ID"+tagId); 152 tag.setProperty("DDES Dat3", m.getElement(6)); 153 tag.setProperty("DDES Dat4", m.getElement(7)); 154 notify(tag); 155 startTimeout(tag); 156 } 157 158 159 160 private void canRcomReport(CanReply m) { 161 162 var locoAddress = parseAddress(m); 163 164 int speed = m.getElement(6)&0x7F; 165 if ((m.getElement(6)&0x80) == 0) { 166 speed = -1; // data unavailable 167 } 168 169 int flags = m.getElement(7); 170 171 RailCom.Orientation orientation; 172 switch (flags&0x03) { 173 case 2: 174 orientation = RailCom.Orientation.EAST; 175 break; 176 case 1: 177 orientation = RailCom.Orientation.WEST; 178 break; 179 case 0: 180 orientation = RailCom.Orientation.UNKNOWN; 181 break; 182 default: 183 log.warn("Unexpected orientation code 3"); 184 orientation = RailCom.Orientation.UNKNOWN; 185 break; 186 } 187 188 RailCom.Direction direction; 189 switch ((flags>>2)&0x03) { 190 case 1: 191 direction = RailCom.Direction.FORWARD; 192 break; 193 case 2: 194 direction = RailCom.Direction.BACKWARD; 195 break; 196 case 0: 197 direction = RailCom.Direction.UNKNOWN; 198 break; 199 default: 200 log.warn("Unexpected direction code 3"); 201 direction = RailCom.Direction.UNKNOWN; 202 break; 203 } 204 205 RailCom.Motion motion; 206 switch ((flags>>4)&0x03) { 207 case 1: 208 motion = RailCom.Motion.STATIONARY; 209 log.debug("Setting speed to zero because known to be not moving"); 210 speed = 0; 211 break; 212 case 2: 213 motion = RailCom.Motion.MOVING; 214 break; 215 case 0: 216 motion = RailCom.Motion.UNKNOWN; 217 break; 218 default: 219 log.warn("Unexpected motion code 3"); 220 motion = RailCom.Motion.UNKNOWN; 221 break; 222 } 223 224 RailCom.QoS qos; 225 switch ((flags>>6)&0x03) { 226 case 1: 227 qos = RailCom.QoS.POOR; 228 break; 229 case 2: 230 qos = RailCom.QoS.GOOD; 231 break; 232 case 0: 233 qos = RailCom.QoS.UNKNOWN; 234 break; 235 default: 236 log.warn("Unexpected QoS code 3"); 237 qos = RailCom.QoS.UNKNOWN; 238 break; 239 } 240 241 var idTag = railComManager.provideIdTag(""+locoAddress.getNumber()); 242 var tag = (RailCom)idTag; 243 244 tag.setDccAddress(locoAddress); 245 tag.setActualSpeed(speed); 246 tag.setOrientation(orientation); 247 tag.setDirection(direction); 248 tag.setMotion(motion); 249 tag.setQoS(qos); 250 251 notify(tag); 252 startTimeout(tag); 253 } 254 255 DccLocoAddress parseAddress(CanReply m) { // package access for testing 256 int dccTypeInt = m.getElement(4)&0xC0; 257 int dccNumber; 258 int b4 = m.getElement(4) & 0x3F; // excludes high "type" bits 259 int b5 = m.getElement(5); 260 LocoAddress.Protocol dccType; 261 boolean isConsist; 262 switch (dccTypeInt) { 263 default: 264 case 0xC0: 265 dccNumber = (b4<<8) | b5; 266 dccType = LocoAddress.Protocol.DCC_LONG; 267 isConsist = false; 268 break; 269 case 0x00: 270 dccNumber = b5; 271 dccType = LocoAddress.Protocol.DCC_SHORT; 272 isConsist = false; 273 break; 274 case 0x40: 275 dccNumber = b5&0x7F; // remove direction bit 276 dccType = LocoAddress.Protocol.DCC_SHORT; 277 isConsist = true; 278 break; 279 case 0x80: 280 int decUpper = (b4 << 1) | ((b5>>7)&0x01); // BCD upper value 281 dccNumber = decUpper*100 + (b5&0x7F); 282 dccType = LocoAddress.Protocol.DCC_LONG; 283 isConsist = true; 284 break; 285 } 286 return new DccLocoAddress(dccNumber, dccType, isConsist); 287 } 288 289 private String toClassicTag(int b1, int b2, int b3, int b4, int b5) { 290 return String.format("%02X", b1) + String.format("%02X", b2) + String.format("%02X", b3) 291 + String.format("%02X", b4) + String.format("%02X", b5); 292 } 293 294 /** 295 * Get the Reporter Listener format type. 296 * <p> 297 * Defaults to Classic RfID, 5 byte unique. 298 * @return reporter format type. 299 */ 300 @Nonnull 301 public String getCbusReporterType() { 302 Object returnVal = getProperty(CbusReporterManager.CBUS_REPORTER_DESCRIPTOR_KEY); 303 return (returnVal==null ? CbusReporterManager.CBUS_DEFAULT_REPORTER_TYPE : returnVal.toString()); 304 } 305 306 /** 307 * Get if the Reporter should provide / update a CBUS Sensor, following Reporter Status. 308 * <p> 309 * Defaults to false. 310 * @return true if the reporter should maintain the Sensor. 311 */ 312 public boolean getMaintainSensor() { 313 Boolean returnVal = (Boolean) getProperty(CbusReporterManager.CBUS_MAINTAIN_SENSOR_DESCRIPTOR_KEY); 314 return (returnVal==null ? false : returnVal); 315 } 316 317 // delay can be set to non-null memo when older constructor fully deprecated. 318 private void startTimeout(IdTag tag){ 319 // only timeout when enabled 320 if (! eraseOnTimeoutAll && ! eraseOnTimeoutThisReporter) return; 321 322 int delay = (_memo==null ? 2000 : ((CbusReporterManager)_memo.get(jmri.ReporterManager.class)).getTimeout() ); 323 ThreadingUtil.runOnLayoutDelayed( () -> { 324 if (!disposed && getCurrentReport() == tag) { 325 notify(null); 326 } 327 },delay); 328 } 329 330 private boolean disposed = false; 331 332 /** 333 * {@inheritDoc} 334 */ 335 @Override 336 public void dispose() { 337 disposed = true; 338 super.dispose(); 339 } 340 341 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusReporter.class); 342}