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: https://www.pi4j.com/ 012 * ********************************************************************** 013 * %% 014 * Copyright (C) 2012 - 2021 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.nio.ByteBuffer; 034import java.nio.IntBuffer; 035import java.util.concurrent.Callable; 036import java.util.concurrent.TimeUnit; 037import java.util.concurrent.locks.ReentrantLock; 038import java.util.logging.Level; 039import java.util.logging.Logger; 040 041import com.pi4j.io.file.LinuxFile; 042import com.pi4j.io.i2c.I2CBus; 043import com.pi4j.io.i2c.I2CConstants; 044import com.pi4j.io.i2c.I2CDevice; 045import com.pi4j.io.i2c.I2CFactory; 046 047/** 048 * 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 049 * implementations use this class file handle. 050 * 051 * 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 052 * those methods. The reason for this is to enable other locking-strategies than the simple "lock before and release after access"-strategy. 053 * 054 * @author Daniel Sendula, refactored by <a href="http://raspelikan.blogspot.co.at">RasPelikan</a> 055 * 056 */ 057public class I2CBusImpl implements I2CBus { 058 059 private static final Logger logger = Logger.getLogger(I2CBusImpl.class.getCanonicalName()); 060 061 /** File handle for this i2c bus */ 062 protected LinuxFile file = null; 063 064 protected int lastAddress = -1; 065 066 /** File name of this i2c bus */ 067 protected String filename; 068 069 /** Used to identifiy the i2c bus within Pi4J **/ 070 protected int busNumber; 071 072 protected long lockAquireTimeout; 073 074 protected TimeUnit lockAquireTimeoutUnit; 075 076 private final ReentrantLock accessLock = new ReentrantLock(true); 077 078 /** 079 * Constructor of i2c bus implementation. 080 * 081 * @param busNumber used to identifiy the i2c bus within Pi4J 082 083 * @throws IOException thrown in case that file cannot be opened 084 */ 085 protected I2CBusImpl(final int busNumber, final String fileName, final long lockAquireTimeout, final TimeUnit lockAquireTimeoutUnit) { 086 this.filename = fileName; 087 this.busNumber = busNumber; 088 089 if (lockAquireTimeout < 0) { 090 this.lockAquireTimeout = I2CFactory.DEFAULT_LOCKAQUIRE_TIMEOUT; 091 } else { 092 this.lockAquireTimeout = lockAquireTimeout; 093 } 094 095 if (lockAquireTimeoutUnit == null) { 096 this.lockAquireTimeoutUnit = I2CFactory.DEFAULT_LOCKAQUIRE_TIMEOUT_UNITS; 097 } else { 098 this.lockAquireTimeoutUnit = lockAquireTimeoutUnit; 099 } 100 } 101 102 /** 103 * Returns i2c device implementation ({@link I2CDeviceImpl}). 104 * 105 * @param address address of i2c device 106 * 107 * @return implementation of i2c device with given address 108 * 109 * @throws IOException never in this implementation 110 */ 111 @Override 112 public I2CDevice getDevice(int address) throws IOException { 113 return new I2CDeviceImpl(this, address); 114 } 115 116 /** 117 * Opens the bus. 118 * 119 * @throws IOException thrown in case there are problems opening the i2c bus. 120 */ 121 protected void open() throws IOException { 122 if (file != null) { 123 return; 124 } 125 126 file = new LinuxFile(filename, "rw"); 127 128 lastAddress = -1; 129 } 130 131 /** 132 * Closes this i2c bus. Can be used in a thread safe way during bus operations. 133 * 134 * @throws IOException never in this implementation 135 */ 136 @Override 137 public synchronized void close() throws IOException { 138 if (file != null) { 139 file.close(); 140 file = null; 141 } 142 } 143 144 public int readByteDirect(final I2CDevice device) throws IOException { 145 return runBusLockedDeviceAction(device, () -> file.readUnsignedByte()); 146 } 147 148 public int readBytesDirect(final I2CDevice device, final int size, final int offset, final byte[] buffer) throws IOException { 149 return runBusLockedDeviceAction(device, () -> file.read(buffer, offset, size)); 150 } 151 152 public int readByte(final I2CDevice device, final int localAddress) throws IOException { 153 return runBusLockedDeviceAction(device, () -> { 154 file.writeByte(localAddress); 155 156 return file.readUnsignedByte(); 157 }); 158 } 159 160 public int readBytes(final I2CDevice device, final int localAddress, final int size, final int offset, final byte[] buffer) throws IOException { 161 return runBusLockedDeviceAction(device, () -> { 162 file.writeByte(localAddress); 163 164 return file.read(buffer, offset, size); 165 }); 166 } 167 168 public void writeByteDirect(final I2CDevice device, final byte data) throws IOException { 169 runBusLockedDeviceAction(device, () -> { 170 file.writeByte(data & 0xFF); 171 172 return null; 173 }); 174 } 175 176 public void writeBytesDirect(final I2CDevice device, final int size, final int offset, final byte[] buffer) throws IOException { 177 runBusLockedDeviceAction(device, () -> { 178 file.write(buffer, offset, size); 179 180 return null; 181 }); 182 } 183 184 public void writeByte(final I2CDevice device, final int localAddress, final byte data) throws IOException { 185 runBusLockedDeviceAction(device, () -> { 186 file.write(new byte[] { (byte)localAddress, data }); 187 188 return null; 189 }); 190 } 191 192 public void writeBytes(final I2CDevice device, final int localAddress, final int size, final int offset, final byte[] buffer) throws IOException { 193 runBusLockedDeviceAction(device, () -> { 194 byte[] buf = new byte[size + 1]; 195 196 buf[0] = (byte)localAddress; 197 198 System.arraycopy(buffer, offset, buf, 1, size); 199 200 file.write(buf); 201 202 return null; 203 }); 204 } 205 206 public int writeAndReadBytesDirect(final I2CDevice device, final int writeSize, final int writeOffset, final byte[] writeBuffer, 207 final int readSize, final int readOffset, final byte[] readBuffer) throws IOException { 208 return runBusLockedDeviceAction(device, () -> { 209 file.write(writeBuffer, writeOffset, writeSize); 210 211 return file.read(readBuffer, readOffset, readSize); 212 }); 213 } 214 215 public void ioctl(final I2CDevice device, final long command, final int value) throws IOException { 216 runBusLockedDeviceAction(device, () -> { 217 file.ioctl(command, value); 218 219 return null; 220 }); 221 } 222 223 public void ioctl(final I2CDevice device, final long command, final ByteBuffer values, final IntBuffer offsets) throws IOException { 224 runBusLockedDeviceAction(device, () -> { 225 file.ioctl(command, values, offsets); 226 227 return null; 228 }); 229 } 230 231 /** 232 * Selects a device on the bus for an action, and locks parallel access around file descriptor operations. 233 * Multiple bus instances may be used in parallel, but a single bus instance must limit parallel access. 234 * <p> 235 * The timeout used for the acquisition of the lock may be defined on getting the I2CBus from I2CFactory. 236 * <p> 237 * The 'run'-method of 'action' may throw an 'IOExceptionWrapperException' to wrap IOExceptions. The wrapped IOException is unwrapped by this method and rethrown as IOException. 238 * 239 * @param <T> The result-type of the method 240 * @param device Device to be selected on the bus 241 * @param action The action to be run 242 * @throws RuntimeException thrown by the custom code 243 * @throws IOException see method description above 244 * @see I2CFactory#getInstance(int, long, java.util.concurrent.TimeUnit) 245 */ 246 public <T> T runBusLockedDeviceAction(final I2CDevice device, final Callable<T> action) throws IOException { 247 if (action == null) { 248 throw new NullPointerException("Parameter 'action' is mandatory!"); 249 } 250 251 testForProperOperationConditions(device); 252 253 try { 254 if (accessLock.tryLock(lockAquireTimeout, lockAquireTimeoutUnit)) { 255 try { 256 testForProperOperationConditions(device); 257 258 selectBusSlave(device); 259 260 return action.call(); 261 } finally { 262 accessLock.unlock(); 263 } 264 } 265 } catch (InterruptedException e) { 266 logger.log(Level.FINER, "Failed locking I2CBusImpl-" + busNumber, e); 267 throw new RuntimeException("Could not obtain an access-lock!", e); 268 } catch (IOException e) { // unwrap IOExceptionWrapperException 269 throw e; 270 } catch (RuntimeException e) { 271 throw e; 272 } catch (Exception e) { // unexpected exceptions 273 throw new RuntimeException(e); 274 } 275 throw new RuntimeException("Could not obtain an access-lock!"); 276 } 277 278 /** 279 * Selects the slave device if not already selected on this bus. 280 * Uses SharedSecrets to get the POSIX file descriptor, and runs 281 * the required ioctl's via JNI. 282 * 283 * @param device Device to select 284 */ 285 protected void selectBusSlave(final I2CDevice device) throws IOException { 286 final int addr = device.getAddress(); 287 288 if (lastAddress != addr) { 289 lastAddress = addr; 290 291 file.ioctl(I2CConstants.I2C_SLAVE, addr & 0xFF); 292 } 293 } 294 295 protected void testForProperOperationConditions(final I2CDevice device) throws IOException { 296 if (file == null) { 297 throw new IOException(toString() + " has already been closed! A new bus has to be acquired."); 298 } 299 300 if (device == null) { 301 throw new NullPointerException("Parameter 'device' is mandatory!"); 302 } 303 } 304 305 @Override 306 public int getBusNumber() { 307 return busNumber; 308 } 309 310 @Override 311 public String toString() { 312 return "I2CBus '" + busNumber + "' ('" + filename + "')"; 313 } 314}