001package jmri.jmrix.bidib;
002
003import java.util.List;
004import jmri.InstanceManager;
005import jmri.RailCom;
006import jmri.RailComManager;
007
008import org.bidib.jbidibc.messages.AddressData;
009import org.bidib.jbidibc.core.DefaultMessageListener;
010import org.bidib.jbidibc.core.MessageListener;
011import org.bidib.jbidibc.messages.enums.OccupationState;
012import org.bidib.jbidibc.messages.utils.NodeUtils;
013
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017/**
018 * This class implements the Reporter Manager interface
019 * for BiDiB railcom feedback.
020 * <p>
021 * Reports from this reporter are of the type jmri.RailCom.
022 *
023 * @author Paul Bender Copyright (C) 2016
024 * @author Eckart Meyer Copyright (C) 2019-2023
025 * 
026 * based on jmri.jmrix.z21.Z21reporter
027 */
028public class BiDiBReporter extends jmri.implementation.AbstractRailComReporter implements BiDiBNamedBeanInterface {
029
030    BiDiBAddress addr;
031    private final char typeLetter;
032    private BiDiBTrafficController tc = null;
033    MessageListener messageListener = null;
034    
035    /**  
036     * Create a reporter instance. 
037     * 
038     * @param systemName name to be created
039     * @param mgr Reporter Manager, we get the memo object and the type letter (R) from the manager
040     */
041    public BiDiBReporter(String systemName, BiDiBReporterManager mgr) {
042        super(systemName);
043        tc = mgr.getMemo().getBiDiBTrafficController();
044        log.debug("New Reporter: {}", systemName);
045        addr = new BiDiBAddress(systemName, mgr.typeLetter(), mgr.getMemo());
046        log.info("New REPORTER created: {} -> {}", systemName, addr);
047        typeLetter = mgr.typeLetter();
048        
049        createReporterListener();
050    }
051
052    /**
053     * {@inheritDoc}
054     */
055    @Override
056    public BiDiBAddress getAddr() {
057        return addr;
058    }
059    
060    /**
061     * {@inheritDoc}
062     */
063    @Override
064    public void nodeNew() {
065        //create a new BiDiBAddress
066        addr = new BiDiBAddress(getSystemName(), typeLetter, tc.getSystemConnectionMemo());
067        if (addr.isValid()) {
068            log.info("new reporter address created: {} -> {}", getSystemName(), addr);
069        }
070    }
071
072    /**
073     * {@inheritDoc}
074     */
075    @Override
076    public void nodeLost() {
077        notify_loco(null);
078    }
079
080    /**
081     * Notify loco address
082     * 
083     * @param tag found tag
084     */
085    public void notify_loco(RailCom tag){
086        //log.trace("tag: {}", tag);
087        super.notify(tag);
088    }
089
090    private void createReporterListener() {
091        // create message listener for RailCom messages
092        messageListener = new DefaultMessageListener() {
093            @Override
094            public void address(byte[] address, int messageNum, int detectorNumber, List<AddressData> addressData) {
095                log.trace("address: node UID: {}, node addr: {}, address: {}, detectorNumber: {}, addressData: {}", addr.getNodeUID(), addr.getNodeAddr(), address, detectorNumber, addressData);
096                if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address)  &&  addr.getAddr() == detectorNumber) {
097                    log.info("REPORTER address was signalled, locos: {}, BM Number: {}, node: {}", addressData, detectorNumber, addr);
098                    if (addressData.size() > 0) {
099                        for (AddressData l : addressData) {
100                            log.debug("loco addr: {}", l);
101                            if (l.getAddress() > 0) {
102                                RailCom tag = (RailCom) InstanceManager.getDefault(RailComManager.class).provideIdTag("" + l.getAddress());
103                                //tag.setActualSpeed(msg.getRailComSpeed(i));                        
104                                notify_loco(tag);
105                            }
106                            else {
107                                notify_loco(null);
108                            }
109                        }
110                    }
111                    else {
112                        notify_loco(null);
113                    }
114                }
115            }
116            // occupation free is catched here to report the loco has left - obviusly an "address" message is not sent then
117            @Override
118            public void occupation(byte[] address, int messageNum, int detectorNumber, OccupationState occupationState, Integer timestamp) {
119                //log.trace("occupation: node UID: {}, node addr: {}, address: {}, detectorNumber: {}, occ state: {}, timestamp: {}", addr.getNodeUID(), addr.getNodeAddr(), address, detectorNumber, occupationState, timestamp);
120                if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address)  &&  addr.getAddr() == detectorNumber) {
121                    if (occupationState == OccupationState.FREE) {
122                        log.info("REPORTER occupation free signalled, state: {}, BM Number: {}, node: {}", occupationState, detectorNumber, addr);
123                        notify_loco(null);
124                    }
125                }
126            }
127            @Override
128            public void occupancyMultiple(byte[] address, int messageNum, int baseAddress, int detectorCount, byte[] detectorData) {
129            log.trace("occupation: node UID: {}, node addr: {}, address: {}, baseAddress: {}, detectorCount: {}, occ states: {}", 
130                        addr.getNodeUID(), addr.getNodeAddr(), address, baseAddress, 
131                        detectorCount, detectorData);
132                if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address)  &&  !addr.isPortAddr()  &&  addr.getAddr() >= baseAddress  &&  addr.getAddr() < (baseAddress + detectorCount)) {
133                    // TODO: This is very inefficent, since this function is called for each sensor! We should place the listener at a more central instance like the sensor manager
134                    // our address is in the data bytes. Check which byte and then check, if the correspondent bit is set.
135                    //log.trace("multiple occupation was signalled, states: {}, BM base Number: {}, BM count: {}, node: {}", ByteUtils.bytesToHex(detectorData), baseAddress, detectorCount, addr);
136                    int relAddr = addr.getAddr() - baseAddress;
137                    byte b = detectorData[ relAddr / 8];
138                    boolean isOccupied = (b & (1 << (relAddr % 8))) != 0;
139                    log.info("REPORTER multi occupation was signalled, state: {}, BM addr: {}, node: {}", isOccupied ? "OCCUPIED" : "FREE", addr.getAddr(), addr);
140                    if (!isOccupied) {
141                        notify_loco(null);
142                    }
143                }
144            }
145        };
146        tc.addMessageListener(messageListener);        
147    }
148    
149    /**
150     * {@inheritDoc}
151     * 
152     * Remove the Message Listener for this reporter
153     */
154    @Override
155    public void dispose() {
156        if (messageListener != null) {
157            tc.removeMessageListener(messageListener);        
158            messageListener = null;
159        }
160        super.dispose();
161    }
162
163    // initialize logging
164    private final static Logger log = LoggerFactory.getLogger(BiDiBReporter.class);
165
166}