001package com.pi4j.io.i2c.impl;
002
003/*
004 * #%L
005 * **********************************************************************
006 * ORGANIZATION  :  Pi4J
007 * PROJECT       :  Pi4J :: Java Library (Core)
008 * FILENAME      :  I2CDeviceImpl.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 - 2015 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 com.pi4j.io.i2c.I2CBus;
033import com.pi4j.io.i2c.I2CDevice;
034import com.pi4j.jni.I2C;
035
036import java.io.IOException;
037import java.util.concurrent.locks.Lock;
038import java.util.concurrent.locks.ReentrantLock;
039
040/**
041 * Implementation of i2c device. This class only holds reference to i2c bus (so it can use its handle) and
042 * device address.
043 * 
044 * @author Daniel Sendula
045 *
046 */
047public class I2CDeviceImpl implements I2CDevice {
048
049    /** Reference to i2c bus */
050    private I2CBus bus;
051    
052    /** I2c device address */
053    private int deviceAddress;
054
055    private final static Lock lock = new ReentrantLock( true );
056    
057    /**
058     * Constructor.
059     * 
060     * @param bus i2c bus
061     * @param address i2c device address
062     */
063    public I2CDeviceImpl(I2CBus bus, int address) {
064        this.bus = bus;
065        this.deviceAddress = address;
066    }
067
068    /**
069     * This method writes one byte to i2c device. 
070     * 
071     * @param data byte to be written
072     * 
073     * @throws IOException thrown in case byte cannot be written to the i2c device or i2c bus
074     */
075    @Override
076    public void write(byte data) throws IOException {
077        lock.lock();
078        try {
079            int ret = I2C.i2cWriteByteDirect(bus.getFileDescriptor(), deviceAddress, data);
080            if (ret < 0) {
081                throw new IOException("Error writing to " + makeDescription() + ". Got " + ret + ".");
082            }
083        } finally {
084            lock.unlock();
085        }
086    }
087
088    /**
089     * This method writes several bytes to the i2c device from given buffer at given offset.
090     * 
091     * @param buffer buffer of data to be written to the i2c device in one go
092     * @param offset offset in buffer 
093     * @param size number of bytes to be written 
094     * 
095     * @throws IOException thrown in case byte cannot be written to the i2c device or i2c bus
096     */
097    @Override
098    public void write(byte[] buffer, int offset, int size) throws IOException {
099        lock.lock();
100        try {
101            int ret = I2C.i2cWriteBytesDirect(bus.getFileDescriptor(), deviceAddress, size, offset, buffer);
102            if (ret < 0) {
103                throw new IOException("Error writing to " + makeDescription() + ". Got " + ret + ".");
104            }
105        } finally {
106            lock.unlock();
107        }
108    }
109
110    /**
111     * This method writes one byte to i2c device. 
112     * 
113     * @param address local address in the i2c device
114     * @param data byte to be written
115     * 
116     * @throws IOException thrown in case byte cannot be written to the i2c device or i2c bus
117     */
118    @Override
119    public void write(int address, byte data) throws IOException {
120        lock.lock();
121        try {
122            int ret = I2C.i2cWriteByte(bus.getFileDescriptor(), deviceAddress, address, data);
123            if (ret < 0) {
124                throw new IOException("Error writing to " + makeDescription(address) + ". Got " + ret + ".");
125            }
126        } finally {
127            lock.unlock();
128        }
129    }
130
131    /**
132     * This method writes several bytes to the i2c device from given buffer at given offset.
133     * 
134     * @param address local address in the i2c device
135     * @param buffer buffer of data to be written to the i2c device in one go
136     * @param offset offset in buffer 
137     * @param size number of bytes to be written 
138     * 
139     * @throws IOException thrown in case byte cannot be written to the i2c device or i2c bus
140     */
141    @Override
142    public void write(int address, byte[] buffer, int offset, int size) throws IOException {
143        lock.lock();
144        try {
145            int ret = I2C.i2cWriteBytes(bus.getFileDescriptor(), deviceAddress, address, size, offset, buffer);
146            if (ret < 0) {
147                throw new IOException("Error writing to " + makeDescription(address) + ". Got " + ret + ".");
148            }
149        } finally {
150            lock.unlock();
151        }
152    }
153
154    /**
155     * This method reads one byte from the i2c device.
156     * Result is between 0 and 255 if read operation was successful, else a negative number for an error.
157     *
158     * @return byte value read: positive number (or zero) to 255 if read was successful. Negative number if reading failed.
159     * 
160     * @throws IOException thrown in case byte cannot be read from the i2c device or i2c bus
161     */
162    @Override
163    public int read() throws IOException {
164        lock.lock();
165        try {
166            int ret = I2C.i2cReadByteDirect(bus.getFileDescriptor(), deviceAddress);
167            if (ret < 0) {
168                throw new IOException("Error reading from " + makeDescription() + ". Got " + ret + ".");
169            }
170            return ret;
171        } finally {
172            lock.unlock();
173        }
174    }
175
176    /**
177     * <p>This method reads bytes from the i2c device to given buffer at asked offset. </p>
178     * 
179     * <p>Note: Current implementation calls {@link #read(int)}. That means for each read byte 
180     * i2c bus will send (next) address to i2c device.
181     * </p>
182     * 
183     * @param buffer buffer of data to be read from the i2c device in one go
184     * @param offset offset in buffer 
185     * @param size number of bytes to be read 
186     * 
187     * @return number of bytes read
188     * 
189     * @throws IOException thrown in case byte cannot be read from the i2c device or i2c bus
190     */
191    @Override
192    public int read(byte[] buffer, int offset, int size) throws IOException {
193        lock.lock();
194        try {
195            // It doesn't work for some reason.
196            int ret = I2C.i2cReadBytesDirect(bus.getFileDescriptor(), deviceAddress, size, offset, buffer);
197            if (ret < 0) {
198                throw new IOException("Error reading from " + makeDescription() + ". Got " + ret + ".");
199            }
200            return ret;
201        } finally {
202            lock.unlock();
203        }
204    }
205
206    /**
207     * This method reads one byte from the i2c device.
208     * Result is between 0 and 255 if read operation was successful, else a negative number for an error.
209     *
210     * @param address local address in the i2c device
211     * @return byte value read: positive number (or zero) to 255 if read was successful. Negative number if reading failed.
212     * 
213     * @throws IOException thrown in case byte cannot be read from the i2c device or i2c bus
214     */
215    @Override
216    public int read(int address) throws IOException {
217        lock.lock();
218        try {
219            int ret = I2C.i2cReadByte(bus.getFileDescriptor(), deviceAddress, address);
220            if (ret < 0) {
221                throw new IOException("Error reading from " + makeDescription(address) + ". Got " + ret + ".");
222            }
223            return ret;
224        } finally {
225            lock.unlock();
226        }
227    }
228
229    /**
230     * <p>This method reads bytes from the i2c device to given buffer at asked offset. </p>
231     * 
232     * <p>Note: Current implementation calls {@link #read(int)}. That means for each read byte 
233     * i2c bus will send (next) address to i2c device.
234     * </p>
235     * 
236     * @param address local address in the i2c device
237     * @param buffer buffer of data to be read from the i2c device in one go
238     * @param offset offset in buffer 
239     * @param size number of bytes to be read 
240     * 
241     * @return number of bytes read
242     * 
243     * @throws IOException thrown in case byte cannot be read from the i2c device or i2c bus
244     */
245    @Override
246    public int read(int address, byte[] buffer, int offset, int size) throws IOException {
247        lock.lock();
248        try {
249            // It doesn't work for some reason.
250            int ret = I2C.i2cReadBytes(bus.getFileDescriptor(), deviceAddress, address, size, offset, buffer);
251            if (ret < 0) {
252                throw new IOException("Error reading from " + makeDescription(address) + ". Got " + ret + ".");
253            }
254            return ret;
255        } finally {
256            lock.unlock();
257        }
258    }
259
260    /**
261     * This method writes and reads bytes to/from the i2c device in a single method call
262     *
263     * @param writeBuffer buffer of data to be written to the i2c device in one go
264     * @param writeOffset offset in write buffer
265     * @param writeSize number of bytes to be written from buffer
266     * @param readBuffer buffer of data to be read from the i2c device in one go
267     * @param readOffset offset in read buffer
268     * @param readSize number of bytes to be read
269     *
270     * @return number of bytes read
271     *
272     * @throws IOException thrown in case byte cannot be read from the i2c device or i2c bus
273     */
274    @Override
275    public int read(byte[] writeBuffer, int writeOffset, int writeSize, byte[] readBuffer, int readOffset, int readSize) throws IOException {
276        lock.lock();
277        try {
278            int ret = I2C.i2cWriteAndReadBytes(bus.getFileDescriptor(), deviceAddress, writeSize, writeOffset, writeBuffer, readSize, readOffset, readBuffer);
279            if (ret < 0) {
280                throw new IOException("Error reading from " + makeDescription() + ". Got " + ret + ".");
281            }
282            return ret;
283        } finally {
284            lock.unlock();
285        }
286    }
287
288    /**
289     * This helper method creates a string describing bus file name and device address (in hex).
290     * 
291     * @return string with all details
292     */
293    protected String makeDescription() {
294        return bus.getFileName() + " at address 0x" + Integer.toHexString(deviceAddress);
295    }
296    
297    /**
298     * This helper method creates a string describing bus file name, device address (in hex)
299     * and local i2c address.
300     * 
301     * @param address local address in i2c device
302     * @return string with all details
303     */
304    protected String makeDescription(int address) {
305        return bus.getFileName() + " at address 0x" + Integer.toHexString(deviceAddress) 
306                + " to address 0x" + Integer.toHexString(address);
307    }
308    
309}
310