Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Checkout the libraries listed below for real-world examples.

* Classes for describing devices, registers and individual bit fields within registers in a fashion which maps closely with the datasheet
* Value translation from real world numbers (such as `512ms`) to register values (such as `0b111`) and back again
* Automatic generation of accessors for every BitField- add a `mode` field and you'll get `get_mode` and `set_mode` methods on your Register.
* Read registers into a namedtuple of fields using `get`
* Write multiple register fields in a transaction using `set` with keyword arguments
* Support for treating multiple-bytes as a single value, or single register with multiple values

# Built With i2cdevice
Expand Down Expand Up @@ -71,3 +72,25 @@ ltr559 = Device(I2C_ADDR, bit_width=8, registers=(
))
```

## Reading Registers

One configured a register's fields can be read into a namedtuple using the `get` method:

```python
register_values = ltr559.get('ALS_CONTROL')
gain = register_values.gain
sw_reset = register_values.sw_reset
mode = register_values.mode
```

## Writing Registers

The namedtuple returned from `get` is immutable and does not attempt to map values back to the hardware, in order to write one or more fields to a register you must use `set` with a keyword argument for each field:

```python
ltr559.set('ALS_CONTROL',
gain=4,
sw_reset=1)
```

This will read the register state from the device, update the bitfields accordingly and write the result back.
123 changes: 66 additions & 57 deletions examples/ltr559.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import time
import sys
sys.path.insert(0, "../library/")
from i2cdevice import Device, Register, BitField
from i2cdevice import Device, Register, BitField, MockSMBus
from i2cdevice.adapter import Adapter, LookupAdapter, U16ByteSwapAdapter

I2C_ADDR = 0x23
Expand All @@ -26,7 +26,7 @@ def _decode(self, value):
return ((value & 0xFF00) >> 8) | ((value & 0x000F) << 8)


ltr559 = Device(I2C_ADDR, bit_width=8, registers=(
ltr559 = Device(I2C_ADDR, i2c_dev=MockSMBus(0, default_registers={0x86: 0x92}), bit_width=8, registers=(

Register('ALS_CONTROL', 0x80, fields=(
BitField('gain', 0b00011100, adapter=LookupAdapter({1: 0b000, 2: 0b001, 4: 0b011, 8: 0b011, 48: 0b110, 96: 0b111})),
Expand Down Expand Up @@ -129,26 +129,28 @@ def _decode(self, value):


if __name__ == "__main__":
with ltr559.PART_ID as PART_ID:
assert PART_ID.get_part_number() == 0x09
assert PART_ID.get_revision() == 0x02
part_id = ltr559.get('PART_ID')

assert part_id.part_number == 0x09
assert part_id.revision == 0x02

print("""
Found LTR-559.
Part ID: 0x{:02x}
Revision: 0x{:02x}
""".format(
ltr559.PART_ID.get_part_number(),
ltr559.PART_ID.get_revision())
)
part_id.part_number,
part_id.revision
))

print("""
Soft Reset
""")
ltr559.ALS_CONTROL.set_sw_reset(1)
ltr559.set('ALS_CONTROL', sw_reset=1)
ltr559.set('ALS_CONTROL', sw_reset=0)
try:
while True:
status = ltr559.ALS_CONTROL.get_sw_reset()
status = ltr559.get('ALS_CONTROL').sw_reset
print("Status: {}".format(status))
if status == 0:
break
Expand All @@ -157,58 +159,69 @@ def _decode(self, value):
pass

print("Setting ALS threshold")
# Modifying the fields of this register without a "with" statement will trigger
# two successive read/modify/write operations. Use "with" to optimise these out.
ltr559.ALS_THRESHOLD.set_lower(0x0001)
ltr559.ALS_THRESHOLD.set_upper(0xFFEE)

# The `set` method can handle writes to multiple register fields
# specify each field value as a keyword argument
ltr559.set('ALS_THRESHOLD',
lower=0x0001,
upper=0xFFEE)

print("{:08x}".format(ltr559.values['ALS_THRESHOLD']))
with ltr559.ALS_THRESHOLD as ALS_THRESHOLD:
print("LOWER: ", ALS_THRESHOLD.get_lower())
assert ALS_THRESHOLD.get_lower() == 0x0001
assert ALS_THRESHOLD.get_upper() == 0xFFEE

# The `get` method returns register values as an immutable
# namedtuple, and fields will be available as properties
# You must use `set` to write a register.
als_threshold = ltr559.get('ALS_THRESHOLD')
assert als_threshold.lower == 0x0001
assert als_threshold.upper == 0xFFEE

print("Setting PS threshold")
ltr559.PS_THRESHOLD.set_lower(0)
ltr559.PS_THRESHOLD.set_upper(500)

ltr559.set('PS_THRESHOLD',
lower=0,
upper=500)

print("{:08x}".format(ltr559.values['PS_THRESHOLD']))
with ltr559.PS_THRESHOLD as PS_THRESHOLD:
assert PS_THRESHOLD.get_lower() == 0
assert PS_THRESHOLD.get_upper() == 500

ps_threshold = ltr559.get('PS_THRESHOLD')
assert ps_threshold.lower == 0
assert ps_threshold.upper == 500

print("Setting integration time and repeat rate")
ltr559.PS_MEAS_RATE.set_rate_ms(100)
ltr559.ALS_MEAS_RATE.set_integration_time_ms(50)
ltr559.ALS_MEAS_RATE.set_repeat_rate_ms(50)
with ltr559.ALS_MEAS_RATE as ALS_MEAS_RATE:
assert ALS_MEAS_RATE.get_integration_time_ms() == 50
assert ALS_MEAS_RATE.get_repeat_rate_ms() == 50
ltr559.set('PS_MEAS_RATE', rate_ms=100)
ltr559.set('ALS_MEAS_RATE',
integration_time_ms=50,
repeat_rate_ms=50)

als_meas_rate = ltr559.get('ALS_MEAS_RATE')
assert als_meas_rate.integration_time_ms == 50
assert als_meas_rate.repeat_rate_ms == 50

print("""
Activating sensor
""")

ltr559.INTERRUPT.set_mode('als+ps')
ltr559.PS_CONTROL.set_active(True)
ltr559.PS_CONTROL.set_saturation_indicator_enable(1)
ltr559.set('INTERRUPT', mode='als+ps')
ltr559.set('PS_CONTROL',
active=True,
saturation_indicator_enable=1)

with ltr559.PS_LED as PS_LED:
PS_LED.set_current_ma(50)
PS_LED.set_duty_cycle(1.0)
PS_LED.set_pulse_freq_khz(30)
PS_LED.write() # *MUST* be called to write the value when in context mode
ltr559.set('PS_LED',
current_ma=50,
duty_cycle=1.0,
pulse_freq_khz=30)

ltr559.PS_N_PULSES.set_count(1)
ltr559.set('PS_N_PULSES', count=1)

with ltr559.ALS_CONTROL as ALS_CONTROL:
ALS_CONTROL.set_mode(1)
ALS_CONTROL.set_gain(4)
ALS_CONTROL.write()
ltr559.set('ALS_CONTROL',
mode=1,
gain=4)

with ltr559.ALS_CONTROL as ALS_CONTROL:
assert ALS_CONTROL.get_mode() == 1
assert ALS_CONTROL.get_gain() == 4
als_control = ltr559.get('ALS_CONTROL')
assert als_control.mode == 1
assert als_control.gain == 4

ltr559.PS_OFFSET.set_offset(69)
ltr559.set('PS_OFFSET', offset=69)

als0 = 0
als1 = 0
Expand All @@ -220,21 +233,17 @@ def _decode(self, value):

try:
while True:
# By default any read from a register field will trigger a read from hardware
# and any write to a register field will trigger a write to hardware.
# Using the "with" statement overrides this behavior by locking the register
# value during the with context so that its value is only read once on context entry.
with ltr559.ALS_PS_STATUS as ALS_PS_STATUS:
ps_int = ALS_PS_STATUS.get_ps_interrupt() or ALS_PS_STATUS.get_ps_data()
als_int = ALS_PS_STATUS.get_als_interrupt() or ALS_PS_STATUS.get_als_data()
als_ps_status = ltr559.get('ALS_PS_STATUS')
ps_int = als_ps_status.ps_interrupt or als_ps_status.ps_data
als_int = als_ps_status.als_interrupt or als_ps_status.als_data

if ps_int:
ps0 = ltr559.PS_DATA.get_ch0()
ps0 = ltr559.get('PS_DATA').ch0

if als_int:
with ltr559.ALS_DATA as ALS_DATA:
als0 = ALS_DATA.get_ch0()
als1 = ALS_DATA.get_ch1()
als_data = ltr559.get('ALS_DATA')
als0 = als_data.ch0
als1 = als_data.ch1

ratio = 1000
if als0 + als0 > 0:
Expand Down
5 changes: 5 additions & 0 deletions library/CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
0.0.6
-----

* New API methods set and get

0.0.5
-----

Expand Down
36 changes: 33 additions & 3 deletions library/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ Features
within registers in a fashion which maps closely with the datasheet
- Value translation from real world numbers (such as ``512ms``) to
register values (such as ``0b111``) and back again
- Automatic generation of accessors for every BitField- add a ``mode``
field and you'll get ``get_mode`` and ``set_mode`` methods on your
Register.
- Read registers into a namedtuple of fields using ``get``
- Write multiple register fields in a transaction using ``set`` with
keyword arguments
- Support for treating multiple-bytes as a single value, or single
register with multiple values

Expand Down Expand Up @@ -94,6 +94,36 @@ keyword argument:
ALS_DATA
))

Reading Registers
-----------------

One configured a register's fields can be read into a namedtuple using
the ``get`` method:

.. code:: python

register_values = ltr559.get('ALS_CONTROL')
gain = register_values.gain
sw_reset = register_values.sw_reset
mode = register_values.mode

Writing Registers
-----------------

The namedtuple returned from ``get`` is immutable and does not attempt
to map values back to the hardware, in order to write one or more fields
to a register you must use ``set`` with a keyword argument for each
field:

.. code:: python

ltr559.set('ALS_CONTROL',
gain=4,
sw_reset=1)

This will read the register state from the device, update the bitfields
accordingly and write the result back.

.. |Build Status| image:: https://travis-ci.com/pimoroni/i2cdevice-python.svg?branch=master
:target: https://travis-ci.com/pimoroni/i2cdevice-python
.. |Coverage Status| image:: https://coveralls.io/repos/github/pimoroni/i2cdevice-python/badge.svg?branch=master
Expand Down
46 changes: 42 additions & 4 deletions library/i2cdevice/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
__version__ = '0.0.5'
from collections import namedtuple

__version__ = '0.0.6'


def _mask_width(value, bit_width=8):
"""Get the width of a bitwise mask
Expand Down Expand Up @@ -56,8 +59,11 @@ def _int_to_bytes(value, length, endianness='big'):


class MockSMBus:
def __init__(self, i2c_bus):
def __init__(self, i2c_bus, default_registers=None):
self.regs = [0 for _ in range(255)]
if default_registers is not None:
for index in default_registers.keys():
self.regs[index] = default_registers.get(index)

def write_i2c_block_data(self, i2c_address, register, values):
self.regs[register:register + len(values)] = values
Expand All @@ -79,7 +85,6 @@ class _RegisterProxy(object):

"""
def __init__(self, device, register):
object.__init__(self)
self.device = device
self.register = register

Expand Down Expand Up @@ -115,11 +120,14 @@ def __init__(self, name, address, fields=None, bit_width=8, read_only=False, vol
self.bit_width = bit_width
self.read_only = read_only
self.volatile = volatile
self.is_read = False
self.fields = {}

for field in fields:
self.fields[field.name] = field

self.namedtuple = namedtuple(self.name, sorted(self.fields))


class BitField():
"""Store information about a field or flag in an i2c register"""
Expand Down Expand Up @@ -171,7 +179,9 @@ def unlock_register(self, name):

def read_register(self, name):
register = self.registers[name]
self.values[register.name] = self._i2c_read(register.address, register.bit_width)
if register.volatile or not register.is_read:
self.values[register.name] = self._i2c_read(register.address, register.bit_width)
register.is_read = True
return self.values[register.name]

def write_register(self, name):
Expand All @@ -194,6 +204,34 @@ def next_address(self):
self._i2c_address = self._i2c_addresses[next_addr]
return self._i2c_address

def set(self, register, **kwargs):
"""Write one or more fields on a device register.

Accepts multiple keyword arguments, one for each field to write.

:param register: Name of register to write.

"""
self.read_register(register)
self.lock_register(register)
for field in kwargs.keys():
value = kwargs.get(field)
self.set_field(register, field, value)
self.write_register(register)
self.unlock_register(register)

def get(self, register):
"""Get a namedtuple containing register fields.

:param register: Name of register to retrieve

"""
result = {}
self.read_register(register)
for field in self.registers[register].fields:
result[field] = self.get_field(register, field)
return self.registers[register].namedtuple(**result)

def get_field(self, register, field):
register = self.registers[register]
field = register.fields[field]
Expand Down
6 changes: 4 additions & 2 deletions library/i2cdevice/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ def __init__(self, lookup_table, snap=True):
self.snap = snap

def _decode(self, value):
index = list(self.lookup_table.values()).index(value)
return list(self.lookup_table.keys())[index]
for k, v in self.lookup_table.items():
if v == value:
return k
raise ValueError("{} not in lookup table".format(value))

def _encode(self, value):
if self.snap and type(value) in [int, float]:
Expand Down
2 changes: 1 addition & 1 deletion library/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

setup(
name='i2cdevice',
version='0.0.5',
version='0.0.6',
author='Philip Howard',
author_email='phil@pimoroni.com',
description="""Python DSL for interacting with SMBus-compatible i2c devices""",
Expand Down
Loading