import os
import fcntl
import struct
import time
import atexit
import threading

ADS111x_ADDRESS = 0x48
I2C_SLAVE = 0x0703


class __I2CDev(object):
    def __init__(self):
        self._os_close = os.close
        # Initialize i2c interface and register it for closing on exit.
        self._dev = os.open("/dev/i2c-1", os.O_SYNC | os.O_RDWR)
        if self._dev < 0:
            raise ImportError("i2c device not found")
        else:
            if fcntl.ioctl(self._dev, I2C_SLAVE, ADS111x_ADDRESS) < 0:
                self._close()
                raise ImportError("Failed to set up i2c address")
            else:
                atexit.register(self._close)

    def _close(self):
        self._os_close(self._dev)

    def write(self, data):
        os.write(self._dev, data)

    def read(self, n):
        return os.read(self._dev, n)

i2c = __I2CDev()

# mutex for multi threading requests
lock = threading.Lock()


def measure(channel):
    """
    Measure voltage on chip input.
    Raises OSError(Errno 121) "Remote I/O error" on reading error.
    Thread safe.
    :param channel: chip channel to use.
    :return: Voltage in Volts.
    """
    global i2c
    if channel < 0 or channel > 3:
        raise ValueError("Wrong channel")
    lock.acquire()
    # configure
    data = struct.pack(">BH",
                       0x01,  # config register
                       # single shot mode, +-4.096V, AINN = GND
                       ((0b100 | channel) << 12) | 0x8380
                       )
    i2c.write(data)
    # wait for conversion
    while True:
        i2c.write(struct.pack("B", 0x01))
        if struct.unpack(">H", i2c.read(2))[0] & 0x8000 != 0:
            break
        time.sleep(0.0001)
    # read result
    i2c.write(struct.pack("B", 0x00))  # conversion register
    v = struct.unpack(">h", i2c.read(2))[0]
    lock.release()
    return v / 8000.0  # / 32768.0 * 4.096 according to specified range


# check if ads111x is connected
try:
    measure(0)
except OSError:
    raise ImportError("ads111x is not connected")


# for test purpose
if __name__ == "__main__":
    while True:
        for i in range(0, 4):
            print(str(i), measure(i))
        print("-----------------------------")
        time.sleep(0.5)