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