001package jmri.jmrix.bidib;
002
003import jmri.NamedBean;
004import jmri.Sensor;
005import jmri.implementation.AbstractSensor;
006import org.bidib.jbidibc.messages.enums.LcOutputType;
007import org.bidib.jbidibc.messages.enums.OccupationState;
008import org.bidib.jbidibc.messages.utils.NodeUtils;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Extend jmri.AbstractSensor for BiDiB systems
014 *
015 * @author Bob Jacobsen Copyright (C) 2003
016 * @author Eckart Meyer Copyright (C) 2019-2023
017 */
018
019public class BiDiBSensor extends AbstractSensor implements BiDiBNamedBeanInterface {
020
021    private BiDiBAddress addr;
022    private final char typeLetter;
023    private BiDiBTrafficController tc = null;
024    //MessageListener messageListener = null;
025    private BiDiBOutputMessageHandler messageHandler = null;
026
027    // for LC Input Sensors
028    LcOutputType lcType; //cached type from portConfigX or fixed drin type based address
029    
030    /**
031     * Create a Sensor object from system name.
032     *
033     * @param systemName name of added Sensor
034     * @param mgr Sensor Manager, we get the memo object and the type letter (S) from the manager
035     */
036    public BiDiBSensor(String systemName, BiDiBSensorManager mgr) {
037        super(systemName);
038        tc = mgr.getMemo().getBiDiBTrafficController();
039        log.debug("New Sensor: {}", systemName);
040        addr = new BiDiBAddress(systemName, mgr.typeLetter(), mgr.getMemo());
041        log.info("New SENSOR created: {} -> {}", systemName, addr);
042        typeLetter = mgr.typeLetter();
043        
044        createSensorListener();
045        
046        messageHandler.sendQueryConfig();
047    }
048    
049    /**
050     * {@inheritDoc}
051     */
052    @Override
053    public BiDiBAddress getAddr() {
054        return addr;
055    }
056    
057    /**
058     * {@inheritDoc}
059     */
060    @Override
061    public void finishLoad() {
062        messageHandler.sendQuery();
063    }
064    
065    /**
066     * {@inheritDoc}
067     */
068    @Override
069    public void nodeNew() {
070        //create a new BiDiBAddress
071        addr = new BiDiBAddress(getSystemName(), typeLetter, tc.getSystemConnectionMemo());
072        if (addr.isValid()) {
073            log.info("new sensor address created: {} -> {}", getSystemName(), addr);
074            if (addr.isPortAddr()) {
075                messageHandler.sendQueryConfig();
076                messageHandler.waitQueryConfig();
077            }
078            if (!addr.isFeedbackAddr()) {
079                // sensor is not a feedback (BiDiB BM), may be a port input - feedback will be queried in a bulk reqeust from sensor manager
080                messageHandler.sendQuery();
081            }
082        }
083    }
084
085    /**
086     * {@inheritDoc}
087     */
088    @Override
089    public void nodeLost() {
090        setOwnState(NamedBean.UNKNOWN);
091    }
092
093    /**
094     * Request an update on status.
095     */
096    @Override
097    public void requestUpdateFromLayout() {
098        if (addr.isValid()) {
099            log.info("Query sensor status from BiDiB: addr: {}", addr);
100            messageHandler.sendQuery();
101        }
102    }
103    
104    /**
105     * Dispose of the sensor object.
106     * 
107     * Remove the Message Handler for this sensor object
108     */
109    @Override
110    public void dispose() {
111        if (messageHandler != null) {
112            tc.removeMessageListener(messageHandler);        
113            messageHandler = null;
114        }
115        super.dispose();
116    }
117
118    
119    private void createSensorListener() {
120        // create message listener
121        messageHandler = new BiDiBOutputMessageHandler(this, "SENSOR", tc) {
122
123            @Override
124            public void occupation(byte[] address, int messageNum, int detectorNumber, OccupationState occupationState, Integer timestamp) {
125                //log.trace("occupation: node UID: {}, node addr: {}, address: {}, detectorNumber: {}, occ state: {}, timestamp: {}", addr.getNodeUID(), addr.getNodeAddr(), address, detectorNumber, occupationState, timestamp);
126                if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address)  &&  !addr.isPortAddr()  &&  addr.getAddr() == detectorNumber) {
127                    log.info("SENSOR occupation was signalled, state: {}, BM Number: {}, node: {}", occupationState, detectorNumber, addr);
128                    if (occupationState == OccupationState.OCCUPIED) {
129                        setOwnState(Sensor.ACTIVE);
130                    }
131                    else {
132                        setOwnState(Sensor.INACTIVE);
133                    }
134                }
135            }
136            @Override
137            public void occupancyMultiple(byte[] address, int messageNum, int baseAddress, int detectorCount, byte[] detectorData) {
138                //log.trace("occupation: node UID: {}, node addr: {}, address: {}, baseAddress: {}, detectorCount: {}, occ states: {}, timestamp: {}", addr.getNodeUID(), addr.getNodeAddr(), address, baseAddress, detectorCount, ByteUtils.bytesToHex(detectorData));
139                if (NodeUtils.isAddressEqual(addr.getNodeAddr(), address)  &&  !addr.isPortAddr()  &&  addr.getAddr() >= baseAddress  &&  addr.getAddr() < (baseAddress + detectorCount)) {
140                    // 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
141                    // our address is in the data bytes. Check which byte and then check, if the correspondent bit is set.
142                    //log.trace("multiple occupation was signalled, states: {}, BM base Number: {}, BM count: {}, node: {}", ByteUtils.bytesToHex(detectorData), baseAddress, detectorCount, addr);
143                    int relAddr = addr.getAddr() - baseAddress;
144                    byte b = detectorData[ relAddr / 8];
145                    boolean isOccupied = (b & (1 << (relAddr % 8))) != 0;
146                    log.info("SENSOR multi occupation was signalled, state: {}, BM addr: {}, node: {}", isOccupied ? "OCCUPIED" : "FREE", addr.getAddr(), addr);
147                    if (isOccupied) {
148                        setOwnState(Sensor.ACTIVE);
149                    }
150                    else {
151                        setOwnState(Sensor.INACTIVE);
152                    }
153                }
154            }
155            @Override
156            public void newOutputState(int state) {
157                int newState = (state == 0) ? Sensor.INACTIVE : Sensor.ACTIVE;
158                log.debug("SENSOR new state: {}", newState);
159                setOwnState(newState);
160            }
161            @Override
162            public void outputWait(int time) {
163                log.debug("SENSOR wait: {}", time);
164            }
165            @Override
166            public void errorState(int err) {
167                log.warn("SENSOR error: {} addr: {}", err, addr);
168                setOwnState(INCONSISTENT);
169            }
170        };
171        tc.addMessageListener(messageHandler);
172    }
173    
174    // initialize logging
175    private final static Logger log = LoggerFactory.getLogger(BiDiBSensor.class);
176
177}