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}