001package com.pi4j.io.i2c.impl; 002 003/* 004 * #%L 005 * ********************************************************************** 006 * ORGANIZATION : Pi4J 007 * PROJECT : Pi4J :: Java Library (Core) 008 * FILENAME : I2CBusImpl.java 009 * 010 * This file is part of the Pi4J project. More information about 011 * this project can be found here: http://www.pi4j.com/ 012 * ********************************************************************** 013 * %% 014 * Copyright (C) 2012 - 2016 Pi4J 015 * %% 016 * This program is free software: you can redistribute it and/or modify 017 * it under the terms of the GNU Lesser General Public License as 018 * published by the Free Software Foundation, either version 3 of the 019 * License, or (at your option) any later version. 020 * 021 * This program is distributed in the hope that it will be useful, 022 * but WITHOUT ANY WARRANTY; without even the implied warranty of 023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 024 * GNU General Lesser Public License for more details. 025 * 026 * You should have received a copy of the GNU General Lesser Public 027 * License along with this program. If not, see 028 * <http://www.gnu.org/licenses/lgpl-3.0.html>. 029 * #L% 030 */ 031 032import java.io.IOException; 033import java.util.concurrent.Callable; 034import java.util.concurrent.TimeUnit; 035import java.util.concurrent.locks.ReentrantLock; 036import java.util.logging.Level; 037import java.util.logging.Logger; 038 039import com.pi4j.io.i2c.I2CBus; 040import com.pi4j.io.i2c.I2CDevice; 041import com.pi4j.io.i2c.I2CFactory; 042import com.pi4j.jni.I2C; 043 044/** 045 * This is implementation of i2c bus. This class keeps underlying linux file descriptor of particular bus. As all reads and writes from/to i2c bus are blocked I/Os current implementation uses only one file per bus for all devices. Device 046 * implementations use this class file handle. 047 * 048 * Hint: For concurrency-locking the methods lock() and unlock() are provided. This requires that there is exactly one I2CBus-instance per bus-number what is guaranteed by the I2CFactory class. The locking is done by I2CDeviceImpl by using 049 * those methods. The reason for this is to enable other locking-strategies than the simple "lock before and release after access"-strategy. 050 * 051 * @author Daniel Sendula, refactored by <a href="http://raspelikan.blogspot.co.at">RasPelikan</a> 052 * 053 */ 054public class I2CBusImpl implements I2CBus { 055 056 private static final Logger logger = Logger.getLogger(I2CBusImpl.class.getCanonicalName()); 057 058 /** File handle for this i2c bus */ 059 protected int fd = -1; 060 061 /** File name of this i2c bus */ 062 protected String filename; 063 064 /** Used to identifiy the i2c bus within Pi4J **/ 065 protected int busNumber; 066 067 protected long lockAquireTimeout; 068 069 protected TimeUnit lockAquireTimeoutUnit; 070 071 private final ReentrantLock accessLock = new ReentrantLock(true); 072 073 /** 074 * Constructor of i2c bus implementation. 075 * 076 * @param busNumber used to identifiy the i2c bus within Pi4J 077 078 * @throws IOException thrown in case that file cannot be opened 079 */ 080 protected I2CBusImpl(final int busNumber, final String fileName, final long lockAquireTimeout, final TimeUnit lockAquireTimeoutUnit) { 081 this.filename = fileName; 082 this.busNumber = busNumber; 083 084 if (lockAquireTimeout < 0) { 085 this.lockAquireTimeout = I2CFactory.DEFAULT_LOCKAQUIRE_TIMEOUT; 086 } else { 087 this.lockAquireTimeout = lockAquireTimeout; 088 } 089 090 if (lockAquireTimeoutUnit == null) { 091 this.lockAquireTimeoutUnit = I2CFactory.DEFAULT_LOCKAQUIRE_TIMEOUT_UNITS; 092 } else { 093 this.lockAquireTimeoutUnit = lockAquireTimeoutUnit; 094 } 095 } 096 097 /** 098 * Returns i2c device implementation ({@link I2CDeviceImpl}). 099 * 100 * @param address address of i2c device 101 * 102 * @return implementation of i2c device with given address 103 * 104 * @throws IOException never in this implementation 105 */ 106 @Override 107 public I2CDevice getDevice(int address) throws IOException { 108 return new I2CDeviceImpl(this, address); 109 } 110 111 /** 112 * Opens the bus. 113 * 114 * @throws IOException thrown in case there are problems opening the i2c bus. 115 */ 116 protected void open() throws IOException { 117 if (fd != -1) { 118 return; 119 } 120 121 fd = I2C.i2cOpen(filename); 122 if (fd < 0) { 123 throw new IOException("Cannot open file handle for " + filename + " got " + fd + " back."); 124 } 125 } 126 127 /** 128 * Closes this i2c bus 129 * 130 * @throws IOException never in this implementation 131 */ 132 @Override 133 public void close() throws IOException { 134 if (fd == -1) { 135 return; 136 } 137 138 I2CProviderImpl.closeBus(getBusNumber(), lockAquireTimeout, lockAquireTimeoutUnit, new Callable<Void>() { 139 @Override 140 public Void call() { 141 I2C.i2cClose(fd); 142 fd = -1; 143 return null; 144 } 145 }); 146 } 147 148 public int readByteDirect(final I2CDeviceImpl device) throws IOException { 149 testForProperOperationConditions(device); 150 151 return runActionOnExclusivLockedBus(new Callable<Integer>() { 152 @Override 153 public Integer call() throws Exception { 154 return I2C.i2cReadByteDirect(fd, device.getAddress()); 155 } 156 }); 157 } 158 159 public int readBytesDirect(final I2CDeviceImpl device, final int size, final int offset, final byte[] buffer) throws IOException { 160 testForProperOperationConditions(device); 161 162 return runActionOnExclusivLockedBus(new Callable<Integer>() { 163 @Override 164 public Integer call() throws Exception { 165 return I2C.i2cReadBytesDirect(fd, device.getAddress(), size, offset, buffer); 166 } 167 }); 168 } 169 170 public int readByte(final I2CDeviceImpl device, final int localAddress) throws IOException { 171 testForProperOperationConditions(device); 172 173 return runActionOnExclusivLockedBus(new Callable<Integer>() { 174 @Override 175 public Integer call() throws Exception { 176 return I2C.i2cReadByte(fd, device.getAddress(), localAddress); 177 } 178 }); 179 } 180 181 public int readBytes(final I2CDeviceImpl device, final int localAddress, final int size, final int offset, final byte[] buffer) throws IOException { 182 testForProperOperationConditions(device); 183 184 return runActionOnExclusivLockedBus(new Callable<Integer>() { 185 @Override 186 public Integer call() throws Exception { 187 return I2C.i2cReadBytes(fd, device.getAddress(), localAddress, size, offset, buffer); 188 } 189 }); 190 } 191 192 public int writeByteDirect(final I2CDeviceImpl device, final byte data) throws IOException { 193 testForProperOperationConditions(device); 194 195 return runActionOnExclusivLockedBus(new Callable<Integer>() { 196 @Override 197 public Integer call() throws Exception { 198 return I2C.i2cWriteByteDirect(fd, device.getAddress(), data); 199 } 200 }); 201 } 202 203 public int writeBytesDirect(final I2CDeviceImpl device, final int size, final int offset, final byte[] buffer) throws IOException { 204 testForProperOperationConditions(device); 205 206 return runActionOnExclusivLockedBus(new Callable<Integer>() { 207 @Override 208 public Integer call() throws Exception { 209 return I2C.i2cWriteBytesDirect(fd, device.getAddress(), size, offset, buffer); 210 } 211 }); 212 } 213 214 public int writeByte(final I2CDeviceImpl device, final int localAddress, final byte data) throws IOException { 215 testForProperOperationConditions(device); 216 217 return runActionOnExclusivLockedBus(new Callable<Integer>() { 218 @Override 219 public Integer call() throws Exception { 220 return I2C.i2cWriteByte(fd, device.getAddress(), localAddress, data); 221 } 222 }); 223 } 224 225 public int writeBytes(final I2CDeviceImpl device, final int localAddress, final int size, final int offset, final byte[] buffer) throws IOException { 226 testForProperOperationConditions(device); 227 228 return runActionOnExclusivLockedBus(new Callable<Integer>() { 229 @Override 230 public Integer call() throws Exception { 231 return I2C.i2cWriteBytes(fd, device.getAddress(), localAddress, size, offset, buffer); 232 } 233 }); 234 } 235 236 public int writeAndReadBytesDirect(final I2CDeviceImpl device, final int writeSize, final int writeOffset, final byte[] writeBuffer, final int readSize, final int readOffset, final byte[] readBuffer) throws IOException { 237 testForProperOperationConditions(device); 238 239 return runActionOnExclusivLockedBus(new Callable<Integer>() { 240 @Override 241 public Integer call() throws Exception { 242 return I2C.i2cWriteAndReadBytes(fd, device.getAddress(), writeSize, writeOffset, writeBuffer, readSize, readOffset, readBuffer); 243 } 244 }); 245 } 246 247 /** 248 * Sometimes communication to an i2c device must not be disturbed by communication to another i2c device. This method can be used to run a custom sequence of writes/reads. 249 * <p> 250 * The timeout used for the acquisition of the lock may be defined on getting the I2CBus from I2CFactory. 251 * <p> 252 * The 'run'-method of 'action' may throw an 'IOExceptionWrapperException' to wrap IOExceptions. The wrapped IOException is unwrapped by this method and rethrown as IOException. 253 * 254 * @param <T> The result-type of the method 255 * @param action The action to be run 256 * @throws RuntimeException thrown by the custom code 257 * @throws IOException see method description above 258 * @see I2CFactory#getInstance(int, long, java.util.concurrent.TimeUnit) 259 */ 260 protected <T> T runActionOnExclusivLockedBus(final Callable<T> action) throws IOException { 261 if (action == null) { 262 throw new RuntimeException("Parameter 'action' is mandatory!"); 263 } 264 265 testWhetherBusHasAlreadyBeenClosed(); 266 267 try { 268 if (accessLock.tryLock(lockAquireTimeout, lockAquireTimeoutUnit)) { 269 try { 270 return action.call(); 271 } finally { 272 accessLock.unlock(); 273 } 274 } 275 } catch (InterruptedException e) { 276 logger.log(Level.FINER, "Failed locking I2CBusImpl-" + busNumber, e); 277 throw new RuntimeException("Could not abtain an access-lock!", e); 278 } catch (IOException e) { // unwrap IOExceptionWrapperException 279 throw e; 280 } catch (RuntimeException e) { 281 throw e; 282 } catch (Exception e) { // unexpected exceptions 283 throw new RuntimeException(e); 284 } 285 throw new RuntimeException("Could not abtain an access-lock!"); 286 } 287 288 private void testForProperOperationConditions(final I2CDeviceImpl device) throws IOException { 289 testWhetherBusHasAlreadyBeenClosed(); 290 291 if (device == null) { 292 throw new NullPointerException("Parameter 'device' is mandatory!"); 293 } 294 } 295 296 private void testWhetherBusHasAlreadyBeenClosed() throws IOException { 297 if (fd == -1) { 298 throw new IOException(toString() + " has already been closed! A new bus has to be aquired."); 299 } 300 } 301 302 @Override 303 public int getBusNumber() { 304 return busNumber; 305 } 306 307 @Override 308 public String toString() { 309 return "I2CBus '" + busNumber + "' ('" + filename + "')"; 310 } 311}