001package jmri.jmrix.powerline.dmx512;
002
003import jmri.jmrix.powerline.SerialSystemConnectionMemo;
004import jmri.jmrix.powerline.SerialTrafficController;
005
006import java.io.IOException;
007
008import jmri.jmrix.AbstractSerialPortController.SerialPort;
009
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013/**
014 * Converts Stream-based I/O to/from messages. The "SerialInterface" side
015 * sends/receives message objects.
016 * <p>
017 * The connection to a SerialPortController is via a pair of *Streams, which
018 * then carry sequences of characters for transmission. Note that this
019 * processing is handled in an independent thread.
020 *
021 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2005, 2006, 2008 Converted to
022 * multiple connection
023 * @author Ken Cameron Copyright (C) 2023
024 */
025public class SpecificTrafficController extends SerialTrafficController {
026
027    public SpecificTrafficController(SerialSystemConnectionMemo memo) {
028        super();
029        this.memo = memo;
030        logDebug = log.isDebugEnabled();
031
032        // not polled at all, so allow unexpected messages, and
033        // use poll delay just to spread out startup
034        setAllowUnexpectedReply(true);
035        mWaitBeforePoll = 1000;  // can take a long time to send
036    }
037
038    private boolean oneTimeLog = true;
039    public byte[] dmxArray = new byte[513];
040    private int intensitySteps = 255;
041    private SerialPort activePort = null;
042
043    /**
044     * set value in dmxArray
045     * @param unitId offset in dmxArray
046     * @param intensityValue value to put in dmxArray
047     * @return true when values ok
048     */
049    public boolean setDmxIntensity(int unitId, byte intensityValue) {
050        if ((unitId > 0) && (unitId <= 512)) {
051            dmxArray[unitId] = intensityValue;
052            return(true);
053        }
054        return(false);
055    }
056
057    @Override
058    protected void transmitLoop() {
059        if (oneTimeLog) {
060            oneTimeLog = false;
061            for (int i = 0; i < dmxArray.length; i++) {
062                dmxArray[i] = 0;
063            }
064            dmxArray[0] = (byte) 0; // type of buffer going out
065            /**
066             * deal with thread sync of main tc still getting setup by time of
067             * first call in the transmit loop. Give it time and retry
068             */
069            int tryLimit = 0;
070            while ((activePort == null) && (tryLimit <= 10)) {
071                try {
072                    Thread.sleep(5000);
073                } catch (InterruptedException ignore) {
074                    Thread.currentThread().interrupt();
075                }
076                tryLimit++;
077                activePort = memo.getActiveSerialPort();
078                if (activePort == null) {
079                    log.info("try {} to get activePort", tryLimit);
080                }
081            }
082        }
083
084        // loop forever
085        while (!connectionError && !threadStopRequest) {
086            try {
087                if (ostream != null) {
088                    // break should be for at least 176 uSec
089                    if (activePort != null) {
090                        //log.info("Start Break");
091                        activePort.setBreak();
092                        try {
093                            Thread.sleep(10);
094                        } catch (InterruptedException e) {
095                            log.warn("transmitLoop did not expected to be interrupted during break");
096                            break;
097                        }
098                        activePort.clearBreak();
099                        //log.info("Break Sent");
100                        // wait at least 8 usec (not msec, usec)
101                        try {
102                            Thread.sleep(1);
103                        } catch (InterruptedException e) {
104                            log.warn("transmitLoop did not expected to be interrupted during clear");
105                            break;
106                        }
107                    }
108                    /**
109                     * send the buffer of data
110                     */
111                    try {
112                        ostream.write(dmxArray);
113                    } catch (com.fazecast.jSerialComm.SerialPortTimeoutException ex) {
114                        if (!threadStopRequest) {
115                            log.warn("DMX512 write operation ended early");
116                        }
117                        return;
118                    }
119                    /**
120                     * wait 25 mSec, then repeat
121                     */
122                    try {
123                        Thread.sleep(25);
124                    } catch (InterruptedException ignore) {
125                        Thread.currentThread().interrupt();
126                    }
127                } else {
128                    connectionError = true;
129                }
130            } catch (IOException | RuntimeException e) {
131                // TODO Currently there's no port recovery if an exception occurs
132                // must restart JMRI to clear xmtException.
133                xmtException = true;
134                portWarn(e);
135            }
136        }
137    }
138
139    // not used, no reback
140    @Override
141    public void receiveLoop() {
142        try {
143            Thread.sleep(10000);
144        } catch (InterruptedException ignore) {
145            Thread.currentThread().interrupt();
146        }
147    }
148
149    /**
150     * This system provides 256 dim steps
151     */
152    @Override
153    public int getNumberOfIntensitySteps() {
154        return intensitySteps;
155    }
156
157    /**
158     * Send a sequence of Dmx messages
159     * <p>
160     * Makes call to update array
161     */
162    @Override
163    public boolean sendDmxSequence(int unitid, byte newStep) {
164        // log.info("Unit {} value {}", unitid, (int) newStep);
165        boolean didIt = setDmxIntensity(unitid, newStep);
166        if (!didIt) {
167            log.error("Invalid Dmx Message for unit {} value {}", unitid, newStep);
168        }
169        return didIt;
170    }
171    private final static Logger log = LoggerFactory.getLogger(SpecificTrafficController.class);
172
173}
174