001package jmri.jmrix.pi; 002 003import com.pi4j.context.Context; 004import com.pi4j.io.gpio.digital.DigitalInput; 005import com.pi4j.io.gpio.digital.DigitalState; 006 007import jmri.Sensor; 008import jmri.implementation.AbstractSensor; 009import jmri.jmrix.pi.simulator.GpioPinDigitalInputSimulator; 010import jmri.jmrix.pi.simulator.GpioSimulator; 011 012/** 013 * Sensor interface for RaspberryPi GPIO pins. 014 * <p> 015 * Uses Pi4J 3.x on real hardware, or the JMRI-internal 016 * {@link GpioSimulator} when in simulator mode. 017 * <p> 018 * <b>Pin numbering:</b> the numeric part of the system name is the BCM 019 * (Broadcom) GPIO number. For example, system name {@code "PS4"} addresses 020 * BCM GPIO 4. 021 * 022 * @author Paul Bender Copyright (C) 2003-2017 023 */ 024public class RaspberryPiSensor extends AbstractSensor { 025 026 /** Pi4J digital input — non-null only in production (non-simulator) mode. */ 027 private DigitalInput pi4jPin = null; 028 029 /** JMRI simulator input pin — non-null only in simulator mode. */ 030 private GpioPinDigitalInputSimulator simPin = null; 031 032 /** Pi4J registry key for this pin, used to shut it down individually. */ 033 private String pinId = null; 034 035 private com.pi4j.io.gpio.digital.PullResistance pi4jPull = com.pi4j.io.gpio.digital.PullResistance.PULL_DOWN; 036 private jmri.Sensor.PullResistance pull = jmri.Sensor.PullResistance.PULL_DOWN; 037 038 public RaspberryPiSensor(String systemName, String userName) { 039 super(systemName, userName); 040 init(systemName, jmri.Sensor.PullResistance.PULL_DOWN); 041 } 042 043 public RaspberryPiSensor(String systemName, String userName, jmri.Sensor.PullResistance p) { 044 super(systemName, userName); 045 init(systemName, p); 046 } 047 048 public RaspberryPiSensor(String systemName) { 049 super(systemName); 050 init(systemName, jmri.Sensor.PullResistance.PULL_DOWN); 051 } 052 053 public RaspberryPiSensor(String systemName, jmri.Sensor.PullResistance p) { 054 super(systemName); 055 init(systemName, p); 056 } 057 058 /** 059 * Common initialisation for all constructors. 060 * <p> 061 * Compare {@link RaspberryPiTurnout} 062 */ 063 private void init(String systemName, jmri.Sensor.PullResistance pRes) { 064 log.debug("Provisioning sensor {}", systemName); 065 pull = pRes; 066 pi4jPull = toPi4JPull(pRes); 067 int address = Integer.parseInt(systemName.substring(systemName.lastIndexOf("S") + 1)); 068 pinId = "jmri-rpi-sensor-" + address; 069 070 if (RaspberryPiAdapter.isSimulator()) { 071 simPin = GpioSimulator.getInstance().provisionDigitalInputPin(address, systemName); 072 } else { 073 Context ctx = RaspberryPiAdapter.getSharedContext(); 074 if (ctx == null) { 075 String msg = Bundle.getMessage("PinNameNotValid", "GPIO " + address, systemName); 076 log.error(msg); 077 throw new IllegalArgumentException(msg); 078 } 079 try { 080 pi4jPin = ctx.create( 081 DigitalInput.newConfigBuilder(ctx) 082 .id(pinId) 083 .name(systemName) 084 .address(address) 085 .pull(pi4jPull) 086 .build() 087 ); 088 pi4jPin.addListener(event -> setStateBeforeInvert(event.state().isHigh())); 089 } catch (RuntimeException re) { 090 log.error("Provisioning sensor {} failed with: {}", systemName, re.getMessage()); 091 throw new IllegalArgumentException(re.getMessage()); 092 } 093 } 094 requestUpdateFromLayout(); 095 } 096 097 private static com.pi4j.io.gpio.digital.PullResistance toPi4JPull(jmri.Sensor.PullResistance pr) { 098 if (pr == jmri.Sensor.PullResistance.PULL_UP) return com.pi4j.io.gpio.digital.PullResistance.PULL_UP; 099 if (pr == jmri.Sensor.PullResistance.PULL_DOWN) return com.pi4j.io.gpio.digital.PullResistance.PULL_DOWN; 100 return com.pi4j.io.gpio.digital.PullResistance.OFF; 101 } 102 103 /** 104 * Request an update on status by reading the current pin state. 105 */ 106 @Override 107 public void requestUpdateFromLayout() { 108 if (pi4jPin != null) { 109 setStateBeforeInvert(pi4jPin.state() == DigitalState.HIGH); 110 } else if (simPin != null) { 111 setStateBeforeInvert(simPin.isHigh()); 112 } 113 } 114 115 private void setStateBeforeInvert(boolean high) { 116 if (high) { 117 setOwnState(!getInverted() ? Sensor.ACTIVE : Sensor.INACTIVE); 118 } else { 119 setOwnState(!getInverted() ? Sensor.INACTIVE : Sensor.ACTIVE); 120 } 121 } 122 123 /** 124 * Set the pull resistance. 125 * <p> 126 * Note: Pi4J 3.x does not support changing pull resistance after a pin is 127 * provisioned. This call updates the cached value but has no effect on 128 * already-provisioned hardware pins. 129 * 130 * @param r the new PullResistance value 131 */ 132 @Override 133 public void setPullResistance(jmri.Sensor.PullResistance r) { 134 pull = r; 135 pi4jPull = toPi4JPull(r); 136 if (!RaspberryPiAdapter.isSimulator()) { 137 log.warn("Changing pull resistance after pin provisioning is not supported in Pi4J 3.x"); 138 } 139 } 140 141 /** 142 * Get the pull resistance. 143 * 144 * @return the currently configured pull resistance 145 */ 146 @Override 147 public jmri.Sensor.PullResistance getPullResistance() { 148 return pull; 149 } 150 151 @Override 152 public void dispose() { 153 if (pi4jPin != null) { 154 try { 155 Context ctx = RaspberryPiAdapter.getSharedContext(); 156 if (ctx != null) { 157 ctx.shutdown(pinId); 158 } 159 } catch (Exception ex) { 160 log.trace("Pin {} not found during dispose — already removed?", pinId); 161 } 162 pi4jPin = null; 163 } else if (simPin != null) { 164 int address = Integer.parseInt( 165 getSystemName().substring(getSystemName().lastIndexOf("S") + 1)); 166 GpioSimulator.getInstance().unprovisionInputPin(address); 167 simPin = null; 168 } 169 super.dispose(); 170 } 171 172 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RaspberryPiSensor.class); 173 174}