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}