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 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Extend jmri.AbstractRailComReporter for CBUS controls. 015 * <hr> 016 * This file is part of JMRI. 017 * <p> 018 * JMRI is free software; you can redistribute it and/or modify it under the 019 * terms of version 2 of the GNU General Public License as published by the Free 020 * Software Foundation. See the "COPYING" file for a copy of this license. 021 * <p> 022 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 024 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 025 * <p> 026 * 027 * CBUS Reporters can accept 028 * 5-byte unique Classic RFID on DDES or ACDAT OPCs, 029 * CANRC522 / CANRCOM DDES OPCs. 030 * 031 * @author Mark Riddoch Copyright (C) 2015 032 * @author Steve Young Copyright (c) 2019, 2020 033 * 034 */ 035public class CbusReporter extends AbstractRailComReporter implements CanListener { 036 037 private final int _number; 038 private final TrafficController tc; // can be removed when former constructor removed 039 private final CanSystemConnectionMemo _memo; 040 041 /** 042 * Should all CbusReporters clear themselves after a timeout? 043 * <p> 044 * Default behavior is to not timeout; this is public access 045 * so it can be updated from a script 046 */ 047 public static boolean eraseOnTimeoutAll = false; 048 049 /** 050 * Should this CbusReporter clear itself after a timeout? 051 * <p> 052 * Default behavior is to not timeout; this is public access 053 * so it can be updated from a script 054 */ 055 public boolean eraseOnTimeoutThisReporter = false; 056 057 /** 058 * Create a new CbusReporter. 059 * 060 * 061 * @param address Reporter address, currently in String number format. No system prefix or type letter. 062 * @param memo System connection. 063 */ 064 public CbusReporter(String address, CanSystemConnectionMemo memo) { // a human-readable Reporter number must be specified! 065 super(memo.getSystemPrefix() + "R" + address); // can't use prefix here, as still in construction 066 _number = Integer.parseInt( address); 067 _memo = memo; 068 // At construction, register for messages 069 tc = memo.getTrafficController(); // can be removed when former constructor removed 070 addTc(memo.getTrafficController()); 071 log.debug("Added new reporter {}R{}", memo.getSystemPrefix(), address); 072 } 073 074 /** 075 * Set the CbusReporter State. 076 * 077 * May also provide / update a CBUS Sensor State, depending on property. 078 * {@inheritDoc} 079 */ 080 @Override 081 public void setState(int s) { 082 super.setState(s); 083 if ( getMaintainSensor() ) { 084 SensorManager sm = _memo.get(SensorManager.class); 085 sm.provide("+"+_number).setCommandedState( s==IdTag.SEEN ? Sensor.ACTIVE : Sensor.INACTIVE ); 086 } 087 } 088 089 /** 090 * {@inheritDoc} 091 * Resets report briefly back to null so Sensor Listeners are updated. 092 */ 093 @Override 094 public void notify(IdTag id){ 095 if ( this.getCurrentReport()!=null && id!=null ){ 096 super.notify(null); // 097 } 098 super.notify(id); 099 } 100 101 /** 102 * {@inheritDoc} 103 * CBUS Reporters can respond to ACDAT or DDES OPC's. 104 */ 105 @Override 106 public void message(CanMessage m) { 107 reply(new CanReply(m)); 108 } 109 110 /** 111 * {@inheritDoc} 112 * CBUS Reporters can respond to ACDAT or DDES OPC's 113 */ 114 @Override 115 public void reply(CanReply m) { 116 if ( m.extendedOrRtr() ) { 117 return; 118 } 119 if ( m.getOpCode() != CbusConstants.CBUS_DDES && m.getOpCode() != CbusConstants.CBUS_ACDAT) { 120 return; 121 } 122 if ((m.getElement(1) << 8) + m.getElement(2) == _number) { // correct reporter number 123 if (m.getOpCode() == CbusConstants.CBUS_DDES && !getCbusReporterType().equals(CbusReporterManager.CBUS_REPORTER_TYPE_CLASSIC) ) { 124 ddesReport(m); 125 } else { 126 classicRFIDReport(m); 127 } 128 } 129 } 130 131 private void ddesReport(CanReply m) { 132 int least_significant_bit = m.getElement(3) & 1; 133 if ( least_significant_bit ==0 ) { 134 canRc522Report(m); 135 } else { 136 canRcomReport(m); 137 } 138 } 139 140 private void classicRFIDReport(CanReply m) { 141 String buf = toClassicTag(m.getElement(3), m.getElement(4), m.getElement(5), m.getElement(6), m.getElement(7)); 142 log.debug("Reporter {} {} RFID tag read of tag: {}", this,getCbusReporterType(),buf); 143 IdTag tag = InstanceManager.getDefault(IdTagManager.class).provideIdTag(buf); 144 notify(tag); 145 startTimeout(tag); 146 } 147 148 // no DCC address correction to allow full 0-65535 range of tags on rolling stock 149 private void canRc522Report(CanReply m){ 150 String tagId = String.valueOf((m.getElement(4)<<8)+ m.getElement(5)); 151 log.debug("Reporter {} RFID tag read of tag: {}",this, tagId); 152 IdTag tag = InstanceManager.getDefault(IdTagManager.class).provideIdTag("ID"+tagId); 153 tag.setProperty("DDES Dat3", m.getElement(6)); 154 tag.setProperty("DDES Dat4", m.getElement(7)); 155 notify(tag); 156 startTimeout(tag); 157 } 158 159 // DCC address correction 0-10239 range 160 private void canRcomReport(CanReply m) { 161 int railcom_id = (m.getElement(3)>>4); 162 log.warn("CANRCOM support still in development."); 163 log.info("{} detected RailCom ID {}",this,railcom_id); 164 } 165 166 private String toClassicTag(int b1, int b2, int b3, int b4, int b5) { 167 return String.format("%02X", b1) + String.format("%02X", b2) + String.format("%02X", b3) 168 + String.format("%02X", b4) + String.format("%02X", b5); 169 } 170 171 /** 172 * Get the Reporter Listener format type. 173 * <p> 174 * Defaults to Classic RfID, 5 byte unique. 175 * @return reporter format type. 176 */ 177 @Nonnull 178 public String getCbusReporterType() { 179 Object returnVal = getProperty(CbusReporterManager.CBUS_REPORTER_DESCRIPTOR_KEY); 180 return (returnVal==null ? CbusReporterManager.CBUS_DEFAULT_REPORTER_TYPE : returnVal.toString()); 181 } 182 183 /** 184 * Get if the Reporter should provide / update a CBUS Sensor, following Reporter Status. 185 * <p> 186 * Defaults to false. 187 * @return true if the reporter should maintain the Sensor. 188 */ 189 public boolean getMaintainSensor() { 190 Boolean returnVal = (Boolean) getProperty(CbusReporterManager.CBUS_MAINTAIN_SENSOR_DESCRIPTOR_KEY); 191 return (returnVal==null ? false : returnVal); 192 } 193 194 // delay can be set to non-null memo when older constructor fully deprecated. 195 private void startTimeout(IdTag tag){ 196 // only timeout when enabled 197 if (! eraseOnTimeoutAll && ! eraseOnTimeoutThisReporter) return; 198 199 int delay = (_memo==null ? 2000 : ((CbusReporterManager)_memo.get(jmri.ReporterManager.class)).getTimeout() ); 200 ThreadingUtil.runOnLayoutDelayed( () -> { 201 if (!disposed && getCurrentReport() == tag) { 202 notify(null); 203 } 204 },delay); 205 } 206 207 private boolean disposed = false; 208 209 /** 210 * {@inheritDoc} 211 */ 212 @Override 213 public void dispose() { 214 disposed = true; 215 tc.removeCanListener(this); 216 super.dispose(); 217 } 218 219 private static final Logger log = LoggerFactory.getLogger(CbusReporter.class); 220}