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}