Source code for duct.protocol.i2c
"""
.. module:: i2c
:synopsis: I2C async transaction wrapper
.. moduleauthor:: Colin Alston <colin@tamvera.com>
"""
import asyncio
import glob
from contextlib import asynccontextmanager
from functools import lru_cache
from smbus2 import SMBus, i2c_msg
[docs]
@lru_cache
def list_buses() -> list[int]:
"Lists available i2c busses"
return sorted(
int(p.replace('/dev/i2c-', ''))
for p in glob.glob('/dev/i2c-*')
)
_locks: dict[int, asyncio.Lock] = {}
def _bus_lock(bus: int) -> asyncio.Lock:
if bus not in _locks:
_locks[bus] = asyncio.Lock()
return _locks[bus]
[docs]
class I2CTransaction:
"""MPL115 temperature and pressure sensor
**Arguments:**
:param smbus: Bus number (default 1)
:type smbus: int.
:param device: I2C device address
:type device: int.
"""
def __init__(self, bus: int, device: int):
self._smbus = SMBus(bus)
self._device = device
[docs]
def close(self):
"Close the transaction"
self._smbus.close()
async def _run(self, fn, *args):
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, fn, *args)
[docs]
async def write(self, data: bytes):
"Write raw data"
msg = i2c_msg.write(self._device, data)
await self._run(self._smbus.i2c_rdwr, msg)
[docs]
async def read(self, n_bytes: int) -> bytes:
"Read n_bytes"
msg = i2c_msg.read(self._device, n_bytes)
await self._run(self._smbus.i2c_rdwr, msg)
return bytes(msg)
[docs]
async def transfer(self, write_data: bytes, read_n: int) -> bytes:
"""Write then immediately read in a single combined transaction."""
w = i2c_msg.write(self._device, write_data)
r = i2c_msg.read(self._device, read_n)
await self._run(self._smbus.i2c_rdwr, w, r)
return bytes(r)
[docs]
async def read_byte(self) -> int:
"Read one byte"
return await self._run(self._smbus.read_byte, self._device)
[docs]
async def write_byte(self, value: int):
"Write one byte"
await self._run(self._smbus.write_byte, self._device, value)
[docs]
async def read_byte_data(self, register: int) -> int:
"Read a register byte"
return await self._run(self._smbus.read_byte_data, self._device, register)
[docs]
async def write_byte_data(self, register: int, value: int):
"Write a register byte"
await self._run(self._smbus.write_byte_data, self._device, register, value)
[docs]
async def read_block(self, register: int, length: int) -> list[int]:
"Read a register block"
return await self._run(self._smbus.read_i2c_block_data, self._device, register, length)
[docs]
async def write_block(self, register: int, data: list[int]):
"Write a register block"
await self._run(self._smbus.write_i2c_block_data, self._device, register, data)
[docs]
@asynccontextmanager
async def transaction(bus: int, device: int):
"""Acquire exclusive access to a bus and yield an I2CTransaction bound to device.
Callers on the same bus number are queued and served in order;
callers on different buses run concurrently.
"""
async with _bus_lock(bus):
iic = I2CTransaction(bus, device)
try:
yield iic
finally:
iic.close()