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 boolean hasExitReports() { 063 return true; 064 } 065 066 /** 067 * {@inheritDoc} 068 */ 069 @Override 070 public BiDiBAddress getAddr() { 071 return addr; 072 } 073 074 /** 075 * {@inheritDoc} 076 */ 077 @Override 078 public void nodeNew() { 079 //create a new BiDiBAddress 080 addr = new BiDiBAddress(getSystemName(), typeLetter, tc.getSystemConnectionMemo()); 081 if (addr.isValid()) { 082 log.info("new reporter address created: {} -> {}", getSystemName(), addr); 083 } 084 } 085 086 /** 087 * {@inheritDoc} 088 */ 089 @Override 090 public void nodeLost() { 091 notify_loco(null); 092 } 093 094 /** 095 * Notify loco address 096 * 097 * @param tag found tag 098 */ 099 public void notify_loco(RailCom tag) { 100 //log.trace("tag: {}", tag); 101 super.notify(tag); 102 } 103 104 private void createReporterListener() { 105 // create message listener for RailCom messages 106 messageListener = new DefaultMessageListener() { 107 @Override 108 public void address(byte[] address, int messageNum, int detectorNumber, List<AddressData> addressData) { 109 //log.trace("address: node UID: {}, node addr: {}, address: {}, detectorNumber: {}, addressData: {}", addr.getNodeUID(), addr.getNodeAddr(), address, detectorNumber, addressData); 110 if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.getAddr() == detectorNumber) { 111 log.info("REPORTER address was signalled, locos: {}, BM Number: {}, node: {}", addressData, detectorNumber, addr); 112 synchronized(entrySet) { 113 entrySet.clear(); 114 if (addressData.size() > 0) { 115 for (AddressData l : addressData) { 116 log.trace("loco addr: {}", l); 117 if (l.getAddress() > 0) { 118 RailCom tag = (RailCom) InstanceManager.getDefault(RailComManager.class).provideIdTag("" + l.getAddress()); 119 //tag.setActualSpeed(msg.getRailComSpeed(i)); 120 entrySet.add(tag); 121 notify_loco(tag); 122 } 123 else { 124 notify_loco(null); 125 } 126 } 127 } 128 else { 129 notify_loco(null); 130 } 131 } 132 } 133 } 134 // occupation free is catched here to report the loco has left - obviusly an "address" message is not sent then 135 @Override 136 public void occupation(byte[] address, int messageNum, int detectorNumber, OccupationState occupationState, Integer timestamp) { 137 //log.trace("occupation: node UID: {}, node addr: {}, address: {}, detectorNumber: {}, occ state: {}, timestamp: {}", addr.getNodeUID(), addr.getNodeAddr(), address, detectorNumber, occupationState, timestamp); 138 if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.getAddr() == detectorNumber) { 139 if (occupationState == OccupationState.FREE) { 140 log.debug("REPORTER occupation free signalled, state: {}, BM Number: {}, node: {}", occupationState, detectorNumber, addr); 141 synchronized(entrySet) { 142 entrySet.clear(); 143 } 144 notify_loco(null); 145 } 146 } 147 } 148 @Override 149 public void occupancyMultiple(byte[] address, int messageNum, int baseAddress, int detectorCount, byte[] detectorData) { 150 log.trace("occupation: node UID: {}, node addr: {}, address: {}, baseAddress: {}, detectorCount: {}, occ states: {}", 151 addr.getNodeUID(), addr.getNodeAddr(), address, baseAddress, 152 detectorCount, detectorData); 153 if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && !addr.isPortAddr() && addr.getAddr() >= baseAddress && addr.getAddr() < (baseAddress + detectorCount)) { 154 // 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 155 // our address is in the data bytes. Check which byte and then check, if the correspondent bit is set. 156 //log.trace("multiple occupation was signalled, states: {}, BM base Number: {}, BM count: {}, node: {}", ByteUtils.bytesToHex(detectorData), baseAddress, detectorCount, addr); 157 int relAddr = addr.getAddr() - baseAddress; 158 byte b = detectorData[ relAddr / 8]; 159 boolean isOccupied = (b & (1 << (relAddr % 8))) != 0; 160 log.debug("REPORTER multi occupation was signalled, state: {}, BM addr: {}, node: {}", isOccupied ? "OCCUPIED" : "FREE", addr.getAddr(), addr); 161 if (!isOccupied) { 162 synchronized(entrySet) { 163 entrySet.clear(); 164 } 165 notify_loco(null); 166 } 167 } 168 } 169 }; 170 tc.addMessageListener(messageListener); 171 } 172 173 // Collecting Reporter Interface methods 174 /** 175 * {@inheritDoc} 176 */ 177 @Override 178 public java.util.Collection<Object> getCollection(){ 179 return entrySet; 180 } 181 182 /** 183 * {@inheritDoc} 184 * 185 * Remove the Message Listener for this reporter 186 */ 187 @Override 188 public void dispose() { 189 if (messageListener != null) { 190 tc.removeMessageListener(messageListener); 191 messageListener = null; 192 } 193 super.dispose(); 194 } 195 196 // initialize logging 197 private static final Logger log = LoggerFactory.getLogger(BiDiBReporter.class); 198 199}