001package jmri.jmrix.pi;
002
003import com.pi4j.Pi4J;
004import com.pi4j.context.Context;
005import com.pi4j.exception.ShutdownException;
006
007import javax.annotation.CheckForNull;
008
009import jmri.jmrix.pi.simulator.GpioSimulator;
010
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * Provides an Adapter to allow the system connection memo and multiple
016 * RaspberryPi managers to be handled.
017 * <p>
018 * Uses Pi4J 3.x on real Raspberry Pi hardware, or the JMRI-internal
019 * {@link GpioSimulator} in simulator mode.
020 * Pin addresses are BCM (Broadcom) numbers; e.g. "PS4" → BCM GPIO 4.
021 *
022 * @author Bob Jacobsen Copyright (C) 2001, 2002
023 * @author Paul Bender Copyright (C) 2015
024 */
025public class RaspberryPiAdapter extends jmri.jmrix.AbstractPortController {
026
027    private static boolean _isSimulator = false;
028
029    /**
030     * Shared Pi4J context used by this adapter, {@link RaspberryPiSensor}, and
031     * {@link RaspberryPiTurnout}. Null in simulator mode or before a real
032     * adapter has been opened.
033     */
034    private static Context sharedPi4JContext = null;
035
036    /**
037     * No-arg constructor. Inherits the current value of the static simulator
038     * flag so that tests which call {@link #setIsSimulator(boolean)} before
039     * construction will automatically get a simulator-mode adapter.
040     */
041    public RaspberryPiAdapter() {
042        this(_isSimulator);
043    }
044
045    public RaspberryPiAdapter(boolean isSimulator) {
046        super(new RaspberryPiSystemConnectionMemo());
047        log.debug("RaspberryPi GPIO Adapter Constructor called");
048        setIsSimulator(isSimulator);
049        this.manufacturerName = RaspberryPiConnectionTypeList.PI;
050        if (!isSimulator) {
051            if (initSharedContext()) {
052                opened = true;
053            }
054        } else {
055            opened = true;
056        }
057    }
058
059    /**
060     * Initialize the shared Pi4J context. Called from constructors only.
061     * Synchronized on the class to safely write the static field.
062     * <p>
063     * Catches {@link LinkageError} (covers {@code UnsatisfiedLinkError} when a
064     * native pigpio library is missing <em>and</em> {@code NoClassDefFoundError}
065     * when pi4j-core is absent from the runtime classpath) as well as
066     * {@link RuntimeException} (covers Pi4J's own
067     * {@code Pi4JInitializationException} when no platform provider is
068     * available on non-Raspberry-Pi hardware).
069     *
070     * @return true if the context was successfully created
071     */
072    private static synchronized boolean initSharedContext() {
073        try {
074            sharedPi4JContext = Pi4J.newAutoContext();
075            return true;
076        } catch (LinkageError | RuntimeException er) {
077            log.error("Pi4J context could not be initialised — not running on Raspberry Pi hardware, "
078                    + "or pi4j-core is not on the runtime classpath. Detail: {}", er.toString());
079            return false;
080        }
081    }
082
083    /**
084     * Shut down and clear the shared Pi4J context.
085     * Synchronized on the class to safely write the static field.
086     */
087    private static synchronized void shutdownSharedContext() {
088        if (sharedPi4JContext != null) {
089            try {
090                sharedPi4JContext.shutdown();
091            } catch (ShutdownException ex) {
092                log.error("Error shutting down Pi4J context", ex);
093            }
094            sharedPi4JContext = null;
095        }
096    }
097
098    public static boolean isSimulator() {
099        return _isSimulator;
100    }
101
102    /**
103     * Package-private setter so tests in {@code jmri.jmrix.pi} can enable
104     * simulator mode before constructing sensors or turnouts.
105     */
106    static void setIsSimulator(boolean isSimulator) {
107        _isSimulator = isSimulator;
108    }
109
110    /**
111     * Return the shared Pi4J context, creating it lazily if needed.
112     * Returns {@code null} in simulator mode or when Pi4J cannot be
113     * initialised (e.g. not running on Raspberry Pi hardware, or the
114     * pi4j-core jar is absent from the runtime classpath).
115     *
116     * @return the Pi4J context, or {@code null}
117     */
118    @CheckForNull
119    static synchronized Context getSharedContext() {
120        if (sharedPi4JContext == null && !_isSimulator) {
121            try {
122                sharedPi4JContext = Pi4J.newAutoContext();
123            } catch (LinkageError | RuntimeException er) {
124                log.error("Pi4J context could not be initialised — not running on Raspberry Pi hardware, "
125                        + "or pi4j-core is not on the runtime classpath. Detail: {}", er.toString());
126            }
127        }
128        return sharedPi4JContext;
129    }
130
131    @Override
132    public String getCurrentPortName() {
133        return "GPIO";
134    }
135
136    @Override
137    public void dispose() {
138        super.dispose();
139        if (!_isSimulator) {
140            shutdownSharedContext();
141        } else {
142            GpioSimulator.getInstance().shutdown();
143        }
144    }
145
146    @Override
147    public void connect() {
148    }
149
150    @Override
151    public void configure() {
152        this.getSystemConnectionMemo().configureManagers();
153    }
154
155    @Override
156    public java.io.DataInputStream getInputStream() {
157        return null;
158    }
159
160    @Override
161    public java.io.DataOutputStream getOutputStream() {
162        return null;
163    }
164
165    @Override
166    public RaspberryPiSystemConnectionMemo getSystemConnectionMemo() {
167        return (RaspberryPiSystemConnectionMemo) super.getSystemConnectionMemo();
168    }
169
170    @Override
171    public void recover() {
172    }
173
174    /**
175     * Get the Pi4J context associated with this adapter.
176     * Returns {@code null} in simulator mode or when not running on a Pi.
177     *
178     * @return the Pi4J {@link Context}, or {@code null}
179     */
180    @CheckForNull
181    public Context getPi4JContext() {
182        return sharedPi4JContext;
183    }
184
185    private static final Logger log = LoggerFactory.getLogger(RaspberryPiAdapter.class);
186
187}