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.
@@ -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]))