esplink 1.0.2__tar.gz
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.
- esplink-1.0.2/.gitignore +227 -0
- esplink-1.0.2/.python-version +1 -0
- esplink-1.0.2/PKG-INFO +52 -0
- esplink-1.0.2/README.md +27 -0
- esplink-1.0.2/esplink/__init__.py +88 -0
- esplink-1.0.2/esplink/_log.py +73 -0
- esplink-1.0.2/esplink/aio.py +225 -0
- esplink-1.0.2/esplink/analog.py +82 -0
- esplink-1.0.2/esplink/ble.py +250 -0
- esplink-1.0.2/esplink/bridge.py +1374 -0
- esplink-1.0.2/esplink/camera.py +90 -0
- esplink-1.0.2/esplink/can.py +120 -0
- esplink-1.0.2/esplink/cli.py +235 -0
- esplink-1.0.2/esplink/compat/__init__.py +0 -0
- esplink-1.0.2/esplink/compat/blinka.py +210 -0
- esplink-1.0.2/esplink/compat/gpiozero.py +233 -0
- esplink-1.0.2/esplink/compat/luma.py +94 -0
- esplink-1.0.2/esplink/compat/rpi_gpio.py +133 -0
- esplink-1.0.2/esplink/compat/smbus.py +63 -0
- esplink-1.0.2/esplink/constants.py +404 -0
- esplink-1.0.2/esplink/drivers/README.md +76 -0
- esplink-1.0.2/esplink/drivers/__init__.py +156 -0
- esplink-1.0.2/esplink/drivers/ads1115.py +113 -0
- esplink-1.0.2/esplink/drivers/bh1750.py +85 -0
- esplink-1.0.2/esplink/drivers/bme280.py +220 -0
- esplink-1.0.2/esplink/drivers/dht.py +79 -0
- esplink-1.0.2/esplink/drivers/ds18b20.py +68 -0
- esplink-1.0.2/esplink/drivers/ds3231.py +114 -0
- esplink-1.0.2/esplink/drivers/hcsr04.py +38 -0
- esplink-1.0.2/esplink/drivers/inmp441.py +67 -0
- esplink-1.0.2/esplink/drivers/ir.py +90 -0
- esplink-1.0.2/esplink/drivers/max98357.py +65 -0
- esplink-1.0.2/esplink/drivers/mcp3202.py +52 -0
- esplink-1.0.2/esplink/drivers/mcp3208.py +53 -0
- esplink-1.0.2/esplink/drivers/motor.py +143 -0
- esplink-1.0.2/esplink/drivers/mpu6050.py +112 -0
- esplink-1.0.2/esplink/drivers/neopixel.py +83 -0
- esplink-1.0.2/esplink/drivers/nrf24.py +287 -0
- esplink-1.0.2/esplink/drivers/oled.py +187 -0
- esplink-1.0.2/esplink/drivers/pca9685.py +133 -0
- esplink-1.0.2/esplink/drivers/pcf8574.py +61 -0
- esplink-1.0.2/esplink/drivers/stepper.py +93 -0
- esplink-1.0.2/esplink/drivers/sx126x.py +282 -0
- esplink-1.0.2/esplink/drivers/sx127x.py +238 -0
- esplink-1.0.2/esplink/drivers/sx128x.py +285 -0
- esplink-1.0.2/esplink/errors.py +71 -0
- esplink-1.0.2/esplink/espnow.py +170 -0
- esplink-1.0.2/esplink/eth.py +106 -0
- esplink-1.0.2/esplink/firmware/__init__.py +10 -0
- esplink-1.0.2/esplink/firmware/bridge.esp32.huge_app.bin +0 -0
- esplink-1.0.2/esplink/flash.py +202 -0
- esplink-1.0.2/esplink/fs.py +196 -0
- esplink-1.0.2/esplink/gpio.py +154 -0
- esplink-1.0.2/esplink/i2c.py +102 -0
- esplink-1.0.2/esplink/i2s.py +90 -0
- esplink-1.0.2/esplink/manager.py +194 -0
- esplink-1.0.2/esplink/mcp/__init__.py +19 -0
- esplink-1.0.2/esplink/mcp/__main__.py +5 -0
- esplink-1.0.2/esplink/mcp/common.py +131 -0
- esplink-1.0.2/esplink/mcp/server.py +197 -0
- esplink-1.0.2/esplink/mcp/tools.py +392 -0
- esplink-1.0.2/esplink/mcpwm.py +47 -0
- esplink-1.0.2/esplink/net.py +341 -0
- esplink-1.0.2/esplink/nvs.py +87 -0
- esplink-1.0.2/esplink/onewire.py +96 -0
- esplink-1.0.2/esplink/ota.py +64 -0
- esplink-1.0.2/esplink/protocol.py +202 -0
- esplink-1.0.2/esplink/pwm.py +56 -0
- esplink-1.0.2/esplink/rmt.py +123 -0
- esplink-1.0.2/esplink/spi.py +51 -0
- esplink-1.0.2/esplink/transports/__init__.py +33 -0
- esplink-1.0.2/esplink/transports/ble.py +282 -0
- esplink-1.0.2/esplink/transports/mock.py +46 -0
- esplink-1.0.2/esplink/transports/serial.py +90 -0
- esplink-1.0.2/esplink/transports/tcp.py +206 -0
- esplink-1.0.2/esplink/uart.py +130 -0
- esplink-1.0.2/esplink/watch.py +232 -0
- esplink-1.0.2/esplink/wifi.py +212 -0
- esplink-1.0.2/examples/README.md +141 -0
- esplink-1.0.2/examples/basics/adc_dac.py +24 -0
- esplink-1.0.2/examples/basics/async_fanout.py +50 -0
- esplink-1.0.2/examples/basics/blink.py +15 -0
- esplink-1.0.2/examples/basics/button_interrupt.py +14 -0
- esplink-1.0.2/examples/basics/i2c_scan.py +10 -0
- esplink-1.0.2/examples/basics/mcp3208_benchmark.py +74 -0
- esplink-1.0.2/examples/basics/pwm_servo.py +28 -0
- esplink-1.0.2/examples/basics/rtos_concurrency.py +198 -0
- esplink-1.0.2/examples/basics/shared_connection.py +52 -0
- esplink-1.0.2/examples/basics/spi_transfer.py +11 -0
- esplink-1.0.2/examples/basics/touch_read.py +28 -0
- esplink-1.0.2/examples/basics/uart_loopback.py +27 -0
- esplink-1.0.2/examples/basics/watch_threshold.py +110 -0
- esplink-1.0.2/examples/compat/adafruit/bme280.py +24 -0
- esplink-1.0.2/examples/compat/gpiozero/led_button.py +32 -0
- esplink-1.0.2/examples/compat/luma/oled.py +54 -0
- esplink-1.0.2/examples/compat/rpi_gpio/blink.py +20 -0
- esplink-1.0.2/examples/compat/smbus/read_register.py +33 -0
- esplink-1.0.2/examples/devices/can_dump.py +21 -0
- esplink-1.0.2/examples/devices/custom_driver.py +53 -0
- esplink-1.0.2/examples/devices/dht_read.py +17 -0
- esplink-1.0.2/examples/devices/ds18b20_temp.py +20 -0
- esplink-1.0.2/examples/devices/hcsr04_ping.py +17 -0
- esplink-1.0.2/examples/devices/i2s_record.py +20 -0
- esplink-1.0.2/examples/devices/ir_remote.py +22 -0
- esplink-1.0.2/examples/devices/neopixel_rainbow.py +25 -0
- esplink-1.0.2/examples/devices/stepper_move.py +21 -0
- esplink-1.0.2/examples/displays/ble_oled.py +41 -0
- esplink-1.0.2/examples/displays/oled_ssd1306.py +41 -0
- esplink-1.0.2/examples/network/many_boards.py +53 -0
- esplink-1.0.2/examples/network/tcp_through_bridge.py +28 -0
- esplink-1.0.2/examples/network/udp_through_bridge.py +23 -0
- esplink-1.0.2/examples/network/wifi_ap.py +28 -0
- esplink-1.0.2/examples/network/wifi_link.py +67 -0
- esplink-1.0.2/examples/network/wifi_scan.py +9 -0
- esplink-1.0.2/examples/pyproject.toml +17 -0
- esplink-1.0.2/examples/system/deep_sleep.py +34 -0
- esplink-1.0.2/examples/system/fs_logger.py +19 -0
- esplink-1.0.2/examples/system/mcp_server.py +47 -0
- esplink-1.0.2/examples/system/multi_client.py +93 -0
- esplink-1.0.2/examples/system/nvs_counter.py +8 -0
- esplink-1.0.2/examples/system/ota_update.py +25 -0
- esplink-1.0.2/examples/system/power_profiles.py +37 -0
- esplink-1.0.2/examples/uv.lock +2462 -0
- esplink-1.0.2/examples/wireless/ble_blink.py +31 -0
- esplink-1.0.2/examples/wireless/ble_scan.py +8 -0
- esplink-1.0.2/examples/wireless/coex_all_radios.py +47 -0
- esplink-1.0.2/examples/wireless/espnow_broadcast.py +62 -0
- esplink-1.0.2/examples/wireless/espnow_pair.py +54 -0
- esplink-1.0.2/examples/wireless/espnow_pingpong.py +62 -0
- esplink-1.0.2/examples/wireless/multi_device.py +39 -0
- esplink-1.0.2/examples/wireless/stress_test.py +423 -0
- esplink-1.0.2/examples/wireless/usb_vs_ble_benchmark.py +122 -0
- esplink-1.0.2/pyproject.toml +49 -0
- esplink-1.0.2/tests/conftest.py +26 -0
- esplink-1.0.2/tests/fake_firmware.py +749 -0
- esplink-1.0.2/tests/hw_stress.py +360 -0
- esplink-1.0.2/tests/stress_single_com3.py +105 -0
- esplink-1.0.2/tests/test_ads1115.py +94 -0
- esplink-1.0.2/tests/test_aio.py +70 -0
- esplink-1.0.2/tests/test_auth.py +74 -0
- esplink-1.0.2/tests/test_bh1750.py +67 -0
- esplink-1.0.2/tests/test_bme280.py +210 -0
- esplink-1.0.2/tests/test_bridge.py +319 -0
- esplink-1.0.2/tests/test_can.py +65 -0
- esplink-1.0.2/tests/test_compat_blinka.py +73 -0
- esplink-1.0.2/tests/test_compat_gpiozero.py +66 -0
- esplink-1.0.2/tests/test_compat_smbus.py +43 -0
- esplink-1.0.2/tests/test_concurrency.py +97 -0
- esplink-1.0.2/tests/test_contract_sync.py +133 -0
- esplink-1.0.2/tests/test_dht.py +68 -0
- esplink-1.0.2/tests/test_drivers.py +83 -0
- esplink-1.0.2/tests/test_ds18b20.py +44 -0
- esplink-1.0.2/tests/test_ds3231.py +79 -0
- esplink-1.0.2/tests/test_espnow.py +191 -0
- esplink-1.0.2/tests/test_eth_cam.py +45 -0
- esplink-1.0.2/tests/test_fs.py +64 -0
- esplink-1.0.2/tests/test_gpio_status.py +38 -0
- esplink-1.0.2/tests/test_hcsr04.py +21 -0
- esplink-1.0.2/tests/test_i2s.py +32 -0
- esplink-1.0.2/tests/test_inmp441.py +62 -0
- esplink-1.0.2/tests/test_ir.py +59 -0
- esplink-1.0.2/tests/test_luma_compat.py +35 -0
- esplink-1.0.2/tests/test_manager.py +128 -0
- esplink-1.0.2/tests/test_max98357.py +60 -0
- esplink-1.0.2/tests/test_mcp.py +252 -0
- esplink-1.0.2/tests/test_mcp3202.py +64 -0
- esplink-1.0.2/tests/test_mcp3208.py +62 -0
- esplink-1.0.2/tests/test_mcpwm.py +25 -0
- esplink-1.0.2/tests/test_motor.py +160 -0
- esplink-1.0.2/tests/test_mpu6050.py +107 -0
- esplink-1.0.2/tests/test_multi.py +393 -0
- esplink-1.0.2/tests/test_neopixel.py +48 -0
- esplink-1.0.2/tests/test_net.py +94 -0
- esplink-1.0.2/tests/test_nrf24.py +159 -0
- esplink-1.0.2/tests/test_nvs.py +44 -0
- esplink-1.0.2/tests/test_oled.py +171 -0
- esplink-1.0.2/tests/test_onewire.py +48 -0
- esplink-1.0.2/tests/test_ota.py +47 -0
- esplink-1.0.2/tests/test_pca9685.py +127 -0
- esplink-1.0.2/tests/test_pcf8574.py +64 -0
- esplink-1.0.2/tests/test_protocol.py +102 -0
- esplink-1.0.2/tests/test_rmt.py +76 -0
- esplink-1.0.2/tests/test_sleep.py +23 -0
- esplink-1.0.2/tests/test_stepper.py +53 -0
- esplink-1.0.2/tests/test_subapis.py +25 -0
- esplink-1.0.2/tests/test_sx126x.py +220 -0
- esplink-1.0.2/tests/test_sx127x.py +204 -0
- esplink-1.0.2/tests/test_sx128x.py +194 -0
- esplink-1.0.2/tests/test_watch.py +148 -0
- esplink-1.0.2/tests/test_wifi_link.py +170 -0
- esplink-1.0.2/uv.lock +2250 -0
esplink-1.0.2/.gitignore
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Written by python/examples/devices/i2s_record.py when you run it.
|
|
2
|
+
python/examples/devices/recording.wav
|
|
3
|
+
|
|
4
|
+
# Byte-compiled / optimized / DLL files
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[codz]
|
|
7
|
+
*$py.class
|
|
8
|
+
|
|
9
|
+
# Prebuilt firmware image bundled into the wheel — regenerated by
|
|
10
|
+
# tools/build_firmware.py (locally / in CI) before the package is built.
|
|
11
|
+
python/esplink/firmware/*.bin
|
|
12
|
+
# Isolated arduino-cli build scratch for tools/build_firmware.py (ELF/map/bins).
|
|
13
|
+
tools/.fw-build/
|
|
14
|
+
|
|
15
|
+
# C extensions
|
|
16
|
+
*.so
|
|
17
|
+
|
|
18
|
+
# Distribution / packaging
|
|
19
|
+
.Python
|
|
20
|
+
build/
|
|
21
|
+
develop-eggs/
|
|
22
|
+
dist/
|
|
23
|
+
downloads/
|
|
24
|
+
eggs/
|
|
25
|
+
.eggs/
|
|
26
|
+
lib/
|
|
27
|
+
lib64/
|
|
28
|
+
parts/
|
|
29
|
+
sdist/
|
|
30
|
+
var/
|
|
31
|
+
wheels/
|
|
32
|
+
share/python-wheels/
|
|
33
|
+
*.egg-info/
|
|
34
|
+
.installed.cfg
|
|
35
|
+
*.egg
|
|
36
|
+
MANIFEST
|
|
37
|
+
|
|
38
|
+
# PyInstaller
|
|
39
|
+
# Usually these files are written by a python script from a template
|
|
40
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
41
|
+
*.manifest
|
|
42
|
+
*.spec
|
|
43
|
+
|
|
44
|
+
# Installer logs
|
|
45
|
+
pip-log.txt
|
|
46
|
+
pip-delete-this-directory.txt
|
|
47
|
+
|
|
48
|
+
# Unit test / coverage reports
|
|
49
|
+
htmlcov/
|
|
50
|
+
.tox/
|
|
51
|
+
.nox/
|
|
52
|
+
.coverage
|
|
53
|
+
.coverage.*
|
|
54
|
+
.cache
|
|
55
|
+
nosetests.xml
|
|
56
|
+
coverage.xml
|
|
57
|
+
*.cover
|
|
58
|
+
*.py.cover
|
|
59
|
+
.hypothesis/
|
|
60
|
+
.pytest_cache/
|
|
61
|
+
cover/
|
|
62
|
+
|
|
63
|
+
# Translations
|
|
64
|
+
*.mo
|
|
65
|
+
*.pot
|
|
66
|
+
|
|
67
|
+
# Django stuff:
|
|
68
|
+
*.log
|
|
69
|
+
local_settings.py
|
|
70
|
+
db.sqlite3
|
|
71
|
+
db.sqlite3-journal
|
|
72
|
+
|
|
73
|
+
# Flask stuff:
|
|
74
|
+
instance/
|
|
75
|
+
.webassets-cache
|
|
76
|
+
|
|
77
|
+
# Scrapy stuff:
|
|
78
|
+
.scrapy
|
|
79
|
+
|
|
80
|
+
# Sphinx documentation
|
|
81
|
+
docs/_build/
|
|
82
|
+
|
|
83
|
+
# PyBuilder
|
|
84
|
+
.pybuilder/
|
|
85
|
+
target/
|
|
86
|
+
|
|
87
|
+
# Jupyter Notebook
|
|
88
|
+
.ipynb_checkpoints
|
|
89
|
+
|
|
90
|
+
# IPython
|
|
91
|
+
profile_default/
|
|
92
|
+
ipython_config.py
|
|
93
|
+
|
|
94
|
+
# pyenv
|
|
95
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
96
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
97
|
+
# .python-version
|
|
98
|
+
|
|
99
|
+
# pipenv
|
|
100
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
101
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
102
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
103
|
+
# install all needed dependencies.
|
|
104
|
+
# Pipfile.lock
|
|
105
|
+
|
|
106
|
+
# UV
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
108
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
109
|
+
# commonly ignored for libraries.
|
|
110
|
+
# uv.lock
|
|
111
|
+
|
|
112
|
+
# poetry
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
114
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
115
|
+
# commonly ignored for libraries.
|
|
116
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
117
|
+
# poetry.lock
|
|
118
|
+
# poetry.toml
|
|
119
|
+
|
|
120
|
+
# pdm
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
122
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
123
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
124
|
+
# pdm.lock
|
|
125
|
+
# pdm.toml
|
|
126
|
+
.pdm-python
|
|
127
|
+
.pdm-build/
|
|
128
|
+
|
|
129
|
+
# pixi
|
|
130
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
131
|
+
# pixi.lock
|
|
132
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
133
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
134
|
+
.pixi
|
|
135
|
+
|
|
136
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
137
|
+
__pypackages__/
|
|
138
|
+
|
|
139
|
+
# Celery stuff
|
|
140
|
+
celerybeat-schedule
|
|
141
|
+
celerybeat.pid
|
|
142
|
+
|
|
143
|
+
# Redis
|
|
144
|
+
*.rdb
|
|
145
|
+
*.aof
|
|
146
|
+
*.pid
|
|
147
|
+
|
|
148
|
+
# RabbitMQ
|
|
149
|
+
mnesia/
|
|
150
|
+
rabbitmq/
|
|
151
|
+
rabbitmq-data/
|
|
152
|
+
|
|
153
|
+
# ActiveMQ
|
|
154
|
+
activemq-data/
|
|
155
|
+
|
|
156
|
+
# SageMath parsed files
|
|
157
|
+
*.sage.py
|
|
158
|
+
|
|
159
|
+
# Environments
|
|
160
|
+
.env
|
|
161
|
+
.envrc
|
|
162
|
+
.venv
|
|
163
|
+
env/
|
|
164
|
+
venv/
|
|
165
|
+
ENV/
|
|
166
|
+
env.bak/
|
|
167
|
+
venv.bak/
|
|
168
|
+
|
|
169
|
+
# Spyder project settings
|
|
170
|
+
.spyderproject
|
|
171
|
+
.spyproject
|
|
172
|
+
|
|
173
|
+
# Rope project settings
|
|
174
|
+
.ropeproject
|
|
175
|
+
|
|
176
|
+
# mkdocs documentation
|
|
177
|
+
/site
|
|
178
|
+
|
|
179
|
+
# mypy
|
|
180
|
+
.mypy_cache/
|
|
181
|
+
.dmypy.json
|
|
182
|
+
dmypy.json
|
|
183
|
+
|
|
184
|
+
# Pyre type checker
|
|
185
|
+
.pyre/
|
|
186
|
+
|
|
187
|
+
# pytype static type analyzer
|
|
188
|
+
.pytype/
|
|
189
|
+
|
|
190
|
+
# Cython debug symbols
|
|
191
|
+
cython_debug/
|
|
192
|
+
|
|
193
|
+
# PyCharm
|
|
194
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
195
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
196
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
197
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
198
|
+
# .idea/
|
|
199
|
+
|
|
200
|
+
# Abstra
|
|
201
|
+
# Abstra is an AI-powered process automation framework.
|
|
202
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
203
|
+
# Learn more at https://abstra.io/docs
|
|
204
|
+
.abstra/
|
|
205
|
+
|
|
206
|
+
# Visual Studio Code
|
|
207
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
208
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
209
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
210
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
211
|
+
# .vscode/
|
|
212
|
+
# Temporary file for partial code execution
|
|
213
|
+
tempCodeRunnerFile.py
|
|
214
|
+
|
|
215
|
+
# Ruff stuff:
|
|
216
|
+
.ruff_cache/
|
|
217
|
+
|
|
218
|
+
# PyPI configuration file
|
|
219
|
+
.pypirc
|
|
220
|
+
|
|
221
|
+
# Marimo
|
|
222
|
+
marimo/_static/
|
|
223
|
+
marimo/_lsp/
|
|
224
|
+
__marimo__/
|
|
225
|
+
|
|
226
|
+
# Streamlit
|
|
227
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
esplink-1.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: esplink
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Control every ESP32 peripheral from Python over USB serial or Bluetooth — GPIO, ADC, DAC, PWM, touch, I2C, SPI, UART, Wi-Fi sockets, BLE, ESP-NOW, RMT, 1-Wire, CAN, I2S, files, NVS, OTA
|
|
5
|
+
Project-URL: Homepage, https://github.com/HamzaYslmn/esplink
|
|
6
|
+
Author: HamzaYslmn
|
|
7
|
+
Keywords: ble,bridge,esp32,espnow,firmata,gpio,i2c,raspberry-pi,serial,spi
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: bleak>=3.0.2
|
|
10
|
+
Requires-Dist: pyserial>=3.5
|
|
11
|
+
Provides-Extra: all
|
|
12
|
+
Requires-Dist: bleak>=0.22; extra == 'all'
|
|
13
|
+
Requires-Dist: esptool>=5; extra == 'all'
|
|
14
|
+
Requires-Dist: fastmcp>=2.3; extra == 'all'
|
|
15
|
+
Requires-Dist: pillow>=10; extra == 'all'
|
|
16
|
+
Provides-Extra: ble
|
|
17
|
+
Requires-Dist: bleak>=0.22; extra == 'ble'
|
|
18
|
+
Provides-Extra: flash
|
|
19
|
+
Requires-Dist: esptool>=5; extra == 'flash'
|
|
20
|
+
Provides-Extra: mcp
|
|
21
|
+
Requires-Dist: fastmcp>=2.3; extra == 'mcp'
|
|
22
|
+
Provides-Extra: oled
|
|
23
|
+
Requires-Dist: pillow>=10; extra == 'oled'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# esplink
|
|
27
|
+
|
|
28
|
+
Control every ESP32 peripheral from Python over USB serial — GPIO, PWM, ADC,
|
|
29
|
+
DAC, touch, I2C, SPI, UART, Wi-Fi (with TCP/UDP sockets through the ESP32
|
|
30
|
+
radio) and BLE. Flash the bridge firmware once, then it's all Python.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from esplink import Bridge
|
|
34
|
+
|
|
35
|
+
with Bridge() as esp: # USB first, then Bluetooth, then Wi-Fi
|
|
36
|
+
esp.gpio.mode(2, "output")
|
|
37
|
+
esp.gpio.write(2, 1)
|
|
38
|
+
print(esp.adc.read_mv(34), "mV")
|
|
39
|
+
esp.i2c.init(sda=21, scl=22)
|
|
40
|
+
print(esp.i2c.scan())
|
|
41
|
+
esp.wifi.connect("ssid", "password")
|
|
42
|
+
status, body = esp.net.http_get("http://example.com/")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- On-device rules react without the host in the loop
|
|
46
|
+
(`esp.watch.add("adc", pin=34, above=3200, do=("pwm", 13, 0))`), and
|
|
47
|
+
`esp.radio_off()` silences Wi-Fi + Bluetooth entirely for jitter-sensitive
|
|
48
|
+
realtime work (frees ~110 KB heap, unlocks the ADC2 pins).
|
|
49
|
+
- Firmware (flash once with Arduino IDE) and full docs:
|
|
50
|
+
**<https://github.com/HamzaYslmn/esplink>**
|
|
51
|
+
- Works on Raspberry Pi OS, Linux, Windows, macOS (Python ≥ 3.11, pyserial).
|
|
52
|
+
- `esplink` CLI: connection info; `esplink ports`: list candidate ports.
|
esplink-1.0.2/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# esplink
|
|
2
|
+
|
|
3
|
+
Control every ESP32 peripheral from Python over USB serial — GPIO, PWM, ADC,
|
|
4
|
+
DAC, touch, I2C, SPI, UART, Wi-Fi (with TCP/UDP sockets through the ESP32
|
|
5
|
+
radio) and BLE. Flash the bridge firmware once, then it's all Python.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from esplink import Bridge
|
|
9
|
+
|
|
10
|
+
with Bridge() as esp: # USB first, then Bluetooth, then Wi-Fi
|
|
11
|
+
esp.gpio.mode(2, "output")
|
|
12
|
+
esp.gpio.write(2, 1)
|
|
13
|
+
print(esp.adc.read_mv(34), "mV")
|
|
14
|
+
esp.i2c.init(sda=21, scl=22)
|
|
15
|
+
print(esp.i2c.scan())
|
|
16
|
+
esp.wifi.connect("ssid", "password")
|
|
17
|
+
status, body = esp.net.http_get("http://example.com/")
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- On-device rules react without the host in the loop
|
|
21
|
+
(`esp.watch.add("adc", pin=34, above=3200, do=("pwm", 13, 0))`), and
|
|
22
|
+
`esp.radio_off()` silences Wi-Fi + Bluetooth entirely for jitter-sensitive
|
|
23
|
+
realtime work (frees ~110 KB heap, unlocks the ADC2 pins).
|
|
24
|
+
- Firmware (flash once with Arduino IDE) and full docs:
|
|
25
|
+
**<https://github.com/HamzaYslmn/esplink>**
|
|
26
|
+
- Works on Raspberry Pi OS, Linux, Windows, macOS (Python ≥ 3.11, pyserial).
|
|
27
|
+
- `esplink` CLI: connection info; `esplink ports`: list candidate ports.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""esplink — control every ESP32 peripheral from Python over USB or Bluetooth.
|
|
2
|
+
|
|
3
|
+
Flash the firmware once (Arduino library "esplink"), then:
|
|
4
|
+
|
|
5
|
+
from esplink import Bridge
|
|
6
|
+
|
|
7
|
+
with Bridge() as esp: # USB first, then Bluetooth, then Wi-Fi
|
|
8
|
+
esp.gpio.mode(2, "output")
|
|
9
|
+
esp.gpio.write(2, 1)
|
|
10
|
+
print(esp.adc.read(34))
|
|
11
|
+
print(esp.i2c.scan())
|
|
12
|
+
esp.wifi.connect("ssid", "password")
|
|
13
|
+
sock = esp.net.tcp_connect("example.com", 80) # TCP through the ESP32 radio
|
|
14
|
+
|
|
15
|
+
esp.nvs.set("runs", 1) # on-board key/value storage
|
|
16
|
+
esp.fs.mount("littlefs") # on-board files
|
|
17
|
+
esp.can.begin(tx=21, rx=22) # CAN bus
|
|
18
|
+
esp.ota.flash("new.bin") # update the firmware over the link
|
|
19
|
+
|
|
20
|
+
# device drivers in pure Python (RMT / 1-Wire / I2C primitives), all under
|
|
21
|
+
# esplink.drivers: dht, oled, neopixel, ds18b20, hcsr04, ir, stepper, ...
|
|
22
|
+
# ...or write/register your own — esp.<name>(...) — see esplink.drivers
|
|
23
|
+
# and docs/DRIVERS.md ("bring your own driver").
|
|
24
|
+
"""
|
|
25
|
+
from .bridge import Bridge, BridgeSet, Info
|
|
26
|
+
from .constants import Cap, ChipModel, Status
|
|
27
|
+
from .drivers import driver_names, register_driver
|
|
28
|
+
from .manager import BridgeManager, connect, disconnect_all
|
|
29
|
+
from .errors import (
|
|
30
|
+
AuthError,
|
|
31
|
+
BridgeError,
|
|
32
|
+
BridgeTimeoutError,
|
|
33
|
+
NoDeviceError,
|
|
34
|
+
ProtocolError,
|
|
35
|
+
RemoteError,
|
|
36
|
+
UnsupportedError,
|
|
37
|
+
)
|
|
38
|
+
from .transports import find_ports, find_wifi_devices
|
|
39
|
+
from .gpio import EdgeEvent
|
|
40
|
+
from .watch import WatchEvent
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def find_ble_devices(timeout: float = 5.0):
|
|
44
|
+
"""Scan for bridges advertising over Bluetooth (needs the [ble] extra)."""
|
|
45
|
+
from .transports.ble import find_ble_devices as _scan
|
|
46
|
+
|
|
47
|
+
return _scan(timeout)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def __getattr__(name):
|
|
51
|
+
# Lazy: importing AsyncBridge pulls in asyncio, so defer it until first use
|
|
52
|
+
# rather than charging every sync/USB-only script for the import.
|
|
53
|
+
if name == "AsyncBridge":
|
|
54
|
+
from .aio import AsyncBridge
|
|
55
|
+
|
|
56
|
+
return AsyncBridge
|
|
57
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
58
|
+
|
|
59
|
+
__version__ = "1.0.1"
|
|
60
|
+
|
|
61
|
+
# Grouped by role, not alphabetised: this list is the shape of the public API.
|
|
62
|
+
__all__ = [
|
|
63
|
+
"Bridge",
|
|
64
|
+
"AsyncBridge",
|
|
65
|
+
"BridgeSet",
|
|
66
|
+
"BridgeManager",
|
|
67
|
+
"connect",
|
|
68
|
+
"disconnect_all",
|
|
69
|
+
"register_driver",
|
|
70
|
+
"driver_names",
|
|
71
|
+
"find_ble_devices",
|
|
72
|
+
"find_wifi_devices",
|
|
73
|
+
"Info",
|
|
74
|
+
"Cap",
|
|
75
|
+
"ChipModel",
|
|
76
|
+
"Status",
|
|
77
|
+
"AuthError",
|
|
78
|
+
"BridgeError",
|
|
79
|
+
"BridgeTimeoutError",
|
|
80
|
+
"NoDeviceError",
|
|
81
|
+
"ProtocolError",
|
|
82
|
+
"RemoteError",
|
|
83
|
+
"UnsupportedError",
|
|
84
|
+
"find_ports",
|
|
85
|
+
"EdgeEvent",
|
|
86
|
+
"WatchEvent",
|
|
87
|
+
"__version__",
|
|
88
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Library logger: "[esplink]" prefix + uvicorn-style colored level prefix.
|
|
2
|
+
|
|
3
|
+
Mirrors uvicorn.logging.DefaultFormatter (same colors, same ``LEVEL:``
|
|
4
|
+
padding) without depending on uvicorn: colors auto-enable only when stderr
|
|
5
|
+
is a terminal that supports ANSI, and NO_COLOR is honored.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
# uvicorn's palette: DEBUG cyan, INFO green, WARNING yellow, ERROR red,
|
|
14
|
+
# CRITICAL bright red.
|
|
15
|
+
_LEVEL_COLORS = {
|
|
16
|
+
logging.DEBUG: 36,
|
|
17
|
+
logging.INFO: 32,
|
|
18
|
+
logging.WARNING: 33,
|
|
19
|
+
logging.ERROR: 31,
|
|
20
|
+
logging.CRITICAL: 91,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _supports_color(stream) -> bool:
|
|
25
|
+
if os.environ.get("NO_COLOR"):
|
|
26
|
+
return False
|
|
27
|
+
if not getattr(stream, "isatty", lambda: False)():
|
|
28
|
+
return False
|
|
29
|
+
if sys.platform == "win32":
|
|
30
|
+
# Windows Terminal and VS Code already have VT processing enabled.
|
|
31
|
+
# For the classic Windows console, try to enable it here.
|
|
32
|
+
# If the call fails, ANSI escape sequences are not supported.
|
|
33
|
+
import ctypes
|
|
34
|
+
|
|
35
|
+
kernel32 = ctypes.windll.kernel32
|
|
36
|
+
handle = kernel32.GetStdHandle(-12) # STD_ERROR_HANDLE
|
|
37
|
+
mode = ctypes.c_uint32()
|
|
38
|
+
if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
|
|
39
|
+
return False
|
|
40
|
+
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
|
41
|
+
return bool(kernel32.SetConsoleMode(
|
|
42
|
+
handle, mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _Formatter(logging.Formatter):
|
|
47
|
+
def __init__(self, use_colors: bool):
|
|
48
|
+
super().__init__("[esplink] %(levelprefix)s %(message)s")
|
|
49
|
+
self.use_colors = use_colors
|
|
50
|
+
|
|
51
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
52
|
+
pad = " " * max(0, 8 - len(record.levelname)) # right-pad level name to 8 chars, matching uvicorn's alignment
|
|
53
|
+
if self.use_colors:
|
|
54
|
+
color = _LEVEL_COLORS.get(record.levelno, 0)
|
|
55
|
+
record.levelprefix = (
|
|
56
|
+
f"\x1b[{color}m{record.levelname}\x1b[0m:{pad}")
|
|
57
|
+
else:
|
|
58
|
+
record.levelprefix = f"{record.levelname}:{pad}"
|
|
59
|
+
return super().format(record)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
log = logging.getLogger("esplink")
|
|
63
|
+
if not log.handlers:
|
|
64
|
+
if os.environ.get("ESPLINK_DEBUG"):
|
|
65
|
+
# Enable frame-level tracing (every request and response with command
|
|
66
|
+
# names) without modifying the script: ESPLINK_DEBUG=1 python app.py
|
|
67
|
+
log.setLevel(logging.DEBUG)
|
|
68
|
+
elif log.level == logging.NOTSET: # respect a level the app set first
|
|
69
|
+
log.setLevel(logging.INFO)
|
|
70
|
+
_handler = logging.StreamHandler(sys.stderr)
|
|
71
|
+
_handler.setFormatter(_Formatter(_supports_color(sys.stderr)))
|
|
72
|
+
log.addHandler(_handler)
|
|
73
|
+
log.propagate = False
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""asyncio facade: ``await`` every bridge call and fan out concurrent I/O.
|
|
2
|
+
|
|
3
|
+
The ESP32 firmware is *already* concurrent — requests carry a sequence number,
|
|
4
|
+
many can be in flight at once, and a slow call on one task never stalls the
|
|
5
|
+
fast lane (see ``examples/basics/rtos_concurrency.py``). ``AsyncBridge`` simply
|
|
6
|
+
exposes that concurrency to ``asyncio`` so you can ``await`` bridge calls and
|
|
7
|
+
run them together with ``asyncio.gather``, instead of juggling threads yourself:
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from esplink import AsyncBridge
|
|
11
|
+
|
|
12
|
+
async def main():
|
|
13
|
+
async with AsyncBridge(ble=False) as esp:
|
|
14
|
+
await esp.gpio.mode(2, "output")
|
|
15
|
+
await esp.gpio.write(2, 1)
|
|
16
|
+
# both reads are issued together and overlap on the wire:
|
|
17
|
+
t, h = await asyncio.gather(esp.adc.read(34), esp.adc.read(35))
|
|
18
|
+
print(t, h)
|
|
19
|
+
|
|
20
|
+
asyncio.run(main())
|
|
21
|
+
|
|
22
|
+
Edge interrupts (``gpio.watch``) become an async stream — no callback thread:
|
|
23
|
+
|
|
24
|
+
async with AsyncBridge() as esp:
|
|
25
|
+
await esp.gpio.mode(4, "input_pullup")
|
|
26
|
+
await esp.gpio.watch(4, edge="falling", debounce_ms=30)
|
|
27
|
+
async for ev in esp.gpio_events():
|
|
28
|
+
print("pressed", ev.pin, ev.millis)
|
|
29
|
+
|
|
30
|
+
How it works (and what to expect): the firmware speaks a synchronous
|
|
31
|
+
request/response protocol and the underlying ``Bridge`` is fully thread-safe,
|
|
32
|
+
so each awaited call runs the matching ``Bridge`` method in a worker thread
|
|
33
|
+
(``asyncio.to_thread``). Calls awaited together genuinely overlap on the link
|
|
34
|
+
because ``Bridge`` pipelines concurrent requests and correlates replies by
|
|
35
|
+
sequence number — ``await`` here is about *the host not blocking*, not about
|
|
36
|
+
moving work onto the ESP32 (the firmware's task split already does that).
|
|
37
|
+
|
|
38
|
+
For tight inner loops or fire-and-forget streaming (e.g. pushing OLED frames),
|
|
39
|
+
the plain synchronous ``Bridge`` is still the right tool — there is nothing to
|
|
40
|
+
gain from awaiting each byte, and a thread per call would only add overhead.
|
|
41
|
+
"""
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import asyncio
|
|
45
|
+
import functools
|
|
46
|
+
|
|
47
|
+
from . import constants as C
|
|
48
|
+
from .bridge import Bridge
|
|
49
|
+
from .gpio import EdgeEvent
|
|
50
|
+
from .watch import WatchEvent
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _to_coro(fn):
|
|
54
|
+
"""Wrap a blocking bridge callable so it runs in a thread and can be awaited."""
|
|
55
|
+
@functools.wraps(fn)
|
|
56
|
+
async def _call(*args, **kwargs):
|
|
57
|
+
return await asyncio.to_thread(fn, *args, **kwargs)
|
|
58
|
+
return _call
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _async_attr(attr):
|
|
62
|
+
"""Adapt a bound bridge attribute for async use: callables become coroutines
|
|
63
|
+
that run in a worker thread; plain values (info, caps, max_write, ...) pass
|
|
64
|
+
through. One uniform rule — nothing has to enumerate which calls touch the
|
|
65
|
+
wire, so a new Bridge method is awaitable automatically."""
|
|
66
|
+
return _to_coro(attr) if callable(attr) else attr
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class _AsyncSubApi:
|
|
70
|
+
"""Proxies a sync sub-API (``esp.gpio``, ``esp.adc``, ...): every method
|
|
71
|
+
becomes awaitable; plain attributes (e.g. ``i2c.max_write``) pass through."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, target):
|
|
74
|
+
self._target = target
|
|
75
|
+
|
|
76
|
+
def __getattr__(self, name):
|
|
77
|
+
if name.startswith("_"): # don't expose the sub-API's internals
|
|
78
|
+
raise AttributeError(name)
|
|
79
|
+
return _async_attr(getattr(self._target, name))
|
|
80
|
+
|
|
81
|
+
def __dir__(self):
|
|
82
|
+
return dir(self._target)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class _EventStream:
|
|
86
|
+
"""Async iterator over a firmware event, fed from the reader thread.
|
|
87
|
+
|
|
88
|
+
Registered with ``Bridge.on_event``; each event payload is handed to the
|
|
89
|
+
asyncio loop via ``call_soon_threadsafe`` and surfaces through ``async for``.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, bridge: Bridge, cmd: int, loop, parse=None, maxsize: int = 0):
|
|
93
|
+
self._bridge = bridge
|
|
94
|
+
self._cmd = cmd
|
|
95
|
+
self._loop = loop
|
|
96
|
+
self._parse = parse
|
|
97
|
+
self._queue: asyncio.Queue = asyncio.Queue(maxsize)
|
|
98
|
+
bridge.on_event(cmd, self._on_event)
|
|
99
|
+
|
|
100
|
+
def _on_event(self, payload: bytes) -> None:
|
|
101
|
+
item = self._parse(payload) if self._parse else payload
|
|
102
|
+
if item is None: # parser rejected a malformed/short payload
|
|
103
|
+
return
|
|
104
|
+
# Hop from the reader thread onto the asyncio loop; drop if the queue is
|
|
105
|
+
# full (a bounded maxsize gives back-pressure without blocking the reader).
|
|
106
|
+
self._loop.call_soon_threadsafe(self._offer, item)
|
|
107
|
+
|
|
108
|
+
def _offer(self, item) -> None:
|
|
109
|
+
try:
|
|
110
|
+
self._queue.put_nowait(item)
|
|
111
|
+
except asyncio.QueueFull:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
def __aiter__(self):
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
async def __anext__(self):
|
|
118
|
+
return await self._queue.get()
|
|
119
|
+
|
|
120
|
+
async def aclose(self) -> None:
|
|
121
|
+
"""Stop the stream and unregister it from the bridge (call when done, or
|
|
122
|
+
just break out of the ``async for`` and let it be garbage-collected)."""
|
|
123
|
+
self._bridge.off_event(self._cmd, self._on_event)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class AsyncBridge:
|
|
127
|
+
"""``asyncio`` wrapper around :class:`Bridge`.
|
|
128
|
+
|
|
129
|
+
Accepts the same arguments as ``Bridge``. Connection happens off the event
|
|
130
|
+
loop, so use it as an async context manager (or ``await .connect()``):
|
|
131
|
+
|
|
132
|
+
>>> async with AsyncBridge(ble=False) as esp:
|
|
133
|
+
... await esp.gpio.write(2, 1)
|
|
134
|
+
|
|
135
|
+
The wrapped synchronous bridge is available as ``.bridge`` for anything not
|
|
136
|
+
surfaced here (events, ``on_event``, device drivers).
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(self, *args, **kwargs):
|
|
140
|
+
# Bridge() connects (and blocks) in its constructor, so defer it. Args
|
|
141
|
+
# are passed straight through, so AsyncBridge("relays") selects a board
|
|
142
|
+
# exactly the way Bridge("relays") does.
|
|
143
|
+
self._args, self._kwargs = args, kwargs
|
|
144
|
+
self._bridge = None
|
|
145
|
+
self._owned = True # we opened it, so we close it
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def wrap(cls, bridge: Bridge) -> AsyncBridge:
|
|
149
|
+
"""Wrap an already-connected Bridge for ``await`` use, sharing its link.
|
|
150
|
+
|
|
151
|
+
Lets an async app reuse the one shared connection instead of opening a
|
|
152
|
+
second: ``esp = AsyncBridge.wrap(esplink.connect(ble=False))``.
|
|
153
|
+
``close()`` is then a no-op for the wrapper — whoever owns the Bridge
|
|
154
|
+
(e.g. the BridgeManager behind connect()) closes it.
|
|
155
|
+
"""
|
|
156
|
+
self = cls.__new__(cls)
|
|
157
|
+
self._args, self._kwargs = (), {}
|
|
158
|
+
self._bridge = bridge
|
|
159
|
+
self._owned = False
|
|
160
|
+
return self
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def bridge(self) -> Bridge:
|
|
164
|
+
"""The underlying synchronous :class:`Bridge` (for events, ``on_event``,
|
|
165
|
+
device drivers, or any method not surfaced here). Raises if not yet
|
|
166
|
+
connected."""
|
|
167
|
+
if self._bridge is None:
|
|
168
|
+
raise RuntimeError(
|
|
169
|
+
"AsyncBridge is not connected — use `async with AsyncBridge(...)` "
|
|
170
|
+
"or `await bridge.connect()`"
|
|
171
|
+
)
|
|
172
|
+
return self._bridge
|
|
173
|
+
|
|
174
|
+
async def connect(self) -> AsyncBridge:
|
|
175
|
+
"""Open the link off the event loop (Bridge() blocks while connecting).
|
|
176
|
+
Usually you don't call this directly — use ``async with AsyncBridge():``."""
|
|
177
|
+
if self._bridge is None:
|
|
178
|
+
self._bridge = await asyncio.to_thread(Bridge, *self._args,
|
|
179
|
+
**self._kwargs)
|
|
180
|
+
return self
|
|
181
|
+
|
|
182
|
+
async def close(self) -> None:
|
|
183
|
+
"""Close the link off the event loop. No-op for a wrapper created with
|
|
184
|
+
:meth:`wrap` (whoever owns the underlying Bridge closes it)."""
|
|
185
|
+
if self._bridge is not None and self._owned:
|
|
186
|
+
await asyncio.to_thread(self._bridge.close)
|
|
187
|
+
self._bridge = None
|
|
188
|
+
|
|
189
|
+
async def __aenter__(self) -> AsyncBridge:
|
|
190
|
+
return await self.connect()
|
|
191
|
+
|
|
192
|
+
async def __aexit__(self, *exc) -> None:
|
|
193
|
+
await self.close()
|
|
194
|
+
|
|
195
|
+
def events(self, cmd: int, *, parse=None, maxsize: int = 0) -> _EventStream:
|
|
196
|
+
"""``async for`` over a raw firmware event ``cmd`` (payloads are bytes
|
|
197
|
+
unless ``parse`` is given). See ``gpio_events()`` for the GPIO edge case."""
|
|
198
|
+
return _EventStream(self.bridge, cmd, asyncio.get_running_loop(),
|
|
199
|
+
parse=parse, maxsize=maxsize)
|
|
200
|
+
|
|
201
|
+
def gpio_events(self, *, maxsize: int = 0) -> _EventStream:
|
|
202
|
+
"""``async for ev in esp.gpio_events()`` yields :class:`EdgeEvent`s from
|
|
203
|
+
every pin armed with ``await esp.gpio.watch(pin, ...)``."""
|
|
204
|
+
return self.events(C.GPIO_EDGE_EVT, parse=EdgeEvent.parse, maxsize=maxsize)
|
|
205
|
+
|
|
206
|
+
def watch_events(self, *, maxsize: int = 0) -> _EventStream:
|
|
207
|
+
"""``async for ev in esp.watch_events()`` yields :class:`WatchEvent`s
|
|
208
|
+
from every rule defined with ``await esp.watch.add(...)`` — the polled,
|
|
209
|
+
non-interrupt, threshold-based events."""
|
|
210
|
+
return self.events(C.WATCH_EVT, parse=WatchEvent.parse, maxsize=maxsize)
|
|
211
|
+
|
|
212
|
+
def __getattr__(self, name):
|
|
213
|
+
if name.startswith("_"): # never proxy private/dunder lookups
|
|
214
|
+
raise AttributeError(name)
|
|
215
|
+
attr = getattr(self.bridge, name)
|
|
216
|
+
if name in Bridge._SUBAPIS:
|
|
217
|
+
proxy = _AsyncSubApi(attr)
|
|
218
|
+
setattr(self, name, proxy) # cache: this name skips __getattr__ next time
|
|
219
|
+
return proxy
|
|
220
|
+
return _async_attr(attr)
|
|
221
|
+
|
|
222
|
+
def __dir__(self):
|
|
223
|
+
extra = (*Bridge._SUBAPIS, "bridge", "connect", "close",
|
|
224
|
+
"events", "gpio_events", "watch_events")
|
|
225
|
+
return [*super().__dir__(), *extra]
|