package com.pi4j.gpio.extension.pcf; /* * #%L * ********************************************************************** * ORGANIZATION : Pi4J * PROJECT : Pi4J :: GPIO Extension * FILENAME : PCF8574GpioProvider.java * * This file is part of the Pi4J project. More information about * this project can be found here: http://www.pi4j.com/ * ********************************************************************** * %% * Copyright (C) 2012 - 2013 Pi4J * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.io.IOException; import java.util.BitSet; import com.pi4j.io.gpio.GpioProvider; import com.pi4j.io.gpio.GpioProviderBase; import com.pi4j.io.gpio.Pin; import com.pi4j.io.gpio.PinMode; import com.pi4j.io.gpio.PinState; import com.pi4j.io.gpio.event.PinDigitalStateChangeEvent; import com.pi4j.io.gpio.event.PinListener; import com.pi4j.io.gpio.exception.InvalidPinException; import com.pi4j.io.gpio.exception.InvalidPinModeException; import com.pi4j.io.gpio.exception.UnsupportedPinModeException; import com.pi4j.io.i2c.I2CBus; import com.pi4j.io.i2c.I2CDevice; import com.pi4j.io.i2c.I2CFactory; /** * <p> * This GPIO provider implements the PCF8574 I2C GPIO expansion board as native Pi4J GPIO pins. * More information about the board can be found here: * * http://www.ti.com/lit/ds/symlink/pcf8574.pdf * </p> * * <p> * The PCF8574 is connected via I2C connection to the Raspberry Pi and provides * 8 GPIO pins that can be used for either digital input or digital output pins. * </p> * * @author Robert Savage * */ public class PCF8574GpioProvider extends GpioProviderBase implements GpioProvider { public static final String NAME = "com.pi4j.gpio.extension.ti.PCF8574GpioProvider"; public static final String DESCRIPTION = "PCF8574 GPIO Provider"; //these addresses belong to PCF8574(P) public static final int PCF8574_0x20 = 0x20; // 000 public static final int PCF8574_0x21 = 0x21; // 001 public static final int PCF8574_0x22 = 0x22; // 010 public static final int PCF8574_0x23 = 0x23; // 011 public static final int PCF8574_0x24 = 0x24; // 100 public static final int PCF8574_0x25 = 0x25; // 101 public static final int PCF8574_0x26 = 0x26; // 110 public static final int PCF8574_0x27 = 0x27; // 111 //these addresses belong to PCF8574A(P) public static final int PCF8574A_0x38 = 0x38; // 000 public static final int PCF8574A_0x39 = 0x39; // 001 public static final int PCF8574A_0x3A = 0x3A; // 010 public static final int PCF8574A_0x3B = 0x3B; // 011 public static final int PCF8574A_0x3C = 0x3C; // 100 public static final int PCF8574A_0x3D = 0x3D; // 101 public static final int PCF8574A_0x3E = 0x3E; // 110 public static final int PCF8574A_0x3F = 0x3F; // 111 public static final int PCF8574_MAX_IO_PINS = 8; private I2CBus bus; private I2CDevice device; private GpioStateMonitor monitor = null; private BitSet currentStates = new BitSet(PCF8574_MAX_IO_PINS); public PCF8574GpioProvider(int busNumber, int address) throws IOException { // create I2C communications bus instance bus = I2CFactory.getInstance(busNumber); // create I2C device instance device = bus.getDevice(address); // set all default pin cache states to match documented chip power up states for (Pin pin : PCF8574Pin.ALL) { getPinCache(pin).setState(PinState.HIGH); currentStates.set(pin.getAddress(), true); } // start monitoring thread monitor = new PCF8574GpioProvider.GpioStateMonitor(device); monitor.start(); } @Override public String getName() { return NAME; } @Override public void export(Pin pin, PinMode mode) { // make sure to set the pin mode super.export(pin, mode); setMode(pin, mode); } @Override public void unexport(Pin pin) { super.unexport(pin); setMode(pin, PinMode.DIGITAL_OUTPUT); } @Override public void setMode(Pin pin, PinMode mode) { // validate if (!pin.getSupportedPinModes().contains(mode)) { throw new InvalidPinModeException(pin, "Invalid pin mode [" + mode.getName() + "]; pin [" + pin.getName() + "] does not support this mode."); } // validate if (!pin.getSupportedPinModes().contains(mode)) { throw new UnsupportedPinModeException(pin, mode); } // cache mode getPinCache(pin).setMode(mode); } @Override public PinMode getMode(Pin pin) { return super.getMode(pin); } @Override public void setState(Pin pin, PinState state) { // validate if (hasPin(pin) == false) { throw new InvalidPinException(pin); } // only permit invocation on pins set to DIGITAL_OUTPUT modes if (getPinCache(pin).getMode() != PinMode.DIGITAL_OUTPUT) { throw new InvalidPinModeException(pin, "Invalid pin mode on pin [" + pin.getName() + "]; cannot setState() when pin mode is [" + getPinCache(pin).getMode().getName() + "]"); } try { // set state value for pin bit currentStates.set(pin.getAddress(), state.isHigh()); // update state value device.write(currentStates.toByteArray()[0]); } catch (IOException ex) { throw new RuntimeException(ex); } // cache pin state getPinCache(pin).setState(state); } @Override public PinState getState(Pin pin) { return super.getState(pin); } @Override public void shutdown() { // prevent reentrant invocation if(isShutdown()) return; // perform shutdown login in base super.shutdown(); try { // if a monitor is running, then shut it down now if (monitor != null) { // shutdown monitoring thread monitor.shutdown(); monitor = null; } // close the I2C bus communication bus.close(); } catch (IOException e) { throw new RuntimeException(e); } } /** * This class/thread is used to to actively monitor for GPIO interrupts * * @author Robert Savage * */ private class GpioStateMonitor extends Thread { private I2CDevice device; private boolean shuttingDown = false; public GpioStateMonitor(I2CDevice device) { this.device = device; } public void shutdown() { shuttingDown = true; } public void run() { while (!shuttingDown) { try { // read device pins state byte[] buffer = new byte[1]; device.read(buffer, 0, 1); BitSet pinStates = BitSet.valueOf(buffer); // determine if there is a pin state difference for (int index = 0; index < pinStates.size(); index++) { if (pinStates.get(index) != currentStates.get(index)) { Pin pin = PCF8574Pin.ALL[index]; PinState newState = (pinStates.get(index)) ? PinState.HIGH : PinState.LOW; // cache state getPinCache(pin).setState(newState); currentStates.set(index, pinStates.get(index)); // only dispatch events for input pins if (getMode(pin) == PinMode.DIGITAL_INPUT) { // change detected for INPUT PIN // System.out.println("<<< CHANGE >>> " + pin.getName() + " : " + state); dispatchPinChangeEvent(pin.getAddress(), newState); } } } // ... lets take a short breather ... Thread.currentThread(); Thread.sleep(50); } catch (Exception ex) { ex.printStackTrace(); } } } private void dispatchPinChangeEvent(int pinAddress, PinState state) { // iterate over the pin listeners map for (Pin pin : listeners.keySet()) { // System.out.println("<<< DISPATCH >>> " + pin.getName() + " : " + // state.getName()); // dispatch this event to the listener // if a matching pin address is found if (pin.getAddress() == pinAddress) { // dispatch this event to all listener handlers for (PinListener listener : listeners.get(pin)) { listener.handlePinEvent(new PinDigitalStateChangeEvent(this, pin, state)); } } } } } }