Skip to content

Commit 67a72cf

Browse files
committed
feat: allow reading and writing of device state
1 parent 559d320 commit 67a72cf

File tree

3 files changed

+122
-7
lines changed

3 files changed

+122
-7
lines changed

feeph/i2c/burst_handler.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
with feeph.i2c.Burst(i2c_bus=i2c_bus, i2c_adr=0x4C) as bh:
1313
value = bh.read_register(register)
1414
bh.write_register(register, value + 1)
15+
16+
with feeph.i2c.Burst(i2c_bus=i2c_bus, i2c_adr=0x70) as bh:
17+
value = bh.get_state()
18+
bh.set_state(value + 1)
1519
```
1620
"""
1721

@@ -38,6 +42,17 @@ def __init__(self, i2c_bus: busio.I2C, i2c_adr: int):
3842
else:
3943
raise ValueError(f"Provided I²C address {i2c_adr} is out of range! (allowed range: 0 ≤ x ≤ 255)")
4044

45+
# fundamentally there isn't actually much difference between accesses
46+
# to a device's register or internal state
47+
#
48+
# read from register write to register
49+
# 1. write one byte write one byte (device address)
50+
# 2. write one byte write one byte (register address)
51+
# 2. read one or more bytes write one or more bytes (register value)
52+
#
53+
# step 2. is skipped when accessing the internal state,
54+
# step 1. and 3. are kept
55+
4156
def read_register(self, register: int, byte_count: int = 1, max_tries: int = 5) -> int:
4257
"""
4358
read a single register from I²C device identified by `i2c_adr` and
@@ -97,6 +112,62 @@ def write_register(self, register: int, value: int, byte_count: int = 1, max_tri
97112
else:
98113
raise RuntimeError(f"Unable to read register 0x{register:02X} after {cur_try} attempts. Giving up.")
99114

115+
def get_state(self, byte_count: int = 1, max_tries: int = 5) -> int:
116+
"""
117+
get current state of I²C device identified by `i2c_adr` and
118+
return its contents as an integer value
119+
- may raise a RuntimeError if it was not possible to acquire
120+
the bus within allowed time
121+
- may raise a RuntimeError if there were too many errors
122+
"""
123+
_validate_inputs(register=0, value=0, byte_count=byte_count, max_tries=max_tries)
124+
if byte_count > 1:
125+
LH.warning("Multi byte reads are not implemented yet! Returning a single byte instead.")
126+
byte_count = 1
127+
for cur_try in range(1, 1 + max_tries):
128+
try:
129+
buf = bytearray(byte_count)
130+
# TODO properly handle multi byte reads
131+
self._i2c_bus.readfrom_into(address=self._i2c_adr, buffer=buf)
132+
return buf[0]
133+
except OSError as e:
134+
# [Errno 121] Remote I/O error
135+
LH.warning("[%s] Failed to read state (%i/%i): %s", __name__, cur_try, max_tries, e)
136+
time.sleep(0.001)
137+
except RuntimeError as e:
138+
LH.warning("[%s] Unable to read state (%i/%i): %s", __name__, cur_try, max_tries, e)
139+
time.sleep(0.001)
140+
else:
141+
raise RuntimeError(f"Unable to read state after {cur_try} attempts. Giving up.")
142+
143+
def set_state(self, value: int, byte_count: int = 1, max_tries: int = 3):
144+
"""
145+
set current state of I²C device identified by `i2c_adr`
146+
- may raise a RuntimeError if it was not possible to acquire
147+
the bus within allowed time
148+
- may raise a RuntimeError if there were too many errors
149+
"""
150+
_validate_inputs(register=0, value=value, byte_count=byte_count, max_tries=max_tries)
151+
if byte_count > 1:
152+
LH.warning("Multi byte writes are not implemented yet! Returning a single byte instead.")
153+
byte_count = 1
154+
for cur_try in range(1, 1 + max_tries):
155+
try:
156+
buf = bytearray(byte_count)
157+
buf[0] = value & 0xFF
158+
# TODO properly handle multi byte reads
159+
self._i2c_bus.writeto(address=self._i2c_adr, buffer=buf)
160+
return
161+
except OSError as e:
162+
# [Errno 121] Remote I/O error
163+
LH.warning("[%s] Failed to write state (%i/%i): %s", __name__, cur_try, max_tries, e)
164+
time.sleep(0.1)
165+
except RuntimeError as e:
166+
LH.warning("[%s] Unable to write state 0x%02X (%i/%i): %s", __name__, cur_try, max_tries, e)
167+
time.sleep(0.1)
168+
else:
169+
raise RuntimeError(f"Unable to write state after {cur_try} attempts. Giving up.")
170+
100171

101172
class BurstHandler:
102173
"""

feeph/i2c/emulation.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,31 @@ def try_lock(self) -> bool:
4343
def unlock(self):
4444
pass
4545

46-
def readinto(self, buffer, *, start: int = 0, end: int | None = None):
47-
# provided to ensure we will never call `I2C.readinto()`
48-
raise RuntimeError("EmulatedI2cBus.readinto() is not implemented")
49-
50-
def writeto(self, address, buffer, *, start=0, end=None):
46+
def readfrom_into(self, address, buffer, *, start=0, end=None, stop=True):
47+
"""
48+
read data from device
49+
"""
5150
i2c_device_address = address
52-
i2c_device_register = buffer[0]
53-
self._state[i2c_device_address][i2c_device_register] = buffer[1]
51+
buffer[0] = self._state[i2c_device_address]
52+
53+
def writeto(self, address: int, buffer: bytearray, *, start=0, end=None):
54+
"""
55+
write data to device or to device register
56+
"""
57+
if len(buffer) == 1:
58+
# device status
59+
i2c_device_address = address
60+
self._state[i2c_device_address] = buffer[0]
61+
else:
62+
# device register
63+
i2c_device_address = address
64+
i2c_device_register = buffer[0]
65+
self._state[i2c_device_address][i2c_device_register] = buffer[1]
5466

5567
def writeto_then_readfrom(self, address: int, buffer_out: bytearray, buffer_in: bytearray, *, out_start=0, out_end=None, in_start=0, in_end=None, stop=False):
68+
"""
69+
read data from a device register
70+
"""
5671
i2c_device_address = address
5772
i2c_device_register = buffer_out[0]
5873
buffer_in[0] = self._state[i2c_device_address][i2c_device_register]

tests/test_burst_handler.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,35 @@ def test_mixed_access(self):
132132
self.assertEqual(computed_r, expected_r)
133133
self.assertEqual(computed_w, expected_w)
134134

135+
# ---------------------------------------------------------------------
136+
137+
def test_get_state(self):
138+
state = {
139+
0x70: 0x01,
140+
}
141+
i2c_bus = sut.EmulatedI2C(state=state)
142+
# -----------------------------------------------------------------
143+
with sut.BurstHandler(i2c_bus=i2c_bus, i2c_adr=0x70) as bh:
144+
computed = bh.get_state()
145+
expected = 0x01
146+
# -----------------------------------------------------------------
147+
self.assertEqual(computed, expected)
148+
149+
def test_set_state(self):
150+
state = {
151+
0x70: 0x00,
152+
}
153+
i2c_bus = sut.EmulatedI2C(state=state)
154+
# -----------------------------------------------------------------
155+
with sut.BurstHandler(i2c_bus=i2c_bus, i2c_adr=0x70) as bh:
156+
bh.set_state(value=0x01)
157+
computed = i2c_bus._state[0x70]
158+
expected = 0x01
159+
# -----------------------------------------------------------------
160+
self.assertEqual(computed, expected)
161+
162+
# ---------------------------------------------------------------------
163+
135164
def test_no_timeout(self):
136165
state = {
137166
0x4C: {

0 commit comments

Comments
 (0)