001package jmri.jmrix.lenz;
002
003import jmri.implementation.AbstractLight;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007/**
008 * Implementation of the Light Object for XpressNet.
009 * <p>
010 * NOTE: This is a simplification of the XNetTurnout class.
011 * <p>
012 * Based in part on SerialLight.java
013 *
014 * @author Paul Bender Copyright (C) 2008-2010
015 */
016public class XNetLight extends AbstractLight implements XNetListener {
017
018    private XNetTrafficController tc;
019    private XNetLightManager lm;
020
021    /**
022     * Create a Light object, with only system name.
023     * <p>
024     * 'systemName' was previously validated in LnLightManager
025     *
026     * @param tc         the traffic controller for the connection
027     * @param lm         the managing LightManager for this Light
028     * @param systemName the system name for this Light
029     */
030    public XNetLight(XNetTrafficController tc, XNetLightManager lm, String systemName) {
031        super(systemName);
032        this.tc = tc;
033        this.lm = lm;
034        // Initialize the Light
035        initializeLight(systemName);
036    }
037
038    /**
039     * Create a Light object, with both system and user names.
040     * <p>
041     * 'systemName' was previously validated in XNetLightManager
042     *
043     * @param tc         the traffic controller for the connection
044     * @param lm         the managing LightManager for this Light
045     * @param systemName the system name for this Light
046     * @param userName   the user name for this Light
047     */
048    public XNetLight(XNetTrafficController tc, XNetLightManager lm, String systemName, String userName) {
049        super(systemName, userName);
050        this.tc = tc;
051        this.lm = lm;
052        // Initialize the Light
053        initializeLight(systemName);
054    }
055
056    /**
057     * Dispose of the light object.
058     */
059    @Override
060    public void dispose() {
061        tc.removeXNetListener(XNetInterface.FEEDBACK | XNetInterface.COMMINFO | XNetInterface.CS_INFO, this);
062        super.dispose();
063    }
064
065    /**
066     * Initialize the light object's parameters.
067     */
068    private synchronized void initializeLight(String systemName) {
069        // Extract the Bit from the name
070        mAddress = lm.getBitFromSystemName(systemName);
071        // Set initial state
072        setState(OFF);
073        // At construction, register for messages
074        tc.addXNetListener(XNetInterface.FEEDBACK | XNetInterface.COMMINFO | XNetInterface.CS_INFO, this);
075    }
076
077    /*
078     * Set up system dependent instance variables and set system independent
079     * instance variables to default values.
080     * Note: most instance variables are in AbstractLight.java
081     */
082
083    /**
084     * System dependent instance variables
085     */
086    int mAddress = 0;            // accessory output address
087
088    /**
089     * Internal State Machine states.
090     */
091    static final int OFFSENT = 1;
092    static final int COMMANDSENT = 2;
093    static final int IDLE = 0;
094    private int internalState = IDLE;
095
096    /**
097     * Set the current state of this Light. This routine requests the hardware
098     * to change.
099     */
100    @Override
101    synchronized public void setState(int newState) {
102        if (newState != ON && newState != OFF) {
103            // Unsuported state
104            log.warn("Unsupported state {} requested for light {}", newState, mSystemName);
105            return;
106        }
107
108        // get the right packet
109        XNetMessage msg = XNetMessage.getTurnoutCommandMsg(mAddress,
110                newState == ON,
111                newState == OFF,
112                true);
113        internalState = COMMANDSENT;
114        tc.sendXNetMessage(msg, this);
115
116        if (newState != mState) {
117            int oldState = mState;
118            mState = newState;
119            // notify listeners, if any
120            firePropertyChange("KnownState", oldState, newState);
121        }
122        sendOffMessage();
123    }
124
125    /**
126     * Handle an incoming message from the XpressNet. NOTE: We aren't registered
127     * as a listener, so this is only triggered when we send out a message.
128     *
129     * @param l the message to handle
130     */
131    @Override
132    synchronized public void message(XNetReply l) {
133        if (log.isDebugEnabled()) {
134            log.debug("received message: {}", l);
135        }
136        if (internalState == OFFSENT) {
137            // If an OFF was sent, we want to check for Communications
138            // errors before we try to do anything else.
139            if (l.isCommErrorMessage()) {
140                /* this is a communications error */
141                log.error("Communications error occurred - message received was: {}", l);
142                sendOffMessage();
143            } else if (l.isCSBusyMessage()) {
144                /* this is a communications error */
145                log.error("Command station busy - message received was: {}", l);
146                sendOffMessage();
147            } else if (l.isOkMessage()) {
148                /* the command was successfully received */
149                internalState = IDLE;
150            } else if (internalState == COMMANDSENT) {
151                // If command was sent,, we want to check for Communications
152                // errors before we try to do anything else.
153                if (l.isCommErrorMessage()) {
154                    /* this is a communications error */
155                    log.error("Communications error occurred - message received was: {}", l);
156                    setState(mState);
157                } else if (l.isCSBusyMessage()) {
158                    /* this is a communications error */
159                    log.error("Command station busy - message received was: {}", l);
160                    setState(mState);
161                } else if (l.isOkMessage()) {
162                    /* the command was successfully received */
163                    sendOffMessage();
164                }
165            }
166        }
167    }
168
169    /**
170     * Listen for the messages to the LI100/LI101.
171     *
172     * @param l the expected message
173     */
174    @Override
175    public void message(XNetMessage l) {
176    }
177
178    // Handle a timeout notification
179    @Override
180    public void notifyTimeout(XNetMessage msg) {
181        if (log.isDebugEnabled()) {
182            log.debug("Notified of timeout on message{}", msg.toString());
183        }
184    }
185
186    /**
187     * Send an "Off" message to the decoder for this output.
188     */
189    private synchronized void sendOffMessage() {
190        // We need to tell the turnout to shut off the output.
191        if (log.isDebugEnabled()) {
192            log.debug("Sending off message for light {} commanded state= {}", mAddress, mState);
193        }
194        XNetMessage msg = XNetMessage.getTurnoutCommandMsg(mAddress,
195                mState == ON,
196                mState == OFF,
197                false);
198        tc.sendXNetMessage(msg, this);
199
200        // Set the known state to the commanded state.
201        internalState = OFFSENT;
202    }
203
204    private static final Logger log = LoggerFactory.getLogger(XNetLight.class);
205
206}