001package jmri.jmrix.pi;
002
003import com.pi4j.context.Context;
004import com.pi4j.io.gpio.digital.DigitalOutput;
005import com.pi4j.io.gpio.digital.DigitalState;
006import com.pi4j.io.exception.IOException;
007
008import jmri.implementation.AbstractTurnout;
009import jmri.jmrix.pi.simulator.GpioPinDigitalOutputSimulator;
010import jmri.jmrix.pi.simulator.GpioSimulator;
011
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015/**
016 * Turnout interface to RaspberryPi GPIO pins.
017 * <p>
018 * Uses Pi4J 3.x on real hardware, or the JMRI-internal
019 * {@link GpioSimulator} when in simulator mode.
020 * <p>
021 * <b>Pin numbering:</b> the numeric part of the system name is the BCM
022 * (Broadcom) GPIO number. For example, system name {@code "PT2"} addresses
023 * BCM GPIO 2.
024 *
025 * @author Paul Bender Copyright (C) 2015
026 */
027public class RaspberryPiTurnout extends AbstractTurnout implements java.io.Serializable {
028
029    private static final long serialVersionUID = 1L;
030
031    /** Pi4J digital output — non-null only in production (non-simulator) mode. */
032    private transient DigitalOutput pi4jPin = null;
033
034    /** JMRI simulator output pin — non-null only in simulator mode. */
035    private transient GpioPinDigitalOutputSimulator simPin = null;
036
037    /** Pi4J registry key for this pin, used to shut it down individually. */
038    private String pinId = null;
039
040    public RaspberryPiTurnout(String systemName) {
041        super(systemName);
042        log.trace("Provisioning turnout '{}'", systemName);
043        init(systemName);
044    }
045
046    public RaspberryPiTurnout(String systemName, String userName) {
047        super(systemName, userName);
048        log.trace("Provisioning turnout '{}' with username '{}'", systemName, userName);
049        init(systemName);
050    }
051
052    /**
053     * Common initialisation for all constructors.
054     * <p>
055     * Compare {@link RaspberryPiSensor}
056     */
057    private void init(String systemName) {
058        log.debug("Provisioning turnout {}", systemName);
059        int address = Integer.parseInt(getSystemName().substring(getSystemName().lastIndexOf("T") + 1));
060        pinId = "jmri-rpi-turnout-" + address;
061
062        if (RaspberryPiAdapter.isSimulator()) {
063            simPin = GpioSimulator.getInstance().provisionDigitalOutputPin(address, systemName);
064        } else {
065            Context ctx = RaspberryPiAdapter.getSharedContext();
066            if (ctx == null) {
067                String msg = Bundle.getMessage("PinNameNotValid", "GPIO " + address, systemName);
068                log.error(msg);
069                throw new IllegalArgumentException(msg);
070            }
071            try {
072                pi4jPin = ctx.create(
073                    DigitalOutput.newConfigBuilder(ctx)
074                        .id(pinId)
075                        .name(systemName)
076                        .address(address)
077                        .shutdown(DigitalState.LOW)
078                        .initial(DigitalState.LOW)
079                        .build()
080                );
081            } catch (RuntimeException re) {
082                log.error("Provisioning turnout {} failed with: {}", systemName, re.getMessage());
083                throw new IllegalArgumentException(re.getMessage());
084            }
085        }
086    }
087
088    // support inversion for RPi turnouts
089    @Override
090    public boolean canInvert() {
091        return true;
092    }
093
094    /**
095     * {@inheritDoc}
096     * Sets the GPIO pin high or low to represent CLOSED or THROWN.
097     */
098    @Override
099    protected void forwardCommandChangeToLayout(int newState) {
100        try {
101            if (newState == CLOSED) {
102                log.debug("Setting turnout '{}' to CLOSED", getSystemName());
103                if (!getInverted()) {
104                    if (pi4jPin != null) pi4jPin.high(); else simPin.high();
105                } else {
106                    if (pi4jPin != null) pi4jPin.low(); else simPin.low();
107                }
108            } else if (newState == THROWN) {
109                log.debug("Setting turnout '{}' to THROWN", getSystemName());
110                if (!getInverted()) {
111                    if (pi4jPin != null) pi4jPin.low(); else simPin.low();
112                } else {
113                    if (pi4jPin != null) pi4jPin.high(); else simPin.high();
114                }
115            }
116        } catch (IOException ex) {
117            log.error("Error setting turnout {}: {}", getSystemName(), ex.getMessage());
118        }
119    }
120
121    @Override
122    public void dispose() {
123        if (pi4jPin != null) {
124            try {
125                Context ctx = RaspberryPiAdapter.getSharedContext();
126                if (ctx != null) {
127                    ctx.shutdown(pinId);
128                }
129            } catch (Exception ex) {
130                log.trace("Pin {} not found during dispose — already removed?", pinId);
131            }
132            pi4jPin = null;
133        } else if (simPin != null) {
134            int address = Integer.parseInt(
135                getSystemName().substring(getSystemName().lastIndexOf("T") + 1));
136            GpioSimulator.getInstance().unprovisionOutputPin(address);
137            simPin = null;
138        }
139        super.dispose();
140    }
141
142    @Override
143    protected void turnoutPushbuttonLockout(boolean locked) {
144    }
145
146    private static final Logger log = LoggerFactory.getLogger(RaspberryPiTurnout.class);
147
148}