001package jmri.jmrix.can.cbus;
002
003import jmri.Sensor;
004import jmri.implementation.AbstractSensor;
005import jmri.jmrix.can.CanListener;
006import jmri.jmrix.can.CanMessage;
007import jmri.jmrix.can.CanReply;
008import jmri.jmrix.can.TrafficController;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Extend jmri.AbstractSensor for CBUS controls.
014 *
015 * @author Bob Jacobsen Copyright (C) 2008
016 */
017public class CbusSensor extends AbstractSensor implements CanListener, CbusEventInterface {
018
019    private CbusAddress addrActive;    // go to active state
020    private CbusAddress addrInactive;  // go to inactive state
021
022    /**
023     * Create a new CbusSensor.
024     * @param prefix Hardware connection system prefix, excluding the S for Sensor..
025     * @param address String form of {@link CbusAddress}
026     * @param tc System Traffic Controller.
027     */
028    public CbusSensor(String prefix, String address, TrafficController tc) {
029        super(prefix + "S" + address);
030        this.tc = tc;
031        init(address);
032    }
033    
034    private final TrafficController tc;
035
036    /**
037     * Common initialization for constructors.
038     */
039    private void init(String address) {
040        // build local addresses
041        CbusAddress a = new CbusAddress(address);
042        CbusAddress[] v = a.split();
043        switch (v.length) {
044            case 1:
045                addrActive = v[0];
046                // need to complement here for addr 1
047                // so address _must_ start with address + or -
048                if (address.startsWith("+")) {
049                    addrInactive = new CbusAddress("-" + address.substring(1));
050                } else if (address.startsWith("-")) {
051                    addrInactive = new CbusAddress("+" + address.substring(1));
052                } else {
053                    log.error("can't make 2nd event from systemname {}", address);
054                    return;
055                }
056                break;
057            case 2:
058                addrActive = v[0];
059                addrInactive = v[1];
060                break;
061            default:
062                log.error("Can't parse CbusSensor system name: {}", address);
063                return;
064        }
065        // connect
066        addTc(tc);
067    }
068
069    /**
070     * Request an update on status by sending CBUS request message to active address.
071     * Sends a query message using the active Sensor address.
072     * e.g. for a CBUS address "-7;+5", the query will go to event 7.
073     * {@inheritDoc}
074     */
075    @Override
076    public void requestUpdateFromLayout() {
077        CanMessage m = addrActive.makeMessage(tc.getCanid());
078        int opc = CbusMessage.getOpcode(m);
079        if (CbusOpCodes.isShortEvent(opc)) {
080            m.setOpCode(CbusConstants.CBUS_ASRQ);
081        }
082        else {
083            m.setOpCode(CbusConstants.CBUS_AREQ);
084        }
085        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
086        tc.sendCanMessage(m, this);
087    }
088
089    /**
090     * User request to set the state.
091     * We broadcast that to all listeners by putting it out on CBUS. 
092     * In turn, the code in this class
093     * should use setOwnState to handle internal sets and bean notifies.
094     * Unknown / Inconsistent states do not send a message to CBUS,
095     * but do update sensor state.
096     * {@inheritDoc}
097     */
098    @Override
099    public void setKnownState(int s) throws jmri.JmriException {
100        setOwnState(s);
101        CanMessage m;
102        switch (s) {
103            case Sensor.ACTIVE:
104                m = ( getInverted() ? addrInactive.makeMessage(tc.getCanid()) : 
105                    addrActive.makeMessage(tc.getCanid()));
106                break;
107            case Sensor.INACTIVE:
108                m = ( !getInverted() ? addrInactive.makeMessage(tc.getCanid()) : 
109                    addrActive.makeMessage(tc.getCanid()));
110                break;
111            default:
112                return;
113        }
114        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
115        tc.sendCanMessage(m, this);
116    }
117    
118    /**
119     * Returns true, can invert.
120     * {@inheritDoc}
121     */
122    @Override
123    public boolean canInvert() {
124        return true;
125    }    
126    
127    /**
128     * Package method returning CanMessage for the Active Sensor Address
129     * @return CanMessage with the Active CBUS Address
130     */    
131    public CanMessage getAddrActive(){
132        CanMessage m;
133        if (getInverted()){
134            m = addrInactive.makeMessage(tc.getCanid());              
135        } else {
136            m = addrActive.makeMessage(tc.getCanid());
137        }
138        return m;
139    }
140    
141    /**
142     * Package method returning CanMessage for the Inactive Sensor Address
143     * @return CanMessage with the InActive CBUS Address
144     */    
145    public CanMessage getAddrInactive(){
146        CanMessage m;
147        if (getInverted()){
148            m = addrActive.makeMessage(tc.getCanid());              
149        } else {
150            m = addrInactive.makeMessage(tc.getCanid());
151        }
152        return m;
153    }
154    
155    /**
156     * {@inheritDoc}
157     */
158    @Override
159    public CanMessage getBeanOnMessage(){
160        return checkEvent(getAddrActive());
161    }
162
163    /**
164     * {@inheritDoc}
165     */
166    @Override
167    public CanMessage getBeanOffMessage(){
168        return checkEvent(getAddrInactive());
169    }
170    
171    /**
172     * Track layout status from messages being sent to CAN
173     * {@inheritDoc}
174     */
175    @Override
176    public void message(CanMessage f) {
177        if ( f.extendedOrRtr() ) {
178            return;
179        }
180        if (addrActive.match(f)) {
181            setOwnState(!getInverted() ? Sensor.ACTIVE : Sensor.INACTIVE);
182        } else if (addrInactive.match(f)) {
183            setOwnState(!getInverted() ? Sensor.INACTIVE : Sensor.ACTIVE);
184        }
185    }
186
187    /**
188     * Event status from messages being received from CAN
189     * {@inheritDoc}
190     */
191    @Override
192    public void reply(CanReply f) {
193        if ( f.extendedOrRtr() ) {
194            return;
195        }
196        // convert response events to normal
197        CanReply opcf = CbusMessage.opcRangeToStl(f);
198        if (addrActive.match(opcf)) {
199            setOwnState(!getInverted() ? Sensor.ACTIVE : Sensor.INACTIVE);
200        } else if (addrInactive.match(opcf)) {
201            setOwnState(!getInverted() ? Sensor.INACTIVE : Sensor.ACTIVE);
202        }
203    }
204    
205    /**
206     * {@inheritDoc}
207     */
208    @Override
209    public void dispose() {
210        tc.removeCanListener(this);
211        super.dispose();
212    }
213
214    private final static Logger log = LoggerFactory.getLogger(CbusSensor.class);
215
216}