dln2 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dln2/__init__.py +27 -0
- dln2/_core.py +505 -0
- dln2/adc.py +89 -0
- dln2/bme280.py +181 -0
- dln2/gpio.py +64 -0
- dln2/i2c.py +135 -0
- dln2/py.typed +0 -0
- dln2/spi.py +81 -0
- dln2-0.1.0.dist-info/LICENSE +21 -0
- dln2-0.1.0.dist-info/METADATA +146 -0
- dln2-0.1.0.dist-info/RECORD +13 -0
- dln2-0.1.0.dist-info/WHEEL +5 -0
- dln2-0.1.0.dist-info/top_level.txt +1 -0
dln2/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Unified DLN2 wrapper package.
|
|
4
|
+
|
|
5
|
+
Single USB connection shared by SPI, GPIO, I2C and ADC modules.
|
|
6
|
+
Eliminates echo-counter conflicts and USB contention from the
|
|
7
|
+
original four independent packages.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
from dln2 import Dln2Connection, SpiDev, GPIO, SMBus, ADC, list_devices
|
|
11
|
+
|
|
12
|
+
conn = Dln2Connection() # single USB connection
|
|
13
|
+
spi = SpiDev(conn) # all modules share conn
|
|
14
|
+
gpio = GPIO(conn)
|
|
15
|
+
i2c = SMBus(conn)
|
|
16
|
+
|
|
17
|
+
See also: dln2_wrapper.py (standalone legacy version)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from ._core import Dln2Connection, list_devices, Dln2DeviceInfo
|
|
21
|
+
from .spi import SpiDev
|
|
22
|
+
from .gpio import GPIO, \
|
|
23
|
+
GPIO_EVENT_NONE, GPIO_EVENT_CHANGE, \
|
|
24
|
+
GPIO_EVENT_LEVEL_HIGH, GPIO_EVENT_LEVEL_LOW
|
|
25
|
+
from .i2c import SMBus, i2c_msg
|
|
26
|
+
from .adc import ADC
|
|
27
|
+
from .bme280 import BME280
|
dln2/_core.py
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DLN2 unified USB connection — single client for SPI + GPIO + I2C + ADC."""
|
|
3
|
+
|
|
4
|
+
import struct
|
|
5
|
+
import time
|
|
6
|
+
import usb.core
|
|
7
|
+
import usb.util
|
|
8
|
+
|
|
9
|
+
VID = 0x1D50
|
|
10
|
+
PID = 0x6170
|
|
11
|
+
|
|
12
|
+
# ═══════════════════════════════════════════════════════════════
|
|
13
|
+
# DLN2 protocol constants
|
|
14
|
+
# ═══════════════════════════════════════════════════════════════
|
|
15
|
+
|
|
16
|
+
DLN2_MODULE_GENERIC = 0x00
|
|
17
|
+
DLN2_MODULE_GPIO = 0x01
|
|
18
|
+
DLN2_MODULE_SPI = 0x02
|
|
19
|
+
DLN2_MODULE_I2C = 0x03
|
|
20
|
+
DLN2_MODULE_ADC = 0x06
|
|
21
|
+
|
|
22
|
+
DLN2_HANDLE_CTRL = 1
|
|
23
|
+
DLN2_HANDLE_GPIO = 2
|
|
24
|
+
DLN2_HANDLE_I2C = 3
|
|
25
|
+
DLN2_HANDLE_SPI = 4
|
|
26
|
+
DLN2_HANDLE_ADC = 5
|
|
27
|
+
DLN2_HANDLE_EVENT = 0
|
|
28
|
+
|
|
29
|
+
def DLN2_CMD(cmd, module):
|
|
30
|
+
return (cmd & 0xFF) | ((module & 0xFF) << 8)
|
|
31
|
+
|
|
32
|
+
# ── SPI ───────────────────────────────────────────────────────
|
|
33
|
+
DLN2_SPI_ENABLE = DLN2_CMD(0x11, DLN2_MODULE_SPI)
|
|
34
|
+
DLN2_SPI_DISABLE = DLN2_CMD(0x12, DLN2_MODULE_SPI)
|
|
35
|
+
DLN2_SPI_SET_MODE = DLN2_CMD(0x14, DLN2_MODULE_SPI)
|
|
36
|
+
DLN2_SPI_SET_FRAME_SIZE = DLN2_CMD(0x16, DLN2_MODULE_SPI)
|
|
37
|
+
DLN2_SPI_SET_FREQUENCY = DLN2_CMD(0x18, DLN2_MODULE_SPI)
|
|
38
|
+
DLN2_SPI_READ_WRITE = DLN2_CMD(0x1A, DLN2_MODULE_SPI)
|
|
39
|
+
DLN2_SPI_READ = DLN2_CMD(0x1B, DLN2_MODULE_SPI)
|
|
40
|
+
DLN2_SPI_WRITE = DLN2_CMD(0x1C, DLN2_MODULE_SPI)
|
|
41
|
+
DLN2_SPI_GET_SS_COUNT = DLN2_CMD(0x44, DLN2_MODULE_SPI)
|
|
42
|
+
DLN2_SPI_SS_MULTI_ENABLE = DLN2_CMD(0x38, DLN2_MODULE_SPI)
|
|
43
|
+
DLN2_SPI_SS_MULTI_DISABLE = DLN2_CMD(0x39, DLN2_MODULE_SPI)
|
|
44
|
+
|
|
45
|
+
# ── GPIO ──────────────────────────────────────────────────────
|
|
46
|
+
DLN2_GPIO_GET_PIN_COUNT = DLN2_CMD(0x01, DLN2_MODULE_GPIO)
|
|
47
|
+
DLN2_GPIO_PIN_GET_VAL = DLN2_CMD(0x0B, DLN2_MODULE_GPIO)
|
|
48
|
+
DLN2_GPIO_PIN_SET_OUT_VAL = DLN2_CMD(0x0C, DLN2_MODULE_GPIO)
|
|
49
|
+
DLN2_GPIO_PIN_GET_OUT_VAL = DLN2_CMD(0x0D, DLN2_MODULE_GPIO)
|
|
50
|
+
DLN2_GPIO_CONDITION_MET_EV = DLN2_CMD(0x0F, DLN2_MODULE_GPIO)
|
|
51
|
+
DLN2_GPIO_PIN_ENABLE = DLN2_CMD(0x10, DLN2_MODULE_GPIO)
|
|
52
|
+
DLN2_GPIO_PIN_DISABLE = DLN2_CMD(0x11, DLN2_MODULE_GPIO)
|
|
53
|
+
DLN2_GPIO_PIN_SET_DIRECTION = DLN2_CMD(0x13, DLN2_MODULE_GPIO)
|
|
54
|
+
DLN2_GPIO_PIN_GET_DIRECTION = DLN2_CMD(0x14, DLN2_MODULE_GPIO)
|
|
55
|
+
DLN2_GPIO_PIN_SET_EVENT_CFG = DLN2_CMD(0x1E, DLN2_MODULE_GPIO)
|
|
56
|
+
|
|
57
|
+
GPIO_EVENT_NONE = 0
|
|
58
|
+
GPIO_EVENT_CHANGE = 1
|
|
59
|
+
GPIO_EVENT_LEVEL_HIGH = 2
|
|
60
|
+
GPIO_EVENT_LEVEL_LOW = 3
|
|
61
|
+
|
|
62
|
+
# ── I2C ───────────────────────────────────────────────────────
|
|
63
|
+
DLN2_I2C_ENABLE = DLN2_CMD(0x01, DLN2_MODULE_I2C)
|
|
64
|
+
DLN2_I2C_DISABLE = DLN2_CMD(0x02, DLN2_MODULE_I2C)
|
|
65
|
+
DLN2_I2C_WRITE = DLN2_CMD(0x06, DLN2_MODULE_I2C)
|
|
66
|
+
DLN2_I2C_READ = DLN2_CMD(0x07, DLN2_MODULE_I2C)
|
|
67
|
+
|
|
68
|
+
# ── ADC ───────────────────────────────────────────────────────
|
|
69
|
+
DLN2_ADC_GET_CHANNEL_COUNT = DLN2_CMD(0x01, DLN2_MODULE_ADC)
|
|
70
|
+
DLN2_ADC_ENABLE = DLN2_CMD(0x02, DLN2_MODULE_ADC)
|
|
71
|
+
DLN2_ADC_DISABLE = DLN2_CMD(0x03, DLN2_MODULE_ADC)
|
|
72
|
+
DLN2_ADC_CHANNEL_ENABLE = DLN2_CMD(0x05, DLN2_MODULE_ADC)
|
|
73
|
+
DLN2_ADC_CHANNEL_DISABLE = DLN2_CMD(0x06, DLN2_MODULE_ADC)
|
|
74
|
+
DLN2_ADC_SET_RESOLUTION = DLN2_CMD(0x08, DLN2_MODULE_ADC)
|
|
75
|
+
DLN2_ADC_CHANNEL_GET_VAL = DLN2_CMD(0x0A, DLN2_MODULE_ADC)
|
|
76
|
+
DLN2_ADC_CHANNEL_GET_ALL_VAL = DLN2_CMD(0x0B, DLN2_MODULE_ADC)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Dln2DeviceInfo:
|
|
80
|
+
"""Metadata for one connected DLN2 device."""
|
|
81
|
+
__slots__ = ("dev", "serial", "index")
|
|
82
|
+
|
|
83
|
+
def __init__(self, dev, serial, index):
|
|
84
|
+
self.dev = dev
|
|
85
|
+
self.serial = serial
|
|
86
|
+
self.index = index
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def list_devices():
|
|
90
|
+
"""Return list of all connected DLN2 devices."""
|
|
91
|
+
devices = []
|
|
92
|
+
for i, dev in enumerate(usb.core.find(find_all=True,
|
|
93
|
+
idVendor=VID, idProduct=PID)):
|
|
94
|
+
try:
|
|
95
|
+
serial = usb.util.get_string(dev, dev.iSerialNumber)
|
|
96
|
+
except Exception:
|
|
97
|
+
serial = ""
|
|
98
|
+
devices.append(Dln2DeviceInfo(dev=dev, serial=serial, index=i))
|
|
99
|
+
return devices
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Dln2Connection:
|
|
103
|
+
"""Unified DLN2 client — SPI + GPIO + I2C + ADC share one USB connection."""
|
|
104
|
+
|
|
105
|
+
def __init__(self, index=None, serial=None, debug=False):
|
|
106
|
+
self.debug = bool(debug)
|
|
107
|
+
self._echo = 1
|
|
108
|
+
self._event_queue = []
|
|
109
|
+
|
|
110
|
+
devices = list_devices()
|
|
111
|
+
if not devices:
|
|
112
|
+
raise ValueError(f"No DLN2 device found ({VID:04X}:{PID:04X})")
|
|
113
|
+
|
|
114
|
+
if serial is not None:
|
|
115
|
+
match = next((d for d in devices if d.serial == serial), None)
|
|
116
|
+
if not match:
|
|
117
|
+
raise ValueError(f"No DLN2 with serial {serial}")
|
|
118
|
+
self._dev = match.dev
|
|
119
|
+
elif index is not None:
|
|
120
|
+
if index >= len(devices):
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"DLN2 index {index} out of range ({len(devices)} found)")
|
|
123
|
+
self._dev = devices[index].dev
|
|
124
|
+
else:
|
|
125
|
+
self._dev = devices[0].dev
|
|
126
|
+
|
|
127
|
+
self._setup()
|
|
128
|
+
|
|
129
|
+
def _setup(self):
|
|
130
|
+
# Reset USB to clear stale state
|
|
131
|
+
try:
|
|
132
|
+
self._dev.reset()
|
|
133
|
+
except Exception:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
self._dev.set_configuration()
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
cfg = self._dev.get_active_configuration()
|
|
142
|
+
intf = cfg[(0, 0)]
|
|
143
|
+
|
|
144
|
+
ep_out = ep_in = None
|
|
145
|
+
for ep in intf.endpoints():
|
|
146
|
+
d = usb.util.endpoint_direction(ep.bEndpointAddress)
|
|
147
|
+
t = usb.util.endpoint_type(ep.bmAttributes)
|
|
148
|
+
if d == usb.util.ENDPOINT_OUT and t == usb.util.ENDPOINT_TYPE_BULK:
|
|
149
|
+
ep_out = ep
|
|
150
|
+
if d == usb.util.ENDPOINT_IN and t == usb.util.ENDPOINT_TYPE_BULK:
|
|
151
|
+
ep_in = ep
|
|
152
|
+
if not ep_out or not ep_in:
|
|
153
|
+
raise RuntimeError("No bulk endpoints found on DLN2")
|
|
154
|
+
self._ep_out = ep_out.bEndpointAddress
|
|
155
|
+
self._ep_in = ep_in.bEndpointAddress
|
|
156
|
+
self._interface = intf.bInterfaceNumber
|
|
157
|
+
|
|
158
|
+
if self._dev.is_kernel_driver_active(self._interface):
|
|
159
|
+
try:
|
|
160
|
+
self._dev.detach_kernel_driver(self._interface)
|
|
161
|
+
except Exception:
|
|
162
|
+
pass
|
|
163
|
+
usb.util.claim_interface(self._dev, self._interface)
|
|
164
|
+
|
|
165
|
+
# ─── USB raw ──────────────────────────────────────────────
|
|
166
|
+
def _send_raw(self, data: bytes):
|
|
167
|
+
self._dev.write(self._ep_out, data, timeout=2000)
|
|
168
|
+
|
|
169
|
+
def _read_raw(self, size=1024, timeout_ms=2000):
|
|
170
|
+
return bytes(self._dev.read(self._ep_in, size, timeout=timeout_ms))
|
|
171
|
+
|
|
172
|
+
# ─── Unified command dispatch ──────────────────────────
|
|
173
|
+
def send_cmd(self, cmd_id, payload=b"", handle=DLN2_HANDLE_SPI):
|
|
174
|
+
hdr_size = 8
|
|
175
|
+
size = hdr_size + len(payload)
|
|
176
|
+
pkt = struct.pack("<HHHH", size, cmd_id, self._echo & 0xFFFF,
|
|
177
|
+
handle & 0xFFFF) + payload
|
|
178
|
+
expected = self._echo & 0xFFFF
|
|
179
|
+
self._echo = (self._echo + 1) & 0xFFFF
|
|
180
|
+
|
|
181
|
+
if self.debug:
|
|
182
|
+
print(f"[DLN2] OUT echo={expected} cmd=0x{cmd_id:04X} "
|
|
183
|
+
f"handle={handle} len={len(payload)}")
|
|
184
|
+
|
|
185
|
+
self._send_raw(pkt)
|
|
186
|
+
|
|
187
|
+
# Read — retry for large payloads
|
|
188
|
+
deadline = time.monotonic() + 10.0
|
|
189
|
+
while time.monotonic() < deadline:
|
|
190
|
+
try:
|
|
191
|
+
raw = self._read_raw(1024, timeout_ms=3000)
|
|
192
|
+
except Exception:
|
|
193
|
+
time.sleep(0.05)
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
if len(raw) < 10:
|
|
197
|
+
time.sleep(0.05)
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
sz, rid, echo, hnd, result = struct.unpack("<HHHHH", raw[:10])
|
|
201
|
+
data = raw[10:sz]
|
|
202
|
+
|
|
203
|
+
if self.debug:
|
|
204
|
+
print(f" IN echo={echo} result={result} data={data[:16].hex()}")
|
|
205
|
+
|
|
206
|
+
if hnd == DLN2_HANDLE_EVENT:
|
|
207
|
+
# Queue event for GPIO.poll_event()
|
|
208
|
+
self._queue_event(raw)
|
|
209
|
+
continue # read next response
|
|
210
|
+
|
|
211
|
+
if echo != expected:
|
|
212
|
+
raise RuntimeError(
|
|
213
|
+
f"Echo mismatch: got {echo}, expected {expected}")
|
|
214
|
+
|
|
215
|
+
return {"result": result, "data": data, "echo": echo}
|
|
216
|
+
|
|
217
|
+
raise RuntimeError("Timeout waiting for DLN2 response")
|
|
218
|
+
|
|
219
|
+
def _queue_event(self, raw):
|
|
220
|
+
"""Decode and queue a GPIO event packet."""
|
|
221
|
+
if len(raw) < 16:
|
|
222
|
+
return
|
|
223
|
+
try:
|
|
224
|
+
# Event packet: size, id, echo, handle(0), result
|
|
225
|
+
# Followed by event_type(2B) + pin(2B) + ?
|
|
226
|
+
_, _, _, _, _ = struct.unpack("<HHHHH", raw[:10])
|
|
227
|
+
ev_type = struct.unpack("<H", raw[10:12])[0]
|
|
228
|
+
ev_data = raw[12:14] # variable
|
|
229
|
+
self._event_queue.append({
|
|
230
|
+
"type": ev_type,
|
|
231
|
+
"pin": ev_data[0],
|
|
232
|
+
"value": ev_data[1] if len(ev_data) > 1 else 0,
|
|
233
|
+
})
|
|
234
|
+
except Exception:
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
def close(self):
|
|
238
|
+
try:
|
|
239
|
+
usb.util.release_interface(self._dev, self._interface)
|
|
240
|
+
except Exception:
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
# ═══════════════════════════════════════════════════════════
|
|
244
|
+
# SPI raw
|
|
245
|
+
# ═══════════════════════════════════════════════════════════
|
|
246
|
+
|
|
247
|
+
def spi_configure(self, mode, freq_hz, bpw=8):
|
|
248
|
+
self.send_cmd(DLN2_SPI_SET_FRAME_SIZE,
|
|
249
|
+
struct.pack("<BB", 0, bpw),
|
|
250
|
+
handle=DLN2_HANDLE_SPI)
|
|
251
|
+
self.send_cmd(DLN2_SPI_SET_FREQUENCY,
|
|
252
|
+
struct.pack("<BI", 0, freq_hz),
|
|
253
|
+
handle=DLN2_HANDLE_SPI)
|
|
254
|
+
self.send_cmd(DLN2_SPI_SET_MODE,
|
|
255
|
+
struct.pack("<BB", 0, mode & 3),
|
|
256
|
+
handle=DLN2_HANDLE_SPI)
|
|
257
|
+
|
|
258
|
+
def spi_enable(self):
|
|
259
|
+
self.send_cmd(DLN2_SPI_ENABLE, struct.pack("<B", 0),
|
|
260
|
+
handle=DLN2_HANDLE_SPI)
|
|
261
|
+
|
|
262
|
+
def spi_disable(self):
|
|
263
|
+
self.send_cmd(DLN2_SPI_DISABLE, struct.pack("<BB", 0, 0),
|
|
264
|
+
handle=DLN2_HANDLE_SPI)
|
|
265
|
+
|
|
266
|
+
def spi_read_write(self, tx_bytes, leave_ss_low=False):
|
|
267
|
+
attr = 1 if leave_ss_low else 0
|
|
268
|
+
payload = struct.pack("<BHB", 0, len(tx_bytes) & 0xFFFF,
|
|
269
|
+
attr & 0xFF) + tx_bytes
|
|
270
|
+
resp = self.send_cmd(DLN2_SPI_READ_WRITE, payload,
|
|
271
|
+
handle=DLN2_HANDLE_SPI)
|
|
272
|
+
if resp["result"] != 0:
|
|
273
|
+
raise RuntimeError(f"SPI transfer failed: result={resp['result']}")
|
|
274
|
+
if len(resp["data"]) < 2:
|
|
275
|
+
raise RuntimeError("SPI response too short")
|
|
276
|
+
rx_len = struct.unpack("<H", resp["data"][:2])[0]
|
|
277
|
+
return resp["data"][2:2 + rx_len]
|
|
278
|
+
|
|
279
|
+
def spi_write(self, data, leave_ss_low=False):
|
|
280
|
+
attr = 1 if leave_ss_low else 0
|
|
281
|
+
for i in range(0, len(data), 128):
|
|
282
|
+
chunk = data[i:i + 128]
|
|
283
|
+
pkt = struct.pack("<BHB", 0, len(chunk), attr) + chunk
|
|
284
|
+
self.send_cmd(DLN2_SPI_WRITE, pkt, handle=DLN2_HANDLE_SPI)
|
|
285
|
+
|
|
286
|
+
# ═══════════════════════════════════════════════════════════
|
|
287
|
+
# GPIO raw
|
|
288
|
+
# ═══════════════════════════════════════════════════════════
|
|
289
|
+
|
|
290
|
+
def gpio_get_pin_count(self):
|
|
291
|
+
resp = self.send_cmd(DLN2_GPIO_GET_PIN_COUNT, b"",
|
|
292
|
+
handle=DLN2_HANDLE_GPIO)
|
|
293
|
+
if resp["result"] != 0:
|
|
294
|
+
raise RuntimeError(f"GPIO get_pin_count failed: {resp['result']}")
|
|
295
|
+
if len(resp["data"]) < 2:
|
|
296
|
+
raise RuntimeError("GPIO get_pin_count response too short")
|
|
297
|
+
return struct.unpack("<H", resp["data"][:2])[0]
|
|
298
|
+
|
|
299
|
+
def gpio_pin_enable(self, pin):
|
|
300
|
+
resp = self.send_cmd(DLN2_GPIO_PIN_ENABLE,
|
|
301
|
+
struct.pack("<H", int(pin)),
|
|
302
|
+
handle=DLN2_HANDLE_GPIO)
|
|
303
|
+
if resp["result"] != 0:
|
|
304
|
+
raise RuntimeError(f"GPIO pin_enable({pin}) failed: {resp['result']}")
|
|
305
|
+
|
|
306
|
+
def gpio_pin_disable(self, pin):
|
|
307
|
+
resp = self.send_cmd(DLN2_GPIO_PIN_DISABLE,
|
|
308
|
+
struct.pack("<H", int(pin)),
|
|
309
|
+
handle=DLN2_HANDLE_GPIO)
|
|
310
|
+
if resp["result"] != 0:
|
|
311
|
+
raise RuntimeError(f"GPIO pin_disable({pin}) failed: {resp['result']}")
|
|
312
|
+
|
|
313
|
+
def gpio_pin_set_direction(self, pin, is_output):
|
|
314
|
+
resp = self.send_cmd(DLN2_GPIO_PIN_SET_DIRECTION,
|
|
315
|
+
struct.pack("<HB", int(pin), 1 if is_output else 0),
|
|
316
|
+
handle=DLN2_HANDLE_GPIO)
|
|
317
|
+
if resp["result"] != 0:
|
|
318
|
+
raise RuntimeError(
|
|
319
|
+
f"GPIO set_direction({pin}) failed: {resp['result']}")
|
|
320
|
+
|
|
321
|
+
def gpio_pin_get_direction(self, pin):
|
|
322
|
+
resp = self.send_cmd(DLN2_GPIO_PIN_GET_DIRECTION,
|
|
323
|
+
struct.pack("<H", int(pin)),
|
|
324
|
+
handle=DLN2_HANDLE_GPIO)
|
|
325
|
+
if resp["result"] != 0:
|
|
326
|
+
raise RuntimeError(
|
|
327
|
+
f"GPIO get_direction({pin}) failed: {resp['result']}")
|
|
328
|
+
if len(resp["data"]) < 3:
|
|
329
|
+
raise RuntimeError("GPIO get_direction response too short")
|
|
330
|
+
_, direction = struct.unpack("<HB", resp["data"][:3])
|
|
331
|
+
return bool(direction)
|
|
332
|
+
|
|
333
|
+
def gpio_get(self, pin):
|
|
334
|
+
resp = self.send_cmd(DLN2_GPIO_PIN_GET_VAL,
|
|
335
|
+
struct.pack("<H", int(pin)),
|
|
336
|
+
handle=DLN2_HANDLE_GPIO)
|
|
337
|
+
if resp["result"] != 0:
|
|
338
|
+
raise RuntimeError(f"GPIO get_val({pin}) failed: {resp['result']}")
|
|
339
|
+
if len(resp["data"]) < 3:
|
|
340
|
+
raise RuntimeError("GPIO get_val response too short")
|
|
341
|
+
_, value = struct.unpack("<HB", resp["data"][:3])
|
|
342
|
+
return value
|
|
343
|
+
|
|
344
|
+
def gpio_set(self, pin, value):
|
|
345
|
+
resp = self.send_cmd(DLN2_GPIO_PIN_SET_OUT_VAL,
|
|
346
|
+
struct.pack("<HB", int(pin), 1 if value else 0),
|
|
347
|
+
handle=DLN2_HANDLE_GPIO)
|
|
348
|
+
if resp["result"] != 0:
|
|
349
|
+
raise RuntimeError(f"GPIO set_out_val({pin}) failed: {resp['result']}")
|
|
350
|
+
|
|
351
|
+
def gpio_get_out_val(self, pin):
|
|
352
|
+
resp = self.send_cmd(DLN2_GPIO_PIN_GET_OUT_VAL,
|
|
353
|
+
struct.pack("<H", int(pin)),
|
|
354
|
+
handle=DLN2_HANDLE_GPIO)
|
|
355
|
+
if resp["result"] != 0:
|
|
356
|
+
raise RuntimeError(
|
|
357
|
+
f"GPIO get_out_val({pin}) failed: {resp['result']}")
|
|
358
|
+
if len(resp["data"]) < 3:
|
|
359
|
+
raise RuntimeError("GPIO get_out_val response too short")
|
|
360
|
+
_, value = struct.unpack("<HB", resp["data"][:3])
|
|
361
|
+
return value
|
|
362
|
+
|
|
363
|
+
def gpio_set_event_cfg(self, pin, event_type, period=0):
|
|
364
|
+
resp = self.send_cmd(DLN2_GPIO_PIN_SET_EVENT_CFG,
|
|
365
|
+
struct.pack("<HBH", int(pin), int(event_type),
|
|
366
|
+
int(period)),
|
|
367
|
+
handle=DLN2_HANDLE_GPIO)
|
|
368
|
+
if resp["result"] != 0:
|
|
369
|
+
raise RuntimeError(
|
|
370
|
+
f"GPIO set_event_cfg({pin}) failed: {resp['result']}")
|
|
371
|
+
|
|
372
|
+
def gpio_poll_event(self, timeout_ms=0):
|
|
373
|
+
if self._event_queue:
|
|
374
|
+
return self._event_queue.pop(0)
|
|
375
|
+
try:
|
|
376
|
+
raw = self._read_raw(1024, timeout_ms=timeout_ms)
|
|
377
|
+
if len(raw) < 10:
|
|
378
|
+
return None
|
|
379
|
+
# Parse as event
|
|
380
|
+
self._queue_event(raw)
|
|
381
|
+
if self._event_queue:
|
|
382
|
+
return self._event_queue.pop(0)
|
|
383
|
+
except Exception:
|
|
384
|
+
pass
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
# ═══════════════════════════════════════════════════════════
|
|
388
|
+
# I2C raw
|
|
389
|
+
# ═══════════════════════════════════════════════════════════
|
|
390
|
+
|
|
391
|
+
def i2c_enable(self):
|
|
392
|
+
return self.send_cmd(DLN2_I2C_ENABLE, struct.pack("<B", 0),
|
|
393
|
+
handle=DLN2_HANDLE_I2C)
|
|
394
|
+
|
|
395
|
+
def i2c_disable(self):
|
|
396
|
+
return self.send_cmd(DLN2_I2C_DISABLE, struct.pack("<B", 0),
|
|
397
|
+
handle=DLN2_HANDLE_I2C)
|
|
398
|
+
|
|
399
|
+
def i2c_write(self, addr, data, mem_addr_len=0, mem_addr=0):
|
|
400
|
+
tx = bytes(data)
|
|
401
|
+
payload = struct.pack("<BBBIH", 0, int(addr) & 0x7F,
|
|
402
|
+
int(mem_addr_len) & 0xFF,
|
|
403
|
+
int(mem_addr) & 0xFFFFFFFF,
|
|
404
|
+
len(tx) & 0xFFFF) + tx
|
|
405
|
+
resp = self.send_cmd(DLN2_I2C_WRITE, payload,
|
|
406
|
+
handle=DLN2_HANDLE_I2C)
|
|
407
|
+
if resp["result"] != 0:
|
|
408
|
+
raise RuntimeError(f"I2C write(0x{addr:02X}) failed: {resp['result']}")
|
|
409
|
+
return resp
|
|
410
|
+
|
|
411
|
+
def i2c_read(self, addr, length, mem_addr_len=0, mem_addr=0):
|
|
412
|
+
payload = struct.pack("<BBBIH", 0, int(addr) & 0x7F,
|
|
413
|
+
int(mem_addr_len) & 0xFF,
|
|
414
|
+
int(mem_addr) & 0xFFFFFFFF,
|
|
415
|
+
int(length) & 0xFFFF)
|
|
416
|
+
resp = self.send_cmd(DLN2_I2C_READ, payload,
|
|
417
|
+
handle=DLN2_HANDLE_I2C)
|
|
418
|
+
if resp["result"] != 0:
|
|
419
|
+
raise RuntimeError(f"I2C read(0x{addr:02X}) failed: {resp['result']}")
|
|
420
|
+
if len(resp["data"]) < 2:
|
|
421
|
+
raise RuntimeError("I2C response too short")
|
|
422
|
+
rx_len = struct.unpack("<H", resp["data"][:2])[0]
|
|
423
|
+
rx = resp["data"][2:2 + rx_len]
|
|
424
|
+
if len(rx) != rx_len:
|
|
425
|
+
raise RuntimeError("Short I2C read payload")
|
|
426
|
+
return rx
|
|
427
|
+
|
|
428
|
+
# ═══════════════════════════════════════════════════════════
|
|
429
|
+
# ADC raw
|
|
430
|
+
# ═══════════════════════════════════════════════════════════
|
|
431
|
+
|
|
432
|
+
def adc_get_channel_count(self, port=0):
|
|
433
|
+
resp = self.send_cmd(DLN2_ADC_GET_CHANNEL_COUNT,
|
|
434
|
+
struct.pack("<B", port & 0xFF),
|
|
435
|
+
handle=DLN2_HANDLE_ADC)
|
|
436
|
+
if resp["result"] != 0:
|
|
437
|
+
raise RuntimeError(f"ADC get_channel_count failed: {resp['result']}")
|
|
438
|
+
if len(resp["data"]) != 1:
|
|
439
|
+
raise RuntimeError("ADC channel count response size mismatch")
|
|
440
|
+
return resp["data"][0]
|
|
441
|
+
|
|
442
|
+
def adc_enable(self, port=0):
|
|
443
|
+
resp = self.send_cmd(DLN2_ADC_ENABLE,
|
|
444
|
+
struct.pack("<B", port & 0xFF),
|
|
445
|
+
handle=DLN2_HANDLE_ADC)
|
|
446
|
+
if resp["result"] != 0:
|
|
447
|
+
raise RuntimeError(f"ADC enable failed: {resp['result']}")
|
|
448
|
+
if len(resp["data"]) < 2:
|
|
449
|
+
raise RuntimeError("ADC enable response too short")
|
|
450
|
+
return struct.unpack("<H", resp["data"][:2])[0]
|
|
451
|
+
|
|
452
|
+
def adc_disable(self, port=0):
|
|
453
|
+
resp = self.send_cmd(DLN2_ADC_DISABLE,
|
|
454
|
+
struct.pack("<B", port & 0xFF),
|
|
455
|
+
handle=DLN2_HANDLE_ADC)
|
|
456
|
+
if resp["result"] != 0:
|
|
457
|
+
raise RuntimeError(f"ADC disable failed: {resp['result']}")
|
|
458
|
+
if len(resp["data"]) < 2:
|
|
459
|
+
raise RuntimeError("ADC disable response too short")
|
|
460
|
+
return struct.unpack("<H", resp["data"][:2])[0]
|
|
461
|
+
|
|
462
|
+
def adc_channel_enable(self, channel, port=0):
|
|
463
|
+
resp = self.send_cmd(DLN2_ADC_CHANNEL_ENABLE,
|
|
464
|
+
struct.pack("<BB", port & 0xFF, int(channel) & 0xFF),
|
|
465
|
+
handle=DLN2_HANDLE_ADC)
|
|
466
|
+
if resp["result"] != 0:
|
|
467
|
+
raise RuntimeError(
|
|
468
|
+
f"ADC channel_enable({channel}) failed: {resp['result']}")
|
|
469
|
+
|
|
470
|
+
def adc_channel_disable(self, channel, port=0):
|
|
471
|
+
resp = self.send_cmd(DLN2_ADC_CHANNEL_DISABLE,
|
|
472
|
+
struct.pack("<BB", port & 0xFF, int(channel) & 0xFF),
|
|
473
|
+
handle=DLN2_HANDLE_ADC)
|
|
474
|
+
if resp["result"] != 0:
|
|
475
|
+
raise RuntimeError(
|
|
476
|
+
f"ADC channel_disable({channel}) failed: {resp['result']}")
|
|
477
|
+
|
|
478
|
+
def adc_set_resolution(self, bits, port=0):
|
|
479
|
+
resp = self.send_cmd(DLN2_ADC_SET_RESOLUTION,
|
|
480
|
+
struct.pack("<BB", port & 0xFF, int(bits) & 0xFF),
|
|
481
|
+
handle=DLN2_HANDLE_ADC)
|
|
482
|
+
if resp["result"] != 0:
|
|
483
|
+
raise RuntimeError(f"ADC set_resolution failed: {resp['result']}")
|
|
484
|
+
|
|
485
|
+
def adc_read_channel(self, channel, port=0):
|
|
486
|
+
resp = self.send_cmd(DLN2_ADC_CHANNEL_GET_VAL,
|
|
487
|
+
struct.pack("<BB", port & 0xFF, int(channel) & 0xFF),
|
|
488
|
+
handle=DLN2_HANDLE_ADC)
|
|
489
|
+
if resp["result"] != 0:
|
|
490
|
+
raise RuntimeError(f"ADC read_channel failed: {resp['result']}")
|
|
491
|
+
if len(resp["data"]) < 2:
|
|
492
|
+
raise RuntimeError("ADC read_channel response too short")
|
|
493
|
+
return struct.unpack("<H", resp["data"][:2])[0]
|
|
494
|
+
|
|
495
|
+
def adc_read_all(self, port=0):
|
|
496
|
+
resp = self.send_cmd(DLN2_ADC_CHANNEL_GET_ALL_VAL,
|
|
497
|
+
struct.pack("<B", port & 0xFF),
|
|
498
|
+
handle=DLN2_HANDLE_ADC)
|
|
499
|
+
if resp["result"] != 0:
|
|
500
|
+
raise RuntimeError(f"ADC read_all failed: {resp['result']}")
|
|
501
|
+
if len(resp["data"]) < 18:
|
|
502
|
+
raise RuntimeError("ADC read_all response too short")
|
|
503
|
+
channel_mask = struct.unpack("<H", resp["data"][:2])[0]
|
|
504
|
+
values = list(struct.unpack("<8H", resp["data"][2:18]))
|
|
505
|
+
return {"channel_mask": channel_mask, "values": values}
|
dln2/adc.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DLN2 ADC wrapper."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ADC:
|
|
6
|
+
MAX_CHANNELS = 3
|
|
7
|
+
DEFAULT_VREF = 3.3
|
|
8
|
+
DEFAULT_BITS = 10
|
|
9
|
+
DEFAULT_MAX_VALUE = (1 << DEFAULT_BITS) - 1
|
|
10
|
+
|
|
11
|
+
def __init__(self, connection=None, port=0, resolution_bits=10,
|
|
12
|
+
vref=3.3):
|
|
13
|
+
if connection is None:
|
|
14
|
+
from ._core import Dln2Connection
|
|
15
|
+
connection = Dln2Connection()
|
|
16
|
+
self._conn = connection
|
|
17
|
+
self.port = int(port)
|
|
18
|
+
self.resolution_bits = int(resolution_bits)
|
|
19
|
+
self.vref = float(vref)
|
|
20
|
+
self._enabled = False
|
|
21
|
+
|
|
22
|
+
def open(self):
|
|
23
|
+
if self._enabled:
|
|
24
|
+
return self
|
|
25
|
+
self._conn.adc_enable(self.port)
|
|
26
|
+
self._conn.adc_set_resolution(self.resolution_bits, self.port)
|
|
27
|
+
self._enabled = True
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def close(self):
|
|
31
|
+
if not self._enabled:
|
|
32
|
+
return
|
|
33
|
+
try:
|
|
34
|
+
self._conn.adc_disable(self.port)
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
self._enabled = False
|
|
38
|
+
|
|
39
|
+
def _require(self):
|
|
40
|
+
if not self._enabled:
|
|
41
|
+
self.open()
|
|
42
|
+
return self._conn
|
|
43
|
+
|
|
44
|
+
def get_channel_count(self):
|
|
45
|
+
return self._require().adc_get_channel_count(self.port)
|
|
46
|
+
|
|
47
|
+
def enable_channel(self, channel):
|
|
48
|
+
self._validate(channel)
|
|
49
|
+
self._require().adc_channel_enable(channel, self.port)
|
|
50
|
+
|
|
51
|
+
def disable_channel(self, channel):
|
|
52
|
+
self._validate(channel)
|
|
53
|
+
self._require().adc_channel_disable(channel, self.port)
|
|
54
|
+
|
|
55
|
+
def set_resolution(self, bits):
|
|
56
|
+
self.resolution_bits = int(bits)
|
|
57
|
+
self._require().adc_set_resolution(self.resolution_bits, self.port)
|
|
58
|
+
|
|
59
|
+
def read_channel(self, channel, enable=False):
|
|
60
|
+
self._validate(channel)
|
|
61
|
+
c = self._require()
|
|
62
|
+
if enable:
|
|
63
|
+
c.adc_channel_enable(channel, self.port)
|
|
64
|
+
return c.adc_read_channel(channel, self.port)
|
|
65
|
+
|
|
66
|
+
def read_all(self):
|
|
67
|
+
data = self._require().adc_read_all(self.port)
|
|
68
|
+
return {
|
|
69
|
+
"channel_mask": data["channel_mask"],
|
|
70
|
+
"values": data["values"][:self.MAX_CHANNELS],
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def read_volts(self, channel, enable=False):
|
|
74
|
+
raw = self.read_channel(channel, enable=enable)
|
|
75
|
+
return float(raw) * self.vref / self.DEFAULT_MAX_VALUE
|
|
76
|
+
|
|
77
|
+
def to_voltage(self, raw):
|
|
78
|
+
return float(raw) * self.vref / self.DEFAULT_MAX_VALUE
|
|
79
|
+
|
|
80
|
+
def _validate(self, channel):
|
|
81
|
+
channel = int(channel)
|
|
82
|
+
if channel < 0 or channel >= self.MAX_CHANNELS:
|
|
83
|
+
raise ValueError(f"ADC channel must be in range 0..{self.MAX_CHANNELS - 1}")
|
|
84
|
+
|
|
85
|
+
def __enter__(self):
|
|
86
|
+
return self.open()
|
|
87
|
+
|
|
88
|
+
def __exit__(self, *a):
|
|
89
|
+
self.close()
|
dln2/bme280.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Minimal BME280 helper built on top of the DLN2 SMBus wrapper.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import struct
|
|
7
|
+
|
|
8
|
+
from .i2c import SMBus
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BME280:
|
|
12
|
+
CHIP_ID_REGISTER = 0xD0
|
|
13
|
+
RESET_REGISTER = 0xE0
|
|
14
|
+
STATUS_REGISTER = 0xF3
|
|
15
|
+
CTRL_HUM_REGISTER = 0xF2
|
|
16
|
+
CTRL_MEAS_REGISTER = 0xF4
|
|
17
|
+
CONFIG_REGISTER = 0xF5
|
|
18
|
+
DATA_REGISTER = 0xF7
|
|
19
|
+
|
|
20
|
+
EXPECTED_CHIP_ID = 0x60
|
|
21
|
+
|
|
22
|
+
def __init__(self, bus=1, address=0x76, debug=False):
|
|
23
|
+
if isinstance(bus, SMBus):
|
|
24
|
+
self.bus = bus
|
|
25
|
+
self._owns_bus = False
|
|
26
|
+
elif hasattr(bus, 'send_cmd'): # Dln2Connection passed directly
|
|
27
|
+
self.bus = SMBus(connection=bus)
|
|
28
|
+
self._owns_bus = True
|
|
29
|
+
else:
|
|
30
|
+
self.bus = SMBus()
|
|
31
|
+
self._owns_bus = True
|
|
32
|
+
|
|
33
|
+
self.address = int(address) & 0x7F
|
|
34
|
+
self.debug = bool(debug)
|
|
35
|
+
self.calibration = None
|
|
36
|
+
self.t_fine = 0.0
|
|
37
|
+
|
|
38
|
+
def close(self):
|
|
39
|
+
if self._owns_bus:
|
|
40
|
+
self.bus.close()
|
|
41
|
+
|
|
42
|
+
def __enter__(self):
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def __exit__(self, exc_type, exc, tb):
|
|
46
|
+
self.close()
|
|
47
|
+
|
|
48
|
+
def read_chip_id(self):
|
|
49
|
+
return self.bus.read_byte_data(self.address, self.CHIP_ID_REGISTER)
|
|
50
|
+
|
|
51
|
+
def check_chip_id(self):
|
|
52
|
+
chip_id = self.read_chip_id()
|
|
53
|
+
if chip_id != self.EXPECTED_CHIP_ID:
|
|
54
|
+
raise RuntimeError(
|
|
55
|
+
f"Unexpected BME280 chip ID: 0x{chip_id:02x} at 0x{self.address:02x}"
|
|
56
|
+
)
|
|
57
|
+
return chip_id
|
|
58
|
+
|
|
59
|
+
def reset(self):
|
|
60
|
+
self.bus.write_byte_data(self.address, self.RESET_REGISTER, 0xB6)
|
|
61
|
+
|
|
62
|
+
def configure(self, osrs_t=1, osrs_p=1, osrs_h=1, mode=3, standby=5, filter_coef=0):
|
|
63
|
+
self.bus.write_byte_data(self.address, self.CTRL_HUM_REGISTER, osrs_h & 0x07)
|
|
64
|
+
ctrl_meas = ((osrs_t & 0x07) << 5) | ((osrs_p & 0x07) << 2) | (mode & 0x03)
|
|
65
|
+
config = ((standby & 0x07) << 5) | ((filter_coef & 0x07) << 2)
|
|
66
|
+
self.bus.write_byte_data(self.address, self.CONFIG_REGISTER, config)
|
|
67
|
+
self.bus.write_byte_data(self.address, self.CTRL_MEAS_REGISTER, ctrl_meas)
|
|
68
|
+
|
|
69
|
+
def read_calibration(self):
|
|
70
|
+
block1 = self.bus.read_i2c_block_data(self.address, 0x88, 26)
|
|
71
|
+
block2 = self.bus.read_i2c_block_data(self.address, 0xE1, 7)
|
|
72
|
+
|
|
73
|
+
calib1 = bytes(block1)
|
|
74
|
+
calib2 = bytes(block2)
|
|
75
|
+
|
|
76
|
+
self.calibration = {
|
|
77
|
+
"dig_T1": struct.unpack_from("<H", calib1, 0)[0],
|
|
78
|
+
"dig_T2": struct.unpack_from("<h", calib1, 2)[0],
|
|
79
|
+
"dig_T3": struct.unpack_from("<h", calib1, 4)[0],
|
|
80
|
+
"dig_P1": struct.unpack_from("<H", calib1, 6)[0],
|
|
81
|
+
"dig_P2": struct.unpack_from("<h", calib1, 8)[0],
|
|
82
|
+
"dig_P3": struct.unpack_from("<h", calib1, 10)[0],
|
|
83
|
+
"dig_P4": struct.unpack_from("<h", calib1, 12)[0],
|
|
84
|
+
"dig_P5": struct.unpack_from("<h", calib1, 14)[0],
|
|
85
|
+
"dig_P6": struct.unpack_from("<h", calib1, 16)[0],
|
|
86
|
+
"dig_P7": struct.unpack_from("<h", calib1, 18)[0],
|
|
87
|
+
"dig_P8": struct.unpack_from("<h", calib1, 20)[0],
|
|
88
|
+
"dig_P9": struct.unpack_from("<h", calib1, 22)[0],
|
|
89
|
+
"dig_H1": calib1[25],
|
|
90
|
+
"dig_H2": struct.unpack_from("<h", calib2, 0)[0],
|
|
91
|
+
"dig_H3": calib2[2],
|
|
92
|
+
"dig_H4": (calib2[3] << 4) | (calib2[4] & 0x0F),
|
|
93
|
+
"dig_H5": (calib2[5] << 4) | (calib2[4] >> 4),
|
|
94
|
+
"dig_H6": struct.unpack_from("<b", calib2, 6)[0],
|
|
95
|
+
}
|
|
96
|
+
if self.calibration["dig_H4"] & 0x800:
|
|
97
|
+
self.calibration["dig_H4"] -= 4096
|
|
98
|
+
if self.calibration["dig_H5"] & 0x800:
|
|
99
|
+
self.calibration["dig_H5"] -= 4096
|
|
100
|
+
return self.calibration
|
|
101
|
+
|
|
102
|
+
def _ensure_calibration(self):
|
|
103
|
+
if self.calibration is None:
|
|
104
|
+
self.read_calibration()
|
|
105
|
+
return self.calibration
|
|
106
|
+
|
|
107
|
+
def read_raw_data(self):
|
|
108
|
+
data = self.bus.read_i2c_block_data(self.address, self.DATA_REGISTER, 8)
|
|
109
|
+
raw_press = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
|
|
110
|
+
raw_temp = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
|
|
111
|
+
raw_hum = (data[6] << 8) | data[7]
|
|
112
|
+
return {
|
|
113
|
+
"pressure": raw_press,
|
|
114
|
+
"temperature": raw_temp,
|
|
115
|
+
"humidity": raw_hum,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def compensate_temperature(self, adc_t):
|
|
119
|
+
cal = self._ensure_calibration()
|
|
120
|
+
var1 = (adc_t / 16384.0 - cal["dig_T1"] / 1024.0) * cal["dig_T2"]
|
|
121
|
+
var2 = (
|
|
122
|
+
(adc_t / 131072.0 - cal["dig_T1"] / 8192.0)
|
|
123
|
+
* (adc_t / 131072.0 - cal["dig_T1"] / 8192.0)
|
|
124
|
+
* cal["dig_T3"]
|
|
125
|
+
)
|
|
126
|
+
self.t_fine = var1 + var2
|
|
127
|
+
return self.t_fine / 5120.0
|
|
128
|
+
|
|
129
|
+
def compensate_pressure(self, adc_p):
|
|
130
|
+
cal = self._ensure_calibration()
|
|
131
|
+
var1 = self.t_fine / 2.0 - 64000.0
|
|
132
|
+
var2 = var1 * var1 * cal["dig_P6"] / 32768.0
|
|
133
|
+
var2 = var2 + var1 * cal["dig_P5"] * 2.0
|
|
134
|
+
var2 = var2 / 4.0 + cal["dig_P4"] * 65536.0
|
|
135
|
+
var1 = (
|
|
136
|
+
cal["dig_P3"] * var1 * var1 / 524288.0 + cal["dig_P2"] * var1
|
|
137
|
+
) / 524288.0
|
|
138
|
+
var1 = (1.0 + var1 / 32768.0) * cal["dig_P1"]
|
|
139
|
+
if var1 == 0:
|
|
140
|
+
return 0.0
|
|
141
|
+
|
|
142
|
+
pressure = 1048576.0 - adc_p
|
|
143
|
+
pressure = (pressure - var2 / 4096.0) * 6250.0 / var1
|
|
144
|
+
var1 = cal["dig_P9"] * pressure * pressure / 2147483648.0
|
|
145
|
+
var2 = pressure * cal["dig_P8"] / 32768.0
|
|
146
|
+
pressure = pressure + (var1 + var2 + cal["dig_P7"]) / 16.0
|
|
147
|
+
return pressure / 100.0
|
|
148
|
+
|
|
149
|
+
def compensate_humidity(self, adc_h):
|
|
150
|
+
cal = self._ensure_calibration()
|
|
151
|
+
humidity = self.t_fine - 76800.0
|
|
152
|
+
humidity = (
|
|
153
|
+
adc_h
|
|
154
|
+
- (cal["dig_H4"] * 64.0 + cal["dig_H5"] / 16384.0 * humidity)
|
|
155
|
+
) * (
|
|
156
|
+
cal["dig_H2"]
|
|
157
|
+
/ 65536.0
|
|
158
|
+
* (
|
|
159
|
+
1.0
|
|
160
|
+
+ cal["dig_H6"] / 67108864.0 * humidity
|
|
161
|
+
* (1.0 + cal["dig_H3"] / 67108864.0 * humidity)
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
humidity = humidity * (1.0 - cal["dig_H1"] * humidity / 524288.0)
|
|
165
|
+
if humidity < 0.0:
|
|
166
|
+
return 0.0
|
|
167
|
+
if humidity > 100.0:
|
|
168
|
+
return 100.0
|
|
169
|
+
return humidity
|
|
170
|
+
|
|
171
|
+
def read_measurements(self):
|
|
172
|
+
raw = self.read_raw_data()
|
|
173
|
+
temperature_c = self.compensate_temperature(raw["temperature"])
|
|
174
|
+
pressure_hpa = self.compensate_pressure(raw["pressure"])
|
|
175
|
+
humidity_percent = self.compensate_humidity(raw["humidity"])
|
|
176
|
+
return {
|
|
177
|
+
"temperature_c": temperature_c,
|
|
178
|
+
"pressure_hpa": pressure_hpa,
|
|
179
|
+
"humidity_percent": humidity_percent,
|
|
180
|
+
"raw": raw,
|
|
181
|
+
}
|
dln2/gpio.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DLN2 GPIO wrapper."""
|
|
3
|
+
|
|
4
|
+
from ._core import GPIO_EVENT_NONE, GPIO_EVENT_CHANGE, \
|
|
5
|
+
GPIO_EVENT_LEVEL_HIGH, GPIO_EVENT_LEVEL_LOW
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GPIO:
|
|
9
|
+
DIRECTION_IN = "in"
|
|
10
|
+
DIRECTION_OUT = "out"
|
|
11
|
+
|
|
12
|
+
def __init__(self, connection=None):
|
|
13
|
+
if connection is None:
|
|
14
|
+
from ._core import Dln2Connection
|
|
15
|
+
connection = Dln2Connection()
|
|
16
|
+
self._conn = connection
|
|
17
|
+
|
|
18
|
+
def get_pin_count(self):
|
|
19
|
+
return self._conn.gpio_get_pin_count()
|
|
20
|
+
|
|
21
|
+
def enable_pin(self, pin):
|
|
22
|
+
self._conn.gpio_pin_enable(pin)
|
|
23
|
+
|
|
24
|
+
def disable_pin(self, pin):
|
|
25
|
+
self._conn.gpio_pin_disable(pin)
|
|
26
|
+
|
|
27
|
+
def set_direction(self, pin, direction):
|
|
28
|
+
if direction not in (self.DIRECTION_IN, self.DIRECTION_OUT):
|
|
29
|
+
raise ValueError("direction must be 'in' or 'out'")
|
|
30
|
+
self._conn.gpio_pin_set_direction(pin, direction == self.DIRECTION_OUT)
|
|
31
|
+
|
|
32
|
+
def get_direction(self, pin):
|
|
33
|
+
is_out = self._conn.gpio_pin_get_direction(pin)
|
|
34
|
+
return self.DIRECTION_OUT if is_out else self.DIRECTION_IN
|
|
35
|
+
|
|
36
|
+
def read(self, pin):
|
|
37
|
+
return self._conn.gpio_get(pin)
|
|
38
|
+
|
|
39
|
+
def write(self, pin, value):
|
|
40
|
+
self._conn.gpio_set(pin, value)
|
|
41
|
+
|
|
42
|
+
def toggle(self, pin):
|
|
43
|
+
v = self.read_output(pin)
|
|
44
|
+
new = 0 if v else 1
|
|
45
|
+
self.write(pin, new)
|
|
46
|
+
return new
|
|
47
|
+
|
|
48
|
+
def read_output(self, pin):
|
|
49
|
+
return self._conn.gpio_get_out_val(pin)
|
|
50
|
+
|
|
51
|
+
def set_event(self, pin, event_type=GPIO_EVENT_CHANGE, period=0):
|
|
52
|
+
self._conn.gpio_set_event_cfg(pin, event_type, period)
|
|
53
|
+
|
|
54
|
+
def clear_event(self, pin):
|
|
55
|
+
self._conn.gpio_set_event_cfg(pin, GPIO_EVENT_NONE, 0)
|
|
56
|
+
|
|
57
|
+
def poll_event(self, timeout_ms=0):
|
|
58
|
+
return self._conn.gpio_poll_event(timeout_ms)
|
|
59
|
+
|
|
60
|
+
def __enter__(self):
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def __exit__(self, *a):
|
|
64
|
+
pass
|
dln2/i2c.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DLN2 SMBus-compatible I2C wrapper."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _to_bytes(data):
|
|
6
|
+
if isinstance(data, bytes):
|
|
7
|
+
return data
|
|
8
|
+
if isinstance(data, bytearray):
|
|
9
|
+
return bytes(data)
|
|
10
|
+
return bytes(int(v) & 0xFF for v in data)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class i2c_msg:
|
|
14
|
+
I2C_M_RD = 0x0001
|
|
15
|
+
|
|
16
|
+
def __init__(self, addr, flags, data=b"", length=0):
|
|
17
|
+
self.addr = int(addr) & 0x7F
|
|
18
|
+
self.flags = int(flags)
|
|
19
|
+
self._data = bytes(data)
|
|
20
|
+
self._length = int(length)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def write(cls, addr, data):
|
|
24
|
+
payload = _to_bytes(data)
|
|
25
|
+
return cls(addr, 0, data=payload, length=len(payload))
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def read(cls, addr, length):
|
|
29
|
+
return cls(addr, cls.I2C_M_RD, length=length)
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def is_read(self):
|
|
33
|
+
return bool(self.flags & self.I2C_M_RD)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def len(self):
|
|
37
|
+
return self._length if self.is_read else len(self._data)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def buf(self):
|
|
41
|
+
return self._data
|
|
42
|
+
|
|
43
|
+
def set_data(self, data):
|
|
44
|
+
self._data = bytes(data)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SMBus:
|
|
48
|
+
"""SMBus-compatible I2C API backed by Dln2Connection."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, connection=None):
|
|
51
|
+
if connection is None:
|
|
52
|
+
from ._core import Dln2Connection
|
|
53
|
+
connection = Dln2Connection()
|
|
54
|
+
self._conn = connection
|
|
55
|
+
self._opened = False
|
|
56
|
+
|
|
57
|
+
def open(self, bus=0):
|
|
58
|
+
if self._opened:
|
|
59
|
+
return
|
|
60
|
+
self._conn.i2c_enable()
|
|
61
|
+
self._opened = True
|
|
62
|
+
|
|
63
|
+
def close(self):
|
|
64
|
+
if not self._opened:
|
|
65
|
+
return
|
|
66
|
+
try:
|
|
67
|
+
self._conn.i2c_disable()
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
self._opened = False
|
|
71
|
+
|
|
72
|
+
def _require(self):
|
|
73
|
+
if not self._opened:
|
|
74
|
+
self.open()
|
|
75
|
+
return self._conn
|
|
76
|
+
|
|
77
|
+
def enable(self, bus=0):
|
|
78
|
+
self.open(bus)
|
|
79
|
+
|
|
80
|
+
# ── SMBus API ──────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
def write_byte(self, addr, value):
|
|
83
|
+
self._require().i2c_write(addr, [value])
|
|
84
|
+
|
|
85
|
+
def write_byte_data(self, addr, cmd, value):
|
|
86
|
+
self._require().i2c_write(addr, [cmd, value])
|
|
87
|
+
|
|
88
|
+
def write_word_data(self, addr, cmd, value):
|
|
89
|
+
data = [(cmd & 0xFF), (value & 0xFF), ((value >> 8) & 0xFF)]
|
|
90
|
+
self._require().i2c_write(addr, data)
|
|
91
|
+
|
|
92
|
+
def write_block_data(self, addr, cmd, data):
|
|
93
|
+
payload = [cmd] + list(_to_bytes(data))
|
|
94
|
+
self._require().i2c_write(addr, payload)
|
|
95
|
+
|
|
96
|
+
def write_i2c_block_data(self, addr, cmd, data):
|
|
97
|
+
self.write_block_data(addr, cmd, data)
|
|
98
|
+
|
|
99
|
+
def read_byte(self, addr):
|
|
100
|
+
return self._require().i2c_read(addr, 1)[0]
|
|
101
|
+
|
|
102
|
+
def read_byte_data(self, addr, cmd):
|
|
103
|
+
return self._require().i2c_read(addr, 1, mem_addr_len=1,
|
|
104
|
+
mem_addr=cmd)[0]
|
|
105
|
+
|
|
106
|
+
def read_word_data(self, addr, cmd):
|
|
107
|
+
rx = self._require().i2c_read(addr, 2, mem_addr_len=1, mem_addr=cmd)
|
|
108
|
+
return rx[0] | (rx[1] << 8)
|
|
109
|
+
|
|
110
|
+
def read_block_data(self, addr, cmd):
|
|
111
|
+
# Read count byte first, then data
|
|
112
|
+
count = self._require().i2c_read(addr, 1, mem_addr_len=1,
|
|
113
|
+
mem_addr=cmd)[0]
|
|
114
|
+
if count == 0:
|
|
115
|
+
return []
|
|
116
|
+
return list(self._require().i2c_read(addr, count))
|
|
117
|
+
|
|
118
|
+
def read_i2c_block_data(self, addr, cmd, length=32):
|
|
119
|
+
return list(self._require().i2c_read(addr, length,
|
|
120
|
+
mem_addr_len=1, mem_addr=cmd))
|
|
121
|
+
|
|
122
|
+
# ── Raw access ─────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
def write(self, addr, data):
|
|
125
|
+
self._require().i2c_write(addr, data)
|
|
126
|
+
|
|
127
|
+
def read(self, addr, length):
|
|
128
|
+
return self._require().i2c_read(addr, length)
|
|
129
|
+
|
|
130
|
+
def __enter__(self):
|
|
131
|
+
self.open()
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def __exit__(self, *a):
|
|
135
|
+
self.close()
|
dln2/py.typed
ADDED
|
File without changes
|
dln2/spi.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DLN2 SpiDev-compatible SPI wrapper."""
|
|
3
|
+
|
|
4
|
+
import struct
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SpiDev:
|
|
9
|
+
"""spidev-compatible API backed by a shared Dln2Connection."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, connection=None):
|
|
12
|
+
if connection is None:
|
|
13
|
+
from ._core import Dln2Connection
|
|
14
|
+
connection = Dln2Connection()
|
|
15
|
+
self._conn = connection
|
|
16
|
+
self.debug = False
|
|
17
|
+
self._max_speed_hz = 1_000_000
|
|
18
|
+
self._mode = 0
|
|
19
|
+
self._bits_per_word = 8
|
|
20
|
+
self.cshigh = False
|
|
21
|
+
self.lsbfirst = False
|
|
22
|
+
self.host_hold_cs = False
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def max_speed_hz(self):
|
|
26
|
+
return self._max_speed_hz
|
|
27
|
+
|
|
28
|
+
@max_speed_hz.setter
|
|
29
|
+
def max_speed_hz(self, v):
|
|
30
|
+
self._max_speed_hz = int(v)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def mode(self):
|
|
34
|
+
return self._mode
|
|
35
|
+
|
|
36
|
+
@mode.setter
|
|
37
|
+
def mode(self, v):
|
|
38
|
+
self._mode = int(v) & 3
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def bits_per_word(self):
|
|
42
|
+
return self._bits_per_word
|
|
43
|
+
|
|
44
|
+
@bits_per_word.setter
|
|
45
|
+
def bits_per_word(self, v):
|
|
46
|
+
self._bits_per_word = int(v)
|
|
47
|
+
|
|
48
|
+
def open(self, bus=0, device=0):
|
|
49
|
+
"""No-op — connection already open. For spidev compatibility."""
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def close(self):
|
|
53
|
+
pass # connection is shared, caller manages _conn.close()
|
|
54
|
+
|
|
55
|
+
def xfer2(self, data):
|
|
56
|
+
"""Full-duplex transfer. Returns MISO data."""
|
|
57
|
+
bpw = self._bits_per_word
|
|
58
|
+
if bpw <= 8:
|
|
59
|
+
tx = bytes(b & 0xFF for b in data)
|
|
60
|
+
else:
|
|
61
|
+
out = bytearray()
|
|
62
|
+
for w in data:
|
|
63
|
+
out += struct.pack("<H", int(w) & 0xFFFF)
|
|
64
|
+
tx = bytes(out)
|
|
65
|
+
return list(self._conn.spi_read_write(
|
|
66
|
+
tx, leave_ss_low=self.host_hold_cs))
|
|
67
|
+
|
|
68
|
+
def xfer(self, data):
|
|
69
|
+
return self.xfer2(data)
|
|
70
|
+
|
|
71
|
+
def writebytes(self, data):
|
|
72
|
+
self.xfer2(data)
|
|
73
|
+
|
|
74
|
+
def readbytes(self, length):
|
|
75
|
+
return self.xfer2([0] * length)
|
|
76
|
+
|
|
77
|
+
def __enter__(self):
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
def __exit__(self, *a):
|
|
81
|
+
pass
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 IPM Group
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dln2
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unified DLN2 driver — SPI, GPIO, I2C, ADC over a single USB connection. Originally developed for the Pico USB I/O Board.
|
|
5
|
+
Home-page: https://github.com/syabyr/dln2_wrapper
|
|
6
|
+
Author: IPM Group
|
|
7
|
+
Author-email: contact@ipmgroup.dev
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: System :: Hardware
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: pyusb >=1.2
|
|
24
|
+
|
|
25
|
+
# dln2 — Unified DLN2 USB I/O Board Wrapper
|
|
26
|
+
|
|
27
|
+
Drop-in replacement for the four independent `dln2_*_wrapper` packages.
|
|
28
|
+
**All modules share a single USB connection** — no echo-counter conflicts, no USB contention.
|
|
29
|
+
|
|
30
|
+
Originally developed for the [Pico USB I/O Board](https://github.com/syabyr/pico-usb-io-board).
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install git+https://github.com/syabyr/dln2_wrapper
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires: `pyusb`
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from dln2 import Dln2Connection, SpiDev, GPIO, SMBus, ADC
|
|
44
|
+
|
|
45
|
+
conn = Dln2Connection() # one USB connection
|
|
46
|
+
|
|
47
|
+
# ── SPI ─────────────────────────────────────────────────
|
|
48
|
+
spi = SpiDev(conn)
|
|
49
|
+
spi.mode = 3
|
|
50
|
+
spi.max_speed_hz = 16_000_000
|
|
51
|
+
rx = spi.xfer2([0x9F, 0x00, 0x00, 0x00]) # JEDEC ID
|
|
52
|
+
|
|
53
|
+
# ── GPIO ────────────────────────────────────────────────
|
|
54
|
+
gpio = GPIO(conn)
|
|
55
|
+
gpio.set_direction(20, "out")
|
|
56
|
+
gpio.write(20, 1) # set pin HIGH
|
|
57
|
+
val = gpio.read(20) # read pin level
|
|
58
|
+
|
|
59
|
+
# ── I2C ─────────────────────────────────────────────────
|
|
60
|
+
i2c = SMBus(conn)
|
|
61
|
+
i2c.open()
|
|
62
|
+
devices = []
|
|
63
|
+
for addr in range(1, 128):
|
|
64
|
+
try:
|
|
65
|
+
i2c.write_byte(addr, 0)
|
|
66
|
+
devices.append(addr)
|
|
67
|
+
except: pass
|
|
68
|
+
|
|
69
|
+
# ── ADC ─────────────────────────────────────────────────
|
|
70
|
+
adc = ADC(conn, vref=3.3)
|
|
71
|
+
volts = adc.read_volts(0) # read channel 0 voltage
|
|
72
|
+
|
|
73
|
+
conn.close()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Multi-Device Support
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from dln2 import list_devices, Dln2Connection
|
|
80
|
+
|
|
81
|
+
for d in list_devices():
|
|
82
|
+
print(f"[{d.index}] serial={d.serial}")
|
|
83
|
+
|
|
84
|
+
lcd_conn = Dln2Connection(index=0) # first board — ST7789 display
|
|
85
|
+
nfc_conn = Dln2Connection(index=1) # second board — NFC reader
|
|
86
|
+
# or by exact serial:
|
|
87
|
+
# conn = Dln2Connection(serial="4250304B38353817")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Module API
|
|
91
|
+
|
|
92
|
+
### SpiDev
|
|
93
|
+
spidev-compatible API: `xfer()`, `xfer2()`, `writebytes()`, `readbytes()`,
|
|
94
|
+
`mode`, `max_speed_hz`, `bits_per_word`, `cshigh`, `lsbfirst`, `host_hold_cs`
|
|
95
|
+
|
|
96
|
+
### GPIO
|
|
97
|
+
`set_direction(pin, "in"|"out")`, `write(pin, value)`, `read(pin)`,
|
|
98
|
+
`toggle(pin)`, `get_direction(pin)`, `set_event(pin, type)`, `poll_event()`
|
|
99
|
+
|
|
100
|
+
### SMBus
|
|
101
|
+
smbus-compatible API: `write_byte()`, `read_byte_data()`,
|
|
102
|
+
`write_word_data()`, `read_word_data()`, `read_block_data()`,
|
|
103
|
+
`write_block_data()`, `write_i2c_block_data()`, `read_i2c_block_data()`
|
|
104
|
+
|
|
105
|
+
### ADC
|
|
106
|
+
`read_channel(channel)`, `read_all()`, `read_volts(channel)`,
|
|
107
|
+
`set_resolution(bits)`, `enable_channel(ch)`, `disable_channel(ch)`
|
|
108
|
+
|
|
109
|
+
### BME280
|
|
110
|
+
`read_chip_id()`, `get_temperature()`, `get_humidity()`, `get_pressure()`,
|
|
111
|
+
`get_altitude()`, `get_all()`
|
|
112
|
+
|
|
113
|
+
## Examples
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# SPI JEDEC ID read
|
|
117
|
+
python3 examples/spidev_test.py
|
|
118
|
+
|
|
119
|
+
# GPIO pin toggle (default: Pico LED)
|
|
120
|
+
python3 examples/gpio_toggle.py --pin 25
|
|
121
|
+
|
|
122
|
+
# I2C bus scan
|
|
123
|
+
python3 examples/i2c_scan.py
|
|
124
|
+
|
|
125
|
+
# ADC monitor
|
|
126
|
+
python3 examples/adc_info.py
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Why Unified?
|
|
130
|
+
|
|
131
|
+
| | Old (4 packages) | New (`dln2`) |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| USB connections | 4 separate (echo conflicts) | 1 shared |
|
|
134
|
+
| Code duplication | ~800 lines (4× Dln2Usb) | 0 lines |
|
|
135
|
+
| GPIO events steal SPI responses | Yes | No (proper event queue) |
|
|
136
|
+
| Multi-device | Manual USB enumeration | `list_devices()` |
|
|
137
|
+
| Cross-module data flow | Impossible (4 echo counters) | Trivial (same counter) |
|
|
138
|
+
|
|
139
|
+
## Related
|
|
140
|
+
|
|
141
|
+
- [pico-usb-io-board](https://github.com/syabyr/pico-usb-io-board) — RP2040 firmware
|
|
142
|
+
- [st7789 display driver](https://github.com/syabyr/dln2_wrapper) — example ST7789 + BME280 application
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
dln2/__init__.py,sha256=Zmp033q4GLszZLBp7k1rdB15rYDx_Rw2F9FzjHBErxs,817
|
|
2
|
+
dln2/_core.py,sha256=7ARFQlst4oAGVT0Yzr41j8v_aujW0eWRkOYMfLU2Bps,22198
|
|
3
|
+
dln2/adc.py,sha256=k8IQSg5Uu0x46kCQoFuVNzZEnvmQ47aISXx_Xic9tVs,2661
|
|
4
|
+
dln2/bme280.py,sha256=Ju2le83bMulC0RG5GKyjPNH5jGNmXI_ApJlsJPqafaY,6515
|
|
5
|
+
dln2/gpio.py,sha256=3zZch84enYorumuELri556Y8E-TK1QBryLX2JuW2Gss,1823
|
|
6
|
+
dln2/i2c.py,sha256=_7Saeg04Aeg_elg_ouaVroj06ra-fL2DUdEhF5mRNPY,3899
|
|
7
|
+
dln2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
dln2/spi.py,sha256=-tNn5q1NNcCde3JCed9yyg_r7JI9Z84f1A08KQb5VF8,1999
|
|
9
|
+
dln2-0.1.0.dist-info/LICENSE,sha256=R657Wi0nj-QnCHv5kUQ2_yE1mNwXLNsM5wWQwPE5cas,1066
|
|
10
|
+
dln2-0.1.0.dist-info/METADATA,sha256=FJoHLHPp7gb4I__wf9VNLczbekWBkzus8d_L4ISw7gs,4731
|
|
11
|
+
dln2-0.1.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
12
|
+
dln2-0.1.0.dist-info/top_level.txt,sha256=0_zQFVfHajItivvqoMkbM1HGutiVazNrNd6AjkKaToQ,5
|
|
13
|
+
dln2-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dln2
|