python-esp-bridge 0.0.2__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.
- espbridge/__init__.py +45 -0
- espbridge/analog.py +64 -0
- espbridge/ble.py +223 -0
- espbridge/bridge.py +495 -0
- espbridge/cli.py +80 -0
- espbridge/compat/__init__.py +0 -0
- espbridge/compat/blinka.py +209 -0
- espbridge/compat/gpiozero.py +233 -0
- espbridge/compat/luma.py +90 -0
- espbridge/compat/rpi_gpio.py +121 -0
- espbridge/compat/smbus.py +63 -0
- espbridge/constants.py +199 -0
- espbridge/errors.py +38 -0
- espbridge/gpio.py +72 -0
- espbridge/i2c.py +65 -0
- espbridge/net.py +279 -0
- espbridge/oled.py +182 -0
- espbridge/protocol.py +159 -0
- espbridge/pwm.py +41 -0
- espbridge/spi.py +43 -0
- espbridge/transport.py +121 -0
- espbridge/uart.py +111 -0
- espbridge/wifi.py +128 -0
- python_esp_bridge-0.0.2.dist-info/METADATA +36 -0
- python_esp_bridge-0.0.2.dist-info/RECORD +27 -0
- python_esp_bridge-0.0.2.dist-info/WHEEL +4 -0
- python_esp_bridge-0.0.2.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""smbus2-compatible SMBus, so classic Raspberry Pi I2C code runs unchanged.
|
|
2
|
+
|
|
3
|
+
from espbridge import Bridge
|
|
4
|
+
from espbridge.compat.smbus import SMBus
|
|
5
|
+
|
|
6
|
+
with Bridge() as esp:
|
|
7
|
+
bus = SMBus(esp) # instead of smbus2.SMBus(1)
|
|
8
|
+
temp = bus.read_byte_data(0x48, 0x00) # the rest of the code is identical
|
|
9
|
+
bus.write_i2c_block_data(0x20, 0x06, [0xFF, 0x00])
|
|
10
|
+
|
|
11
|
+
Word data is little-endian, matching the SMBus specification (and smbus2).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SMBus:
|
|
17
|
+
def __init__(self, esp, *, sda: int = 21, scl: int = 22,
|
|
18
|
+
freq: int = 100_000, bus: int = 0, init: bool = True):
|
|
19
|
+
self._i2c = esp.i2c
|
|
20
|
+
self._bus = bus
|
|
21
|
+
if init:
|
|
22
|
+
self._i2c.init(sda=sda, scl=scl, freq=freq, bus=bus)
|
|
23
|
+
|
|
24
|
+
# ---- plain byte ----------------------------------------------------------
|
|
25
|
+
def read_byte(self, addr: int) -> int:
|
|
26
|
+
return self._i2c.read(addr, 1, self._bus)[0]
|
|
27
|
+
|
|
28
|
+
def write_byte(self, addr: int, value: int) -> None:
|
|
29
|
+
self._i2c.write(addr, bytes([value & 0xFF]), self._bus)
|
|
30
|
+
|
|
31
|
+
def write_quick(self, addr: int) -> None:
|
|
32
|
+
self._i2c.write(addr, b"", self._bus)
|
|
33
|
+
|
|
34
|
+
# ---- register (command) based -----------------------------------------------
|
|
35
|
+
def read_byte_data(self, addr: int, register: int) -> int:
|
|
36
|
+
return self._i2c.write_read(addr, bytes([register]), 1, self._bus)[0]
|
|
37
|
+
|
|
38
|
+
def write_byte_data(self, addr: int, register: int, value: int) -> None:
|
|
39
|
+
self._i2c.write(addr, bytes([register, value & 0xFF]), self._bus)
|
|
40
|
+
|
|
41
|
+
def read_word_data(self, addr: int, register: int) -> int:
|
|
42
|
+
lo, hi = self._i2c.write_read(addr, bytes([register]), 2, self._bus)
|
|
43
|
+
return lo | (hi << 8)
|
|
44
|
+
|
|
45
|
+
def write_word_data(self, addr: int, register: int, value: int) -> None:
|
|
46
|
+
self._i2c.write(addr, bytes([register, value & 0xFF, (value >> 8) & 0xFF]),
|
|
47
|
+
self._bus)
|
|
48
|
+
|
|
49
|
+
def read_i2c_block_data(self, addr: int, register: int, length: int = 32) -> list[int]:
|
|
50
|
+
return list(self._i2c.write_read(addr, bytes([register]), length, self._bus))
|
|
51
|
+
|
|
52
|
+
def write_i2c_block_data(self, addr: int, register: int, data) -> None:
|
|
53
|
+
self._i2c.write(addr, bytes([register]) + bytes(data), self._bus)
|
|
54
|
+
|
|
55
|
+
# ---- lifecycle -------------------------------------------------------------
|
|
56
|
+
def close(self) -> None:
|
|
57
|
+
pass # the Bridge owns the link
|
|
58
|
+
|
|
59
|
+
def __enter__(self):
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def __exit__(self, *exc):
|
|
63
|
+
self.close()
|
espbridge/constants.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""python-esp-bridge — shared protocol contract.
|
|
2
|
+
|
|
3
|
+
MUST stay in sync with esp/src/espbridge/commands.h.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import enum
|
|
8
|
+
|
|
9
|
+
PROTOCOL_VERSION = 1
|
|
10
|
+
|
|
11
|
+
# Frame (logical, pre-COBS):
|
|
12
|
+
# flags u8 | seq u8 | cmd u16 BE | payload .. | crc16 BE
|
|
13
|
+
# COBS-encoded on the wire, frames delimited by 0x00.
|
|
14
|
+
FLAG_EVENT = 0x01
|
|
15
|
+
FLAG_ERROR = 0x02
|
|
16
|
+
FLAG_MORE = 0x04
|
|
17
|
+
|
|
18
|
+
MAX_PAYLOAD = 2048
|
|
19
|
+
NET_CHUNK = 512
|
|
20
|
+
UART_CHUNK = 256
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Status(enum.IntEnum):
|
|
24
|
+
OK = 0x00
|
|
25
|
+
UNKNOWN_CMD = 0x01
|
|
26
|
+
BAD_ARGS = 0x02
|
|
27
|
+
UNSUPPORTED = 0x03
|
|
28
|
+
BUSY = 0x04
|
|
29
|
+
TIMEOUT = 0x05
|
|
30
|
+
NO_MEM = 0x06
|
|
31
|
+
BAD_PIN = 0x07
|
|
32
|
+
NOT_INIT = 0x08
|
|
33
|
+
IO = 0x09
|
|
34
|
+
WIFI = 0x0A
|
|
35
|
+
SOCKET = 0x0B
|
|
36
|
+
CRC = 0x0C
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Cap(enum.IntFlag):
|
|
40
|
+
WIFI = 1 << 0
|
|
41
|
+
BLE = 1 << 1
|
|
42
|
+
BT_CLASSIC = 1 << 2
|
|
43
|
+
DAC = 1 << 3
|
|
44
|
+
TOUCH = 1 << 4
|
|
45
|
+
HALL = 1 << 5
|
|
46
|
+
PSRAM = 1 << 6
|
|
47
|
+
NATIVE_USB = 1 << 7
|
|
48
|
+
BLE_FW = 1 << 8
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ChipModel(enum.IntEnum):
|
|
52
|
+
UNKNOWN = 0
|
|
53
|
+
ESP32 = 1
|
|
54
|
+
ESP32S2 = 2
|
|
55
|
+
ESP32S3 = 3
|
|
56
|
+
ESP32C3 = 4
|
|
57
|
+
ESP32C6 = 5
|
|
58
|
+
ESP32H2 = 6
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _cmd(mod: int, op: int) -> int:
|
|
62
|
+
return (mod << 8) | op
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
MOD_SYS = 0x00
|
|
66
|
+
MOD_GPIO = 0x10
|
|
67
|
+
MOD_ADC = 0x20
|
|
68
|
+
MOD_DAC = 0x21
|
|
69
|
+
MOD_TOUCH = 0x22
|
|
70
|
+
MOD_PWM = 0x30
|
|
71
|
+
MOD_I2C = 0x40
|
|
72
|
+
MOD_SPI = 0x41
|
|
73
|
+
MOD_UART = 0x42
|
|
74
|
+
MOD_WIFI = 0x50
|
|
75
|
+
MOD_NET = 0x51
|
|
76
|
+
MOD_BLE = 0x60
|
|
77
|
+
|
|
78
|
+
# SYS
|
|
79
|
+
SYS_PING = _cmd(MOD_SYS, 0x01)
|
|
80
|
+
SYS_INFO = _cmd(MOD_SYS, 0x02)
|
|
81
|
+
SYS_SET_BAUD = _cmd(MOD_SYS, 0x03)
|
|
82
|
+
SYS_RESET = _cmd(MOD_SYS, 0x04)
|
|
83
|
+
SYS_FREE_HEAP = _cmd(MOD_SYS, 0x05)
|
|
84
|
+
SYS_SET_NAME = _cmd(MOD_SYS, 0x06)
|
|
85
|
+
SYS_READY = _cmd(MOD_SYS, 0x80)
|
|
86
|
+
SYS_LOG = _cmd(MOD_SYS, 0x81)
|
|
87
|
+
|
|
88
|
+
BRIDGE_NAME_MAX = 32
|
|
89
|
+
|
|
90
|
+
# GPIO
|
|
91
|
+
GPIO_SET_MODE = _cmd(MOD_GPIO, 0x01)
|
|
92
|
+
GPIO_WRITE = _cmd(MOD_GPIO, 0x02)
|
|
93
|
+
GPIO_READ = _cmd(MOD_GPIO, 0x03)
|
|
94
|
+
GPIO_WRITE_MASK = _cmd(MOD_GPIO, 0x04)
|
|
95
|
+
GPIO_READ_ALL = _cmd(MOD_GPIO, 0x05)
|
|
96
|
+
GPIO_WATCH = _cmd(MOD_GPIO, 0x06)
|
|
97
|
+
GPIO_UNWATCH = _cmd(MOD_GPIO, 0x07)
|
|
98
|
+
GPIO_EDGE_EVT = _cmd(MOD_GPIO, 0x80)
|
|
99
|
+
|
|
100
|
+
# ADC / DAC / TOUCH
|
|
101
|
+
ADC_CONFIG = _cmd(MOD_ADC, 0x01)
|
|
102
|
+
ADC_READ = _cmd(MOD_ADC, 0x02)
|
|
103
|
+
ADC_READ_MV = _cmd(MOD_ADC, 0x03)
|
|
104
|
+
DAC_WRITE = _cmd(MOD_DAC, 0x01)
|
|
105
|
+
DAC_COSINE = _cmd(MOD_DAC, 0x02)
|
|
106
|
+
DAC_COS_STOP = _cmd(MOD_DAC, 0x03)
|
|
107
|
+
DAC_DISABLE = _cmd(MOD_DAC, 0x04)
|
|
108
|
+
TOUCH_READ = _cmd(MOD_TOUCH, 0x01)
|
|
109
|
+
|
|
110
|
+
# PWM
|
|
111
|
+
PWM_ATTACH = _cmd(MOD_PWM, 0x01)
|
|
112
|
+
PWM_WRITE = _cmd(MOD_PWM, 0x02)
|
|
113
|
+
PWM_DETACH = _cmd(MOD_PWM, 0x03)
|
|
114
|
+
PWM_TONE = _cmd(MOD_PWM, 0x04)
|
|
115
|
+
|
|
116
|
+
# I2C
|
|
117
|
+
I2C_INIT = _cmd(MOD_I2C, 0x01)
|
|
118
|
+
I2C_SCAN = _cmd(MOD_I2C, 0x02)
|
|
119
|
+
I2C_WRITE = _cmd(MOD_I2C, 0x03)
|
|
120
|
+
I2C_READ = _cmd(MOD_I2C, 0x04)
|
|
121
|
+
I2C_WRITE_READ = _cmd(MOD_I2C, 0x05)
|
|
122
|
+
I2C_DEINIT = _cmd(MOD_I2C, 0x06)
|
|
123
|
+
|
|
124
|
+
# SPI
|
|
125
|
+
SPI_INIT = _cmd(MOD_SPI, 0x01)
|
|
126
|
+
SPI_TRANSFER = _cmd(MOD_SPI, 0x02)
|
|
127
|
+
SPI_DEINIT = _cmd(MOD_SPI, 0x03)
|
|
128
|
+
|
|
129
|
+
# UART
|
|
130
|
+
UART_INIT = _cmd(MOD_UART, 0x01)
|
|
131
|
+
UART_WRITE = _cmd(MOD_UART, 0x02)
|
|
132
|
+
UART_DEINIT = _cmd(MOD_UART, 0x03)
|
|
133
|
+
UART_RX_EVT = _cmd(MOD_UART, 0x80)
|
|
134
|
+
|
|
135
|
+
# WIFI
|
|
136
|
+
WIFI_SCAN = _cmd(MOD_WIFI, 0x01)
|
|
137
|
+
WIFI_CONNECT = _cmd(MOD_WIFI, 0x02)
|
|
138
|
+
WIFI_DISCONNECT = _cmd(MOD_WIFI, 0x03)
|
|
139
|
+
WIFI_STATUS = _cmd(MOD_WIFI, 0x04)
|
|
140
|
+
WIFI_AP_START = _cmd(MOD_WIFI, 0x05)
|
|
141
|
+
WIFI_AP_STOP = _cmd(MOD_WIFI, 0x06)
|
|
142
|
+
WIFI_HOSTNAME = _cmd(MOD_WIFI, 0x07)
|
|
143
|
+
WIFI_STATE_EVT = _cmd(MOD_WIFI, 0x80)
|
|
144
|
+
WIFI_SCAN_RES = _cmd(MOD_WIFI, 0x81)
|
|
145
|
+
WIFI_SCAN_DONE = _cmd(MOD_WIFI, 0x82)
|
|
146
|
+
|
|
147
|
+
# NET
|
|
148
|
+
NET_TCP_CONNECT = _cmd(MOD_NET, 0x01)
|
|
149
|
+
NET_TCP_LISTEN = _cmd(MOD_NET, 0x02)
|
|
150
|
+
NET_UDP_OPEN = _cmd(MOD_NET, 0x03)
|
|
151
|
+
NET_SEND = _cmd(MOD_NET, 0x04)
|
|
152
|
+
NET_SEND_TO = _cmd(MOD_NET, 0x05)
|
|
153
|
+
NET_CLOSE = _cmd(MOD_NET, 0x06)
|
|
154
|
+
NET_WINDOW_ACK = _cmd(MOD_NET, 0x07)
|
|
155
|
+
NET_DATA_EVT = _cmd(MOD_NET, 0x80)
|
|
156
|
+
NET_ACCEPT_EVT = _cmd(MOD_NET, 0x81)
|
|
157
|
+
NET_CLOSED_EVT = _cmd(MOD_NET, 0x82)
|
|
158
|
+
NET_UDP_EVT = _cmd(MOD_NET, 0x83)
|
|
159
|
+
|
|
160
|
+
# BLE
|
|
161
|
+
BLE_SCAN_START = _cmd(MOD_BLE, 0x01)
|
|
162
|
+
BLE_SCAN_STOP = _cmd(MOD_BLE, 0x02)
|
|
163
|
+
BLE_ADV_START = _cmd(MOD_BLE, 0x03)
|
|
164
|
+
BLE_ADV_STOP = _cmd(MOD_BLE, 0x04)
|
|
165
|
+
BLE_GATTS_DEF = _cmd(MOD_BLE, 0x05)
|
|
166
|
+
BLE_GATTS_SET = _cmd(MOD_BLE, 0x06)
|
|
167
|
+
BLE_GATTS_NTFY = _cmd(MOD_BLE, 0x07)
|
|
168
|
+
BLE_GATTC_CONN = _cmd(MOD_BLE, 0x08)
|
|
169
|
+
BLE_GATTC_DISC = _cmd(MOD_BLE, 0x09)
|
|
170
|
+
BLE_GATTC_READ = _cmd(MOD_BLE, 0x0A)
|
|
171
|
+
BLE_GATTC_WRITE = _cmd(MOD_BLE, 0x0B)
|
|
172
|
+
BLE_GATTC_SUB = _cmd(MOD_BLE, 0x0C)
|
|
173
|
+
BLE_ADV_EVT = _cmd(MOD_BLE, 0x80)
|
|
174
|
+
BLE_GATTS_WR_EVT = _cmd(MOD_BLE, 0x81)
|
|
175
|
+
BLE_GATTS_CONN_EVT = _cmd(MOD_BLE, 0x82)
|
|
176
|
+
BLE_GATTC_NTFY_EVT = _cmd(MOD_BLE, 0x83)
|
|
177
|
+
BLE_GATTC_DISC_EVT = _cmd(MOD_BLE, 0x84)
|
|
178
|
+
|
|
179
|
+
GATT_PROP_READ = 0x01
|
|
180
|
+
GATT_PROP_WRITE = 0x02
|
|
181
|
+
GATT_PROP_NOTIFY = 0x04
|
|
182
|
+
GATT_PROP_WRITE_NR = 0x08
|
|
183
|
+
|
|
184
|
+
# USB-UART bridge chips found on ESP32 dev boards: (vid, pid) -> chip
|
|
185
|
+
KNOWN_USB_IDS = {
|
|
186
|
+
(0x10C4, 0xEA60): "cp210x", # CP2102/CP2104 (most DevKitC)
|
|
187
|
+
(0x1A86, 0x7523): "ch340", # CH340
|
|
188
|
+
(0x1A86, 0x55D4): "ch9102", # CH9102 (CH340 successor)
|
|
189
|
+
(0x303A, None): "native", # Espressif native USB (S2/S3/C3/...)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Safe upgraded baud per bridge chip (conservative defaults; CH340 can do 2M).
|
|
193
|
+
UPGRADE_BAUD = {
|
|
194
|
+
"cp210x": 921600,
|
|
195
|
+
"ch340": 921600,
|
|
196
|
+
"ch9102": 921600,
|
|
197
|
+
"native": None, # USB CDC ignores baud
|
|
198
|
+
None: 921600,
|
|
199
|
+
}
|
espbridge/errors.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Exception hierarchy for espbridge."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from .constants import Status
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BridgeError(Exception):
|
|
8
|
+
"""Base class for all espbridge errors."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BridgeTimeoutError(BridgeError, TimeoutError):
|
|
12
|
+
"""The firmware did not answer in time."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProtocolError(BridgeError):
|
|
16
|
+
"""Malformed frame, CRC mismatch, or protocol version mismatch."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NoDeviceError(BridgeError):
|
|
20
|
+
"""No (or ambiguous) ESP32 bridge serial port found."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class UnsupportedError(BridgeError):
|
|
24
|
+
"""The connected chip lacks this capability (e.g. DAC on ESP32-S3)."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RemoteError(BridgeError):
|
|
28
|
+
"""The firmware returned an error status for a request."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, status: int, cmd: int):
|
|
31
|
+
try:
|
|
32
|
+
self.status = Status(status)
|
|
33
|
+
name = self.status.name
|
|
34
|
+
except ValueError:
|
|
35
|
+
self.status = status # unknown code
|
|
36
|
+
name = f"0x{status:02X}"
|
|
37
|
+
self.cmd = cmd
|
|
38
|
+
super().__init__(f"firmware error {name} for command 0x{cmd:04X}")
|
espbridge/gpio.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""GPIO: modes, read/write, batch ops, edge interrupts."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import struct
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from . import constants as C
|
|
8
|
+
|
|
9
|
+
MODES = {
|
|
10
|
+
"input": 0,
|
|
11
|
+
"output": 1,
|
|
12
|
+
"input_pullup": 2,
|
|
13
|
+
"input_pulldown": 3,
|
|
14
|
+
"output_open_drain": 4,
|
|
15
|
+
}
|
|
16
|
+
EDGES = {"rising": 1, "falling": 2, "change": 3}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class EdgeEvent:
|
|
21
|
+
pin: int
|
|
22
|
+
level: int
|
|
23
|
+
millis: int # firmware uptime ms
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Gpio:
|
|
27
|
+
def __init__(self, bridge):
|
|
28
|
+
self._b = bridge
|
|
29
|
+
self._watchers: dict[int, list] = {}
|
|
30
|
+
bridge.on_event(C.GPIO_EDGE_EVT, self._on_edge)
|
|
31
|
+
|
|
32
|
+
def _on_edge(self, payload: bytes) -> None:
|
|
33
|
+
if len(payload) < 6:
|
|
34
|
+
return
|
|
35
|
+
ev = EdgeEvent(payload[0], payload[1], struct.unpack_from(">I", payload, 2)[0])
|
|
36
|
+
for cb in self._watchers.get(ev.pin, ()):
|
|
37
|
+
cb(ev)
|
|
38
|
+
|
|
39
|
+
def mode(self, pin: int, mode: str | int) -> None:
|
|
40
|
+
m = MODES[mode] if isinstance(mode, str) else int(mode)
|
|
41
|
+
self._b.request(C.GPIO_SET_MODE, bytes([pin, m]))
|
|
42
|
+
|
|
43
|
+
def write(self, pin: int, value: int | bool) -> None:
|
|
44
|
+
self._b.request(C.GPIO_WRITE, bytes([pin, 1 if value else 0]))
|
|
45
|
+
|
|
46
|
+
def read(self, pin: int) -> int:
|
|
47
|
+
return self._b.request(C.GPIO_READ, bytes([pin]))[0]
|
|
48
|
+
|
|
49
|
+
def write_many(self, values: dict[int, int | bool]) -> None:
|
|
50
|
+
"""Set multiple output pins in one round-trip."""
|
|
51
|
+
mask = vals = 0
|
|
52
|
+
for pin, v in values.items():
|
|
53
|
+
mask |= 1 << pin
|
|
54
|
+
if v:
|
|
55
|
+
vals |= 1 << pin
|
|
56
|
+
self._b.request(C.GPIO_WRITE_MASK, struct.pack(">QQ", mask, vals))
|
|
57
|
+
|
|
58
|
+
def read_all(self) -> int:
|
|
59
|
+
"""Levels of all pins as a bitmask (bit N = GPIO N)."""
|
|
60
|
+
(levels,) = struct.unpack(">Q", self._b.request(C.GPIO_READ_ALL))
|
|
61
|
+
return levels
|
|
62
|
+
|
|
63
|
+
def watch(self, pin: int, edge: str = "change", debounce_ms: int = 0, callback=None) -> None:
|
|
64
|
+
"""Get `callback(EdgeEvent)` on pin edges (callback runs on the reader thread)."""
|
|
65
|
+
e = EDGES[edge] if isinstance(edge, str) else int(edge)
|
|
66
|
+
if callback is not None:
|
|
67
|
+
self._watchers.setdefault(pin, []).append(callback)
|
|
68
|
+
self._b.request(C.GPIO_WATCH, struct.pack(">BBH", pin, e, debounce_ms))
|
|
69
|
+
|
|
70
|
+
def unwatch(self, pin: int) -> None:
|
|
71
|
+
self._b.request(C.GPIO_UNWATCH, bytes([pin]))
|
|
72
|
+
self._watchers.pop(pin, None)
|
espbridge/i2c.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""I2C master (buses 0 and 1)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import struct
|
|
5
|
+
|
|
6
|
+
from . import constants as C
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class I2c:
|
|
10
|
+
def __init__(self, bridge):
|
|
11
|
+
self._b = bridge
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def max_write(self) -> int:
|
|
15
|
+
"""Largest data block write() accepts, firmware-dependent: older
|
|
16
|
+
firmware leaves Wire's TX buffer at its 128-byte default and
|
|
17
|
+
silently truncates anything longer."""
|
|
18
|
+
info = self._b.info
|
|
19
|
+
if info is not None and info.fw_version < (0, 0, 2):
|
|
20
|
+
return 128
|
|
21
|
+
return C.MAX_PAYLOAD - 2 # frame carries bus + addr first
|
|
22
|
+
|
|
23
|
+
def init(self, *, sda: int = 21, scl: int = 22, freq: int = 400_000, bus: int = 0) -> None:
|
|
24
|
+
self._b.request(C.I2C_INIT, struct.pack(">BBBI", bus, sda, scl, freq))
|
|
25
|
+
|
|
26
|
+
def scan(self, bus: int = 0) -> list[int]:
|
|
27
|
+
"""Addresses (7-bit) that ACK on the bus."""
|
|
28
|
+
r = self._b.request(C.I2C_SCAN, bytes([bus]), timeout=5.0)
|
|
29
|
+
return list(r[1 : 1 + r[0]])
|
|
30
|
+
|
|
31
|
+
def write(self, addr: int, data: bytes, bus: int = 0, *, wait: bool = True) -> None:
|
|
32
|
+
"""Write bytes to a device. ``wait=False`` sends fire-and-forget —
|
|
33
|
+
no ACK round-trip, errors are not reported; pair a burst of unwaited
|
|
34
|
+
writes with a final waited one to sync (the firmware executes
|
|
35
|
+
requests in arrival order)."""
|
|
36
|
+
if len(data) > self.max_write:
|
|
37
|
+
raise ValueError(f"max {self.max_write} bytes per I2C write "
|
|
38
|
+
"(update the firmware for 2046)")
|
|
39
|
+
payload = bytes([bus, addr]) + bytes(data)
|
|
40
|
+
if wait:
|
|
41
|
+
self._b.request(C.I2C_WRITE, payload)
|
|
42
|
+
else:
|
|
43
|
+
self._b.send(C.I2C_WRITE, payload)
|
|
44
|
+
|
|
45
|
+
def read(self, addr: int, n: int, bus: int = 0) -> bytes:
|
|
46
|
+
if not 1 <= n <= 255:
|
|
47
|
+
raise ValueError("read length must be 1..255")
|
|
48
|
+
return self._b.request(C.I2C_READ, bytes([bus, addr, n]))
|
|
49
|
+
|
|
50
|
+
def write_read(self, addr: int, wdata: bytes, rlen: int, bus: int = 0) -> bytes:
|
|
51
|
+
"""Write then read with a repeated start (typical register read)."""
|
|
52
|
+
if len(wdata) > 255 or not 1 <= rlen <= 255:
|
|
53
|
+
raise ValueError("wdata max 255 bytes, rlen 1..255")
|
|
54
|
+
payload = bytes([bus, addr, len(wdata)]) + bytes(wdata) + bytes([rlen])
|
|
55
|
+
return self._b.request(C.I2C_WRITE_READ, payload)
|
|
56
|
+
|
|
57
|
+
def read_reg(self, addr: int, reg: int, n: int = 1, bus: int = 0) -> bytes:
|
|
58
|
+
return self.write_read(addr, bytes([reg]), n, bus)
|
|
59
|
+
|
|
60
|
+
def write_reg(self, addr: int, reg: int, data: bytes | int, bus: int = 0) -> None:
|
|
61
|
+
data = bytes([data]) if isinstance(data, int) else bytes(data)
|
|
62
|
+
self.write(addr, bytes([reg]) + data, bus)
|
|
63
|
+
|
|
64
|
+
def deinit(self, bus: int = 0) -> None:
|
|
65
|
+
self._b.request(C.I2C_DEINIT, bytes([bus]))
|