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}