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}