# Python I2C Register [![Build Status](https://travis-ci.org/Noah-Huppert/py-i2c-register.svg?branch=master)](https://travis-ci.org/Noah-Huppert/py-i2c-register) [![Test Coverage](https://codeclimate.com/github/Noah-Huppert/py-i2c-register/badges/coverage.svg)](https://codeclimate.com/github/Noah-Huppert/py-i2c-register/coverage)
Python wrapper library around the common I2C controller register pattern. 

I2C Register is a python library which aims to make communicating with registers on I2C devices dead simple. It is meant 
to directly transfer the Register Definitions pages of a data sheet into your program. 

# Table Of Contents
- [Quick Example](#quick-example)
- [Systems Overview](#systems-overview)
    - [Creating a RegisterList](#creating-a-registerlist)
    - [Defining Registers](#defining-registers)
    - [Adding RegisterSegments](#adding-registersegments)
    - [Reading from RegisterSegments](#reading-from-registersegments)
    - [Writing to RegisterSegments](#writing-to-registersegments)
- [Writting Wrapper Classes](#writing-wrapper-classes)
- [Development](#development)
    - [Running Tests](#running-tests)

# Quick Example
Take these control register definitions from a data sheet:
![Example Hardware Data Sheet Register Definitions page 1](/docs/img/example-register-defs-p1.png)
![Example Hardware Data Sheet Register Definitions page 2](/docs/img/example-register-defs-p2.png)

With the help of the I2C Register library they can easily be represented and manipulated.
```python
# Create RegisterList instance to hold registers, device's i2c address is 0x62
controls = RegisterList(0x62, i2c, {})

# Add a definition for an ACQ_COMMAND (Acquisition Command) register, address 0x00 with WRITE permissions
controls.add("ACQ_COMMAND", 0x00, Register.WRITE, {})\
    .add("ACQ_COMMAND", 0, 7, [0] * 8)  # Define the segment of bits to read with LSB index of 0 and MSB index of 7
    
# Add a definition for a STATUS register, address 0x01 with READ permissions
controls.add("STATUS", 0x01, Register.READ, {}) \
    # Define various individual Register Segments which each signify different parts of the status
    .add("PROC_ERROR_FLAG", 6, 6, [0]) \
    .add("HEALTH_FLAG", 5, 5, [0]) \
    .add("SECONDARY_RET_FLAG", 4, 4, [0]) \
    .add("INVALID_SIGNAL_FLAG", 3, 3, [0]) \
    .add("SIGNAL_OVERFLOW_FLAG", 2, 2, [0]) \
    .add("REFERENCE_OVERFLOW_FLAG", 1, 1, [0]) \
    .add("BUSY_FLAG", 0, 0, [0])
    
# Add a definition for a VELOCITY register, address 0x09 with READ permissions
controls.add("VELOCITY", 0x09, Register.READ, {})\
    .add("VELOCITY", 0, 7, [0] * 8)  # Define the segment of bits to read for velocity value with LSB index of 0 and MSB index of 7

    
# Super simple to read and write values
# Set ACQ_COMMAND Register bits to value of 0x04, then write to register
controls.set_bits_from_int("ACQ_COMMAND", "ACQ_COMMAND", 0x04, write_after=True)  

# Read STATUS register for BUSY_FLAG value and convert to an integer
busy = controls.to_int("STATUS", "BUSY_FLAG", read_first=True)

# Read VELOCITY register and convert to two's compliment integer
velocity = controls.to_twos_comp_int("VELOCITY", "VELOCITY", read_first=True)
```

# Systems Overview
The main class this library provides is the `RegisterList` class. This class manages a list of 
`Register` definitions. It also provides some useful helper methods to make performing certain common actions quick and 
easy.

## Creating a RegisterList
To create a `RegisterList` import the `register_list.RegisterList` class. Then call the constructor giving it a I2C device 
address, an [I2C Object](/docs/i2c-object.md), and any `Register`s you have already defined:
```python
from py_i2c_register.register_list import RegisterList
controls = RegisterList(0x62, i2c, {})
```
The provided I2C Device address will be used to contact the device which holds the registers over I2C. The [I2C Object](/docs/i2c-object.md) 
depends on your platform, see the [documentation](/docs/i2c-object.md) for more information. In most cases you can provide 
an empty `Register` map as well. 

## Defining Registers
After you create a `RegisterList` you must define some `Register`s to control. A `Register` is defined by a name (for 
easy programmatic access), an I2C address, and a string containing IO operation permissions. The `RegisterList` class 
provides a useful `add(reg_name, reg_addr, reg_permissions, reg_segments)` method for adding `Register`s. 
```python
from py_i2c_register.register import Register
controls.add("REGISTER_NAME", 0x00, Registers.READ + Register.WRITE, {})
```
This would define a `Register` with the name `REGISTER_NAME`, the address `0x00` and the permission to read and write to/from it.

## Adding RegisterSegments
To actually read or write to/from a `Register` you need to define at least one `RegisterSegment`. These describe how bits 
read from registers map to sub values. This could be useful if a device for example: provides a health register and each 
bit represents a different system's health. You define `RegisterSegment`s by giving a name (for easy programmatic access) 
and the index of the segment's least and most significant bits. The previously mentioned `RegisterList.add()` method 
returns the `Register` that it just created. You can then in turn use a similar helper method that `Register` provides 
called `add(seg_name, lsb_i, msb_i, default_bits)`:
```python
controls.add("HEALTH", 0x00, Registers.READ, {})\
    .add("LEFT_MOTOR_FLAG", 2, 2, [0])\
    .add("RIGHT_MOTOR_FLAG", 1, 1, [0])\
    .add("NETWORK_FLAG", 0, 0, [0])
```
This would define a `Register` named `HEALTH` at address `0x00` with read permissions. This `Register` would have 3 
`RegisterSegment`s. These 3 register segments would look at bits 0, 1, and 2 for the status of the left and right motors as 
well as some made up network module.

## Reading from RegisterSegments
The `RegisterList` provides some useful helper methods for reading `RegisterSegment`s as integer values. They are called 
`to_int` and `to_twos_comp_int`. They both take the name of a `Register` and `RegisterSegment` to read. Optionally you can 
pass a `read_first` value. When `True` these methods will read the `Register` off the I2C device before returning the 
`RegisterSegment` value:
```python
network_status = controls.to_int("HEALTH", "NETWORK_FLAG", read_first=True)
velocity = controls.to_twos_comp_int("VELOCITY", "VELOCITY", read_first=True)
```
This would read the `NETWORK_FLAG` segment of the `HEALTH` register and the `VELOCITY` segment of the `VELOCITY` register. 

Ontop of using `RegisterList`s helper methods one can access raw `RegisterSegment` values via the `RegisterSegment.bits` 
array. This array contains the raw `0` or `1` values of the register. Just be sure to call `Register.read` before accessing 
the `RegisterSegment.bits` array:
```python
controls.get("VELOCITY").read()
velocity_bits = controls.get("VELOCITY").get("VELOCITY").bits
```

## Writing to RegisterSegments
The `RegisterList` class provides the `set_bits` and `set_bits_from_int` helper methods. Similar to the reading helper 
methods mentioned above `set_bits` and `set_bits_from_int` both also take a `Register` and `RegisterSegment` name as 
their first two parameters. The third value of both functions is the value to set. In the case of the `set_bits` method 
it is expected to be an array of bits to set. In the case of the `set_bits_from_int` method it is expected to be an integer 
value to set. The `set_bits` and `set_bits_from_int` methods also offer an optional `write_after` flag. If `True` they will 
write the value of the `Register` to the I2C device after the value has been set. 
```python
controls.set_bits("ACQ_COMMAND", "ACQ_COMMAND", [0, 0, 0, 0, 0, 1, 0, 0], write_after=True)  
controls.set_bits_from_int("ACQ_COMMAND", "ACQ_COMMAND", 0x04, write_after=True)  
```
This would set the `ACQ_COMMAND` segment of the `ACQ_COMMAND` register to the value `0x04` using the `set_bits` and 
`set_bits_from_int` methods.

# Writing Wrapper Classes
I2C Register's simple architecture lends itself well to being used in hardware wrapper classes. All one must do is 
create a class with its own `RegisterList` instance. Then add `Register` and `RegisterSegment` definitions in the `__init__()` 
method:
```python
from py_i2c_register.register_list import RegisterList
from py_i2c_register.register import Register

class LidarLiteV3():
    # Register and Segment name constants
    REG_ACQ_COMMAND = "ACQ_COMMAND"
    SEG_ACQ_COMMAND = REG_ACQ_COMMAND

    REG_STATUS = "STATUS"
    SEG_PROC_ERROR_FLAG = "PROC_ERROR_FLAG"
    SEG_HEALTH_FLAG = "HEALTH_FLAG"
    SEG_SECONDARY_RET_FLAG = "SECONDARY_RET_FLAG"
    SEG_INVALID_SIGNAL_FLAG = "INVALID_SIGNAL_FLAG"
    SEG_SIGNAL_OVERFLOW_FLAG = "SIGNAL_OVERFLOW_FLAG"
    SEG_REFERENCE_OVERFLOW_FLAG = "REFERENCE_OVERFLOW_FLAG"
    SEG_BUSY_FLAG = "BUSY_FLAG"

    REG_VELOCITY = "VELOCITY"
    SEG_VELOCITY= REG_VELOCITY
    
    REG_DISTANCE = "DISTANCE"
    SEG_DISTANCE = REG_DISTANCE
    
    def __init__(self):
        # Create some device specific I2C Object
        self.i2c = ...
        
        # Configure control registers
        self.controls = RegisterList(0x62, self.i2c, {})
        self.controls.add(LightLiteV3.REG_ACQ_COMMAND, 0x00, Register.WRITE, {}) \
            .add(LightLiteV3.SEG_ACQ_COMMAND, 0, 7, [0] * 8)

        self.controls.add(LightLiteV3.REG_STATUS, 0x01, Register.READ, {}) \
            .add(LightLiteV3.SEG_PROC_ERROR_FLAG, 6, 6, [0]) \
            .add(LightLiteV3.SEG_HEALTH_FLAG, 5, 5, [0]) \
            .add(LightLiteV3.SEG_SECONDARY_RET_FLAG, 4, 4, [0]) \
            .add(LightLiteV3.SEG_INVALID_SIGNAL_FLAG, 3, 3, [0]) \
            .add(LightLiteV3.SEG_SIGNAL_OVERFLOW_FLAG, 2, 2, [0]) \
            .add(LightLiteV3.SEG_REFERENCE_OVERFLOW_FLAG, 1, 1, [0]) \
            .add(LightLiteV3.SEG_BUSY_FLAG, 0, 0, [0])

        self.controls.add(LightLiteV3.REG_VELOCITY, 0x09, Register.READ, {})\
            .add(LightLiteV3.SEG_VELOCITY, 0, 7, [0] * 8)

        self.controls.add(LightLiteV3.REG_DISTANCE, 0x8f, Register.READ, {})\
            .add(LightLiteV3.SEG_DISTANCE, 0, 15, [0] * 16)
            
    # Provide useful helper methods
    def measure(self):
        self.controls.set_bits_from_int(LidarLiteV3.REG_ACQ_COMMAND, LidarLiteV3.SEG_ACQ_COMMAND, 0x04, write_after=True)
         
    def distance(self):
        return self.controls.to_int(LidarLiteV3.REG_DISTANCE, LidarLiteV3.SEG_DISTANCE, read_first=True)
    
    def velocity(self):
        return self.controls.to_int(LidarLiteV3.REG_VELOCITY, LidarLiteV3.SEG_VELOCITY, read_first=True)

# Now using your hardware has never been easier
lidar = LidarLiteV3()

while True:
    lidar.measure()
    print("Car is going {} m/s when it was {} m away".format(lidar.velocity(), lidar.distance()))
```     

# Development
The code for I2C Register is located in the `py_i2c_register` directory. Feel free to contribute by opening a pull 
request. I try to test and document as much as I can.

Supported Python Versions: 2.7, 3.6

## Running Tests
To run tests a couple python packages are required. To install them you can run the `test-install` Make target:
```bash
make test-install
```

You can then run test by executing the `test` Make target:
```bash
make test
```

To see a more detailed HTML report you can run the `test-html` Make target. The results will then be saved to `htmlcov/index.html`.
