esp32io-mqtt 0.1.0__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.
- esp32io_mqtt-0.1.0/LICENSE +21 -0
- esp32io_mqtt-0.1.0/PKG-INFO +233 -0
- esp32io_mqtt-0.1.0/README.md +209 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt/__init__.py +17 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt/client.py +345 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt/exceptions.py +19 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt/protocol.py +68 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt/py.typed +0 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt.egg-info/PKG-INFO +233 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt.egg-info/SOURCES.txt +13 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt.egg-info/dependency_links.txt +1 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt.egg-info/requires.txt +1 -0
- esp32io_mqtt-0.1.0/esp32io_mqtt.egg-info/top_level.txt +1 -0
- esp32io_mqtt-0.1.0/pyproject.toml +40 -0
- esp32io_mqtt-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Noritama-Lab
|
|
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,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: esp32io-mqtt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight Python MQTT client for controlling ESP32IO-MQTT devices (Home Assistant compatible).
|
|
5
|
+
Author: Noritama-Lab
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/Noritama-Lab/esp32io-mqtt-api
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
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
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: paho-mqtt<2.0,>=1.6
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# ESP32IO-MQTT Python MQTT Client
|
|
26
|
+
|
|
27
|
+
ESP32IO-MQTT is a lightweight Python **MQTT** client for controlling [ESP32IO-MQTT](https://github.com/Noritama-Lab/esp32io-mqtt-firmware) devices.
|
|
28
|
+
|
|
29
|
+
日本語 / English
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 日本語
|
|
34
|
+
|
|
35
|
+
ESP32IO-MQTT Pythonクライアントは、**MQTTブローカー** 経由で ESP32IO-MQTT ファームウェアを制御するための軽量Pythonクライアントです。
|
|
36
|
+
|
|
37
|
+
ESP32IO-MQTT ファームウェアはデバイス制御を **MQTT** 経由で行います。本クライアントもMQTT専用です。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 特徴
|
|
42
|
+
|
|
43
|
+
- MQTTブローカー経由での接続のみをサポート(HTTP/シリアルは非対応)
|
|
44
|
+
- 汎用JSONコマンドチャネル (`<base>/cmd` → `<base>/cmd/result`) を使った同期API (`read_di()`, `set_do()`, `get_io_state()` 等)
|
|
45
|
+
- 個別エンティティのトピック (Home Assistant MQTT Discovery互換) にも対応
|
|
46
|
+
- `get_cached_state()` で直近受信した状態のスナップショットを取得
|
|
47
|
+
- `on_state_change()` で状態変化のコールバックを登録
|
|
48
|
+
- `publish_do()` / `publish_pwm()` / `publish_rgb()` / `publish_led_off()` / `publish_led_mode()` / `publish_oled()` で応答待ちなしに直接publish
|
|
49
|
+
- I2Cスキャン、リード/ライト、BME280/MPU6050/VL53L1Xなどのセンサー一括取得、OLED表示をサポート
|
|
50
|
+
- PWM周波数・分解能の取得/更新に対応
|
|
51
|
+
- 通信エラー・タイムアウト・プロトコル不整合・デバイスエラーなど統一例外を定義
|
|
52
|
+
- 任意JSONを送れる低レベルAPI `command()` を提供
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 動作環境
|
|
57
|
+
|
|
58
|
+
- Python 3.8 以上
|
|
59
|
+
- `paho-mqtt>=1.6,<2.0`
|
|
60
|
+
- MQTTブローカー (Mosquitto等)
|
|
61
|
+
- ESP32IO-MQTT ファームウェア(MQTT対応)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## インストール
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install -r requirements.txt
|
|
69
|
+
# または
|
|
70
|
+
pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## クイックスタート
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from esp32io_mqtt import ESP32S3IOMqtt
|
|
79
|
+
|
|
80
|
+
# DEVICE_ID は設定ポータル (http://<device_ip>/) のページタイトルや
|
|
81
|
+
# mDNSホスト名 (ESP32IO_MQTT_XXXXXX) で確認できます。
|
|
82
|
+
with ESP32S3IOMqtt("ESP32IO_MQTT_XXXXXX", "192.168.1.5", debug=False) as esp:
|
|
83
|
+
print("ping =", esp.ping())
|
|
84
|
+
print("di0 =", esp.read_di(0))
|
|
85
|
+
print("adc0 =", esp.read_adc(0))
|
|
86
|
+
print("pwm config =", esp.get_pwm_config())
|
|
87
|
+
|
|
88
|
+
esp.set_do(0, 1)
|
|
89
|
+
esp.set_pwm(0, 128)
|
|
90
|
+
|
|
91
|
+
print(esp.get_io_state())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
サンプル実行:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
py -m examples.mqtt_example
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 通信方式
|
|
103
|
+
|
|
104
|
+
### 1. 汎用JSONコマンドチャネル (同期API)
|
|
105
|
+
|
|
106
|
+
`command()` および `read_di()` / `set_do()` などの高レベルメソッドは、`<base>/cmd` にJSONをpublishし `<base>/cmd/result` の応答を待つ同期方式です。戻り値やエラー(`ESP32IODeviceError`)で結果を確認できます。
|
|
107
|
+
|
|
108
|
+
### 2. 個別エンティティのトピック (Home Assistant MQTT Discovery互換)
|
|
109
|
+
|
|
110
|
+
接続時に `di/+/state` `do/+/state` `pwm/+/state` `rgb/state` `led_mode/state` `oled/state` `bme280/#` `mpu6050/#` `vl53l1x/#` `diag/#` `status` を自動的にsubscribeし、受信するたびに内部キャッシュへ保存します。
|
|
111
|
+
|
|
112
|
+
- `get_cached_state()` — 直近に受信した値のスナップショット (dict) を取得
|
|
113
|
+
- `on_state_change(callback)` — 状態トピックを受信するたびに `callback(topic_suffix, payload)` を呼び出す
|
|
114
|
+
- `is_online` — LWTトピック (`<base>/status`) が `online` かどうか
|
|
115
|
+
- `publish_do(pin_id, value)` / `publish_pwm(pin_id, duty)` / `publish_rgb(r, g, b, brightness)` / `publish_led_off()` / `publish_led_mode(mode)` / `publish_oled(text)` — 応答を待たずに直接publish(Home Assistant側からの操作と同じ経路)
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## API 一覧(主要メソッド)
|
|
120
|
+
|
|
121
|
+
- `ESP32S3IOMqtt(device_id, host, port=1883, user=None, password=None, topic_root="esp32io", keepalive=30, cmd_timeout=5.0, connect_timeout=10.0, debug=False)`
|
|
122
|
+
- `ping()`
|
|
123
|
+
- `read_di(pin_id)` / `set_do(pin_id, value)`
|
|
124
|
+
- `read_adc(pin_id)`
|
|
125
|
+
- `set_pwm(pin_id, duty)`
|
|
126
|
+
- `get_pwm_config()` / `set_pwm_config(freq, res)`
|
|
127
|
+
- `set_led_mode(mode)` / `set_rgb(r, g, b, brightness)` / `led_off()` / `get_led_state()`
|
|
128
|
+
- `i2c_scan()` / `i2c_read(addr, length)` / `i2c_write(addr, data)`
|
|
129
|
+
- `get_sensors()`
|
|
130
|
+
- `set_oled(text, x, y, size, clear)`
|
|
131
|
+
- `get_io_state()` / `get_status()`
|
|
132
|
+
- `command(cmd, **kwargs)`
|
|
133
|
+
- `help()`
|
|
134
|
+
- `get_cached_state()` / `on_state_change(callback)` / `is_online`
|
|
135
|
+
- `publish_do()` / `publish_pwm()` / `publish_rgb()` / `publish_led_off()` / `publish_led_mode()` / `publish_oled()`
|
|
136
|
+
- `close()`
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 例外
|
|
141
|
+
|
|
142
|
+
- `ESP32IOError`(基底クラス)
|
|
143
|
+
- `ESP32IOMqttError` — ブローカー接続・publish失敗
|
|
144
|
+
- `ESP32IOTimeoutError` — `cmd/result` 応答待ちのタイムアウト(`ESP32IOMqttError`のサブクラス)
|
|
145
|
+
- `ESP32IOProtocolError` — デバイスから不正な応答
|
|
146
|
+
- `ESP32IODeviceError` — デバイスが `status=error` を返した場合
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## プロジェクト構成
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
.
|
|
154
|
+
├── esp32io_mqtt/
|
|
155
|
+
│ ├── __init__.py
|
|
156
|
+
│ ├── client.py
|
|
157
|
+
│ ├── exceptions.py
|
|
158
|
+
│ └── protocol.py
|
|
159
|
+
├── examples/
|
|
160
|
+
│ ├── mqtt_example.py
|
|
161
|
+
│ └── i2c_example.py
|
|
162
|
+
├── pyproject.toml
|
|
163
|
+
├── requirements.txt
|
|
164
|
+
├── LICENSE
|
|
165
|
+
└── README.md
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## ファームウェア
|
|
171
|
+
|
|
172
|
+
対応ファームウェア: [ESP32IO-MQTT](https://github.com/Noritama-Lab/esp32io-mqtt-firmware)(MQTT + Home Assistant MQTT Discovery対応)。
|
|
173
|
+
トピック構成・汎用JSONコマンド一覧は同リポジトリの `COMMAND_REFERENCE.md` を参照してください。
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## ライセンス
|
|
178
|
+
|
|
179
|
+
MIT License
|
|
180
|
+
Copyright (c) 2026 Noritama-Lab
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## English
|
|
185
|
+
|
|
186
|
+
ESP32IO-MQTT Python client is a lightweight **MQTT** client for controlling [ESP32IO-MQTT](https://github.com/Noritama-Lab/esp32io-mqtt-firmware) firmware.
|
|
187
|
+
|
|
188
|
+
ESP32IO-MQTT firmware controls devices over **MQTT**. This client is MQTT-only.
|
|
189
|
+
|
|
190
|
+
### Features
|
|
191
|
+
|
|
192
|
+
- MQTT broker connection only (no HTTP/Serial support)
|
|
193
|
+
- Synchronous API over the generic JSON command channel (`<base>/cmd` → `<base>/cmd/result`) (`read_di()`, `set_do()`, `get_io_state()`, etc.)
|
|
194
|
+
- Support for individual entity topics (Home Assistant MQTT Discovery compatible)
|
|
195
|
+
- `get_cached_state()` for a snapshot of the latest received state
|
|
196
|
+
- `on_state_change()` to register a callback for state changes
|
|
197
|
+
- `publish_do()` / `publish_pwm()` / `publish_rgb()` / `publish_led_off()` / `publish_led_mode()` / `publish_oled()` to publish directly without waiting for a response
|
|
198
|
+
- I2C scanning, raw read/write, sensor batch reads (BME280/MPU6050/VL53L1X), and OLED control
|
|
199
|
+
- PWM frequency/resolution read & update
|
|
200
|
+
- Unified exceptions for MQTT, protocol, timeout, and device errors
|
|
201
|
+
- Low-level `command()` API for custom JSON commands
|
|
202
|
+
|
|
203
|
+
### Requirements
|
|
204
|
+
|
|
205
|
+
- Python 3.8+
|
|
206
|
+
- `paho-mqtt>=1.6,<2.0`
|
|
207
|
+
- An MQTT broker (e.g. Mosquitto)
|
|
208
|
+
- ESP32IO-MQTT firmware (MQTT-based)
|
|
209
|
+
|
|
210
|
+
### Installation
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pip install -r requirements.txt
|
|
214
|
+
# or
|
|
215
|
+
pip install -e .
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Quick Start
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from esp32io_mqtt import ESP32S3IOMqtt
|
|
222
|
+
|
|
223
|
+
with ESP32S3IOMqtt("ESP32IO_MQTT_XXXXXX", "192.168.1.5", debug=False) as esp:
|
|
224
|
+
print("ping =", esp.ping())
|
|
225
|
+
print("di0 =", esp.read_di(0))
|
|
226
|
+
esp.set_do(0, 1)
|
|
227
|
+
print(esp.get_io_state())
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### License
|
|
231
|
+
|
|
232
|
+
MIT License
|
|
233
|
+
Copyright (c) 2026 Noritama-Lab
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# ESP32IO-MQTT Python MQTT Client
|
|
2
|
+
|
|
3
|
+
ESP32IO-MQTT is a lightweight Python **MQTT** client for controlling [ESP32IO-MQTT](https://github.com/Noritama-Lab/esp32io-mqtt-firmware) devices.
|
|
4
|
+
|
|
5
|
+
日本語 / English
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 日本語
|
|
10
|
+
|
|
11
|
+
ESP32IO-MQTT Pythonクライアントは、**MQTTブローカー** 経由で ESP32IO-MQTT ファームウェアを制御するための軽量Pythonクライアントです。
|
|
12
|
+
|
|
13
|
+
ESP32IO-MQTT ファームウェアはデバイス制御を **MQTT** 経由で行います。本クライアントもMQTT専用です。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 特徴
|
|
18
|
+
|
|
19
|
+
- MQTTブローカー経由での接続のみをサポート(HTTP/シリアルは非対応)
|
|
20
|
+
- 汎用JSONコマンドチャネル (`<base>/cmd` → `<base>/cmd/result`) を使った同期API (`read_di()`, `set_do()`, `get_io_state()` 等)
|
|
21
|
+
- 個別エンティティのトピック (Home Assistant MQTT Discovery互換) にも対応
|
|
22
|
+
- `get_cached_state()` で直近受信した状態のスナップショットを取得
|
|
23
|
+
- `on_state_change()` で状態変化のコールバックを登録
|
|
24
|
+
- `publish_do()` / `publish_pwm()` / `publish_rgb()` / `publish_led_off()` / `publish_led_mode()` / `publish_oled()` で応答待ちなしに直接publish
|
|
25
|
+
- I2Cスキャン、リード/ライト、BME280/MPU6050/VL53L1Xなどのセンサー一括取得、OLED表示をサポート
|
|
26
|
+
- PWM周波数・分解能の取得/更新に対応
|
|
27
|
+
- 通信エラー・タイムアウト・プロトコル不整合・デバイスエラーなど統一例外を定義
|
|
28
|
+
- 任意JSONを送れる低レベルAPI `command()` を提供
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 動作環境
|
|
33
|
+
|
|
34
|
+
- Python 3.8 以上
|
|
35
|
+
- `paho-mqtt>=1.6,<2.0`
|
|
36
|
+
- MQTTブローカー (Mosquitto等)
|
|
37
|
+
- ESP32IO-MQTT ファームウェア(MQTT対応)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## インストール
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install -r requirements.txt
|
|
45
|
+
# または
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## クイックスタート
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from esp32io_mqtt import ESP32S3IOMqtt
|
|
55
|
+
|
|
56
|
+
# DEVICE_ID は設定ポータル (http://<device_ip>/) のページタイトルや
|
|
57
|
+
# mDNSホスト名 (ESP32IO_MQTT_XXXXXX) で確認できます。
|
|
58
|
+
with ESP32S3IOMqtt("ESP32IO_MQTT_XXXXXX", "192.168.1.5", debug=False) as esp:
|
|
59
|
+
print("ping =", esp.ping())
|
|
60
|
+
print("di0 =", esp.read_di(0))
|
|
61
|
+
print("adc0 =", esp.read_adc(0))
|
|
62
|
+
print("pwm config =", esp.get_pwm_config())
|
|
63
|
+
|
|
64
|
+
esp.set_do(0, 1)
|
|
65
|
+
esp.set_pwm(0, 128)
|
|
66
|
+
|
|
67
|
+
print(esp.get_io_state())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
サンプル実行:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
py -m examples.mqtt_example
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 通信方式
|
|
79
|
+
|
|
80
|
+
### 1. 汎用JSONコマンドチャネル (同期API)
|
|
81
|
+
|
|
82
|
+
`command()` および `read_di()` / `set_do()` などの高レベルメソッドは、`<base>/cmd` にJSONをpublishし `<base>/cmd/result` の応答を待つ同期方式です。戻り値やエラー(`ESP32IODeviceError`)で結果を確認できます。
|
|
83
|
+
|
|
84
|
+
### 2. 個別エンティティのトピック (Home Assistant MQTT Discovery互換)
|
|
85
|
+
|
|
86
|
+
接続時に `di/+/state` `do/+/state` `pwm/+/state` `rgb/state` `led_mode/state` `oled/state` `bme280/#` `mpu6050/#` `vl53l1x/#` `diag/#` `status` を自動的にsubscribeし、受信するたびに内部キャッシュへ保存します。
|
|
87
|
+
|
|
88
|
+
- `get_cached_state()` — 直近に受信した値のスナップショット (dict) を取得
|
|
89
|
+
- `on_state_change(callback)` — 状態トピックを受信するたびに `callback(topic_suffix, payload)` を呼び出す
|
|
90
|
+
- `is_online` — LWTトピック (`<base>/status`) が `online` かどうか
|
|
91
|
+
- `publish_do(pin_id, value)` / `publish_pwm(pin_id, duty)` / `publish_rgb(r, g, b, brightness)` / `publish_led_off()` / `publish_led_mode(mode)` / `publish_oled(text)` — 応答を待たずに直接publish(Home Assistant側からの操作と同じ経路)
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## API 一覧(主要メソッド)
|
|
96
|
+
|
|
97
|
+
- `ESP32S3IOMqtt(device_id, host, port=1883, user=None, password=None, topic_root="esp32io", keepalive=30, cmd_timeout=5.0, connect_timeout=10.0, debug=False)`
|
|
98
|
+
- `ping()`
|
|
99
|
+
- `read_di(pin_id)` / `set_do(pin_id, value)`
|
|
100
|
+
- `read_adc(pin_id)`
|
|
101
|
+
- `set_pwm(pin_id, duty)`
|
|
102
|
+
- `get_pwm_config()` / `set_pwm_config(freq, res)`
|
|
103
|
+
- `set_led_mode(mode)` / `set_rgb(r, g, b, brightness)` / `led_off()` / `get_led_state()`
|
|
104
|
+
- `i2c_scan()` / `i2c_read(addr, length)` / `i2c_write(addr, data)`
|
|
105
|
+
- `get_sensors()`
|
|
106
|
+
- `set_oled(text, x, y, size, clear)`
|
|
107
|
+
- `get_io_state()` / `get_status()`
|
|
108
|
+
- `command(cmd, **kwargs)`
|
|
109
|
+
- `help()`
|
|
110
|
+
- `get_cached_state()` / `on_state_change(callback)` / `is_online`
|
|
111
|
+
- `publish_do()` / `publish_pwm()` / `publish_rgb()` / `publish_led_off()` / `publish_led_mode()` / `publish_oled()`
|
|
112
|
+
- `close()`
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 例外
|
|
117
|
+
|
|
118
|
+
- `ESP32IOError`(基底クラス)
|
|
119
|
+
- `ESP32IOMqttError` — ブローカー接続・publish失敗
|
|
120
|
+
- `ESP32IOTimeoutError` — `cmd/result` 応答待ちのタイムアウト(`ESP32IOMqttError`のサブクラス)
|
|
121
|
+
- `ESP32IOProtocolError` — デバイスから不正な応答
|
|
122
|
+
- `ESP32IODeviceError` — デバイスが `status=error` を返した場合
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## プロジェクト構成
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
.
|
|
130
|
+
├── esp32io_mqtt/
|
|
131
|
+
│ ├── __init__.py
|
|
132
|
+
│ ├── client.py
|
|
133
|
+
│ ├── exceptions.py
|
|
134
|
+
│ └── protocol.py
|
|
135
|
+
├── examples/
|
|
136
|
+
│ ├── mqtt_example.py
|
|
137
|
+
│ └── i2c_example.py
|
|
138
|
+
├── pyproject.toml
|
|
139
|
+
├── requirements.txt
|
|
140
|
+
├── LICENSE
|
|
141
|
+
└── README.md
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## ファームウェア
|
|
147
|
+
|
|
148
|
+
対応ファームウェア: [ESP32IO-MQTT](https://github.com/Noritama-Lab/esp32io-mqtt-firmware)(MQTT + Home Assistant MQTT Discovery対応)。
|
|
149
|
+
トピック構成・汎用JSONコマンド一覧は同リポジトリの `COMMAND_REFERENCE.md` を参照してください。
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## ライセンス
|
|
154
|
+
|
|
155
|
+
MIT License
|
|
156
|
+
Copyright (c) 2026 Noritama-Lab
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## English
|
|
161
|
+
|
|
162
|
+
ESP32IO-MQTT Python client is a lightweight **MQTT** client for controlling [ESP32IO-MQTT](https://github.com/Noritama-Lab/esp32io-mqtt-firmware) firmware.
|
|
163
|
+
|
|
164
|
+
ESP32IO-MQTT firmware controls devices over **MQTT**. This client is MQTT-only.
|
|
165
|
+
|
|
166
|
+
### Features
|
|
167
|
+
|
|
168
|
+
- MQTT broker connection only (no HTTP/Serial support)
|
|
169
|
+
- Synchronous API over the generic JSON command channel (`<base>/cmd` → `<base>/cmd/result`) (`read_di()`, `set_do()`, `get_io_state()`, etc.)
|
|
170
|
+
- Support for individual entity topics (Home Assistant MQTT Discovery compatible)
|
|
171
|
+
- `get_cached_state()` for a snapshot of the latest received state
|
|
172
|
+
- `on_state_change()` to register a callback for state changes
|
|
173
|
+
- `publish_do()` / `publish_pwm()` / `publish_rgb()` / `publish_led_off()` / `publish_led_mode()` / `publish_oled()` to publish directly without waiting for a response
|
|
174
|
+
- I2C scanning, raw read/write, sensor batch reads (BME280/MPU6050/VL53L1X), and OLED control
|
|
175
|
+
- PWM frequency/resolution read & update
|
|
176
|
+
- Unified exceptions for MQTT, protocol, timeout, and device errors
|
|
177
|
+
- Low-level `command()` API for custom JSON commands
|
|
178
|
+
|
|
179
|
+
### Requirements
|
|
180
|
+
|
|
181
|
+
- Python 3.8+
|
|
182
|
+
- `paho-mqtt>=1.6,<2.0`
|
|
183
|
+
- An MQTT broker (e.g. Mosquitto)
|
|
184
|
+
- ESP32IO-MQTT firmware (MQTT-based)
|
|
185
|
+
|
|
186
|
+
### Installation
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
pip install -r requirements.txt
|
|
190
|
+
# or
|
|
191
|
+
pip install -e .
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Quick Start
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from esp32io_mqtt import ESP32S3IOMqtt
|
|
198
|
+
|
|
199
|
+
with ESP32S3IOMqtt("ESP32IO_MQTT_XXXXXX", "192.168.1.5", debug=False) as esp:
|
|
200
|
+
print("ping =", esp.ping())
|
|
201
|
+
print("di0 =", esp.read_di(0))
|
|
202
|
+
esp.set_do(0, 1)
|
|
203
|
+
print(esp.get_io_state())
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### License
|
|
207
|
+
|
|
208
|
+
MIT License
|
|
209
|
+
Copyright (c) 2026 Noritama-Lab
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .client import ESP32S3IOMqtt
|
|
2
|
+
from .exceptions import (
|
|
3
|
+
ESP32IOError,
|
|
4
|
+
ESP32IODeviceError,
|
|
5
|
+
ESP32IOMqttError,
|
|
6
|
+
ESP32IOTimeoutError,
|
|
7
|
+
ESP32IOProtocolError,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ESP32S3IOMqtt",
|
|
12
|
+
"ESP32IOError",
|
|
13
|
+
"ESP32IODeviceError",
|
|
14
|
+
"ESP32IOMqttError",
|
|
15
|
+
"ESP32IOTimeoutError",
|
|
16
|
+
"ESP32IOProtocolError",
|
|
17
|
+
]
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Optional, Dict, Any, List, Literal, Callable
|
|
6
|
+
|
|
7
|
+
import paho.mqtt.client as mqtt
|
|
8
|
+
|
|
9
|
+
from .exceptions import (
|
|
10
|
+
ESP32IODeviceError,
|
|
11
|
+
ESP32IOMqttError,
|
|
12
|
+
ESP32IOTimeoutError,
|
|
13
|
+
ESP32IOProtocolError,
|
|
14
|
+
)
|
|
15
|
+
from . import protocol
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ESP32S3IOMqtt:
|
|
21
|
+
"""MQTTブローカー経由でESP32IO-MQTTデバイスを操作するクライアント。
|
|
22
|
+
|
|
23
|
+
デバイス制御には2通りの経路がある:
|
|
24
|
+
- 汎用JSONコマンドチャネル (``command()`` および各 ``read_*``/``set_*`` メソッド):
|
|
25
|
+
`<base>/cmd` にpublishし `<base>/cmd/result` の応答を同期的に待つ。旧HTTP/Serial版と同じ使用感。
|
|
26
|
+
- 個別エンティティのトピック (Home Assistant MQTT Discovery互換):
|
|
27
|
+
`get_cached_state()` で直近の受信値を参照でき、``publish_*`` 系メソッドで
|
|
28
|
+
応答待ちなしに状態変更を発行できる。
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
device_id: str,
|
|
34
|
+
host: str,
|
|
35
|
+
port: int = 1883,
|
|
36
|
+
user: Optional[str] = None,
|
|
37
|
+
password: Optional[str] = None,
|
|
38
|
+
topic_root: str = protocol.DEFAULT_TOPIC_ROOT,
|
|
39
|
+
keepalive: int = 30,
|
|
40
|
+
cmd_timeout: float = 5.0,
|
|
41
|
+
connect_timeout: float = 10.0,
|
|
42
|
+
debug: bool = False,
|
|
43
|
+
):
|
|
44
|
+
if debug:
|
|
45
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
46
|
+
|
|
47
|
+
self.device_id = device_id
|
|
48
|
+
self.base = f"{topic_root}/{device_id}"
|
|
49
|
+
self.cmd_timeout = cmd_timeout
|
|
50
|
+
|
|
51
|
+
self._state: Dict[str, str] = {}
|
|
52
|
+
self._state_lock = threading.Lock()
|
|
53
|
+
self._listeners: List[Callable[[str, str], None]] = []
|
|
54
|
+
|
|
55
|
+
self._connected_event = threading.Event()
|
|
56
|
+
self._cmd_lock = threading.Lock() # 汎用コマンドチャネルは一度に1件のみ処理
|
|
57
|
+
self._cmd_response_event = threading.Event()
|
|
58
|
+
self._cmd_response: Optional[Dict[str, Any]] = None
|
|
59
|
+
|
|
60
|
+
self._client = mqtt.Client(client_id=f"esp32io-mqtt-py-{uuid.uuid4().hex[:8]}")
|
|
61
|
+
if user:
|
|
62
|
+
self._client.username_pw_set(user, password or "")
|
|
63
|
+
self._client.on_connect = self._on_connect
|
|
64
|
+
self._client.on_disconnect = self._on_disconnect
|
|
65
|
+
self._client.on_message = self._on_message
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
self._client.connect(host, port, keepalive=keepalive)
|
|
69
|
+
except OSError as e:
|
|
70
|
+
raise ESP32IOMqttError(f"Failed to connect to broker {host}:{port}: {e}")
|
|
71
|
+
|
|
72
|
+
self._client.loop_start()
|
|
73
|
+
if not self._connected_event.wait(connect_timeout):
|
|
74
|
+
self._client.loop_stop()
|
|
75
|
+
raise ESP32IOMqttError(f"Timed out connecting to broker {host}:{port}")
|
|
76
|
+
|
|
77
|
+
def __enter__(self):
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
def __exit__(self, _exc_type, _exc_val, _exc_tb):
|
|
81
|
+
self.close()
|
|
82
|
+
|
|
83
|
+
def _log(self, *args: Any) -> None:
|
|
84
|
+
logger.debug(" ".join(map(str, args)))
|
|
85
|
+
|
|
86
|
+
# ------------------------------------------------------------------
|
|
87
|
+
# 接続 / 購読 / 状態キャッシュ
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
def _on_connect(self, client, _userdata, _flags, rc):
|
|
90
|
+
if rc != 0:
|
|
91
|
+
logger.error("MQTT connect failed rc=%s", rc)
|
|
92
|
+
return
|
|
93
|
+
client.subscribe(f"{self.base}/cmd/result")
|
|
94
|
+
for pattern in protocol.ENTITY_SUBSCRIBE_TOPICS:
|
|
95
|
+
client.subscribe(pattern.format(base=self.base))
|
|
96
|
+
self._connected_event.set()
|
|
97
|
+
|
|
98
|
+
def _on_disconnect(self, _client, _userdata, _rc):
|
|
99
|
+
self._connected_event.clear()
|
|
100
|
+
|
|
101
|
+
def _on_message(self, _client, _userdata, msg):
|
|
102
|
+
payload = msg.payload.decode("utf-8", errors="replace")
|
|
103
|
+
self._log(f"RECV: {msg.topic} {payload}")
|
|
104
|
+
|
|
105
|
+
if msg.topic == f"{self.base}/cmd/result":
|
|
106
|
+
try:
|
|
107
|
+
self._cmd_response = json.loads(payload)
|
|
108
|
+
except json.JSONDecodeError:
|
|
109
|
+
logger.warning("Invalid JSON on cmd/result: %s", payload)
|
|
110
|
+
return
|
|
111
|
+
self._cmd_response_event.set()
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
suffix = msg.topic[len(self.base) + 1:]
|
|
115
|
+
with self._state_lock:
|
|
116
|
+
self._state[suffix] = payload
|
|
117
|
+
for listener in list(self._listeners):
|
|
118
|
+
try:
|
|
119
|
+
listener(suffix, payload)
|
|
120
|
+
except Exception:
|
|
121
|
+
logger.exception("State listener raised an exception")
|
|
122
|
+
|
|
123
|
+
def on_state_change(self, callback: Callable[[str, str], None]) -> None:
|
|
124
|
+
"""個別エンティティのトピック受信ごとに ``callback(topic_suffix, payload)`` を呼び出す。"""
|
|
125
|
+
self._listeners.append(callback)
|
|
126
|
+
|
|
127
|
+
def get_cached_state(self) -> Dict[str, str]:
|
|
128
|
+
"""個別エンティティのトピックから直近に受信した値のスナップショットを返す。"""
|
|
129
|
+
with self._state_lock:
|
|
130
|
+
return dict(self._state)
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def is_online(self) -> bool:
|
|
134
|
+
"""デバイスのLWTトピック (`<base>/status`) が ``online`` かどうか。"""
|
|
135
|
+
return self._state.get("status") == "online"
|
|
136
|
+
|
|
137
|
+
# ------------------------------------------------------------------
|
|
138
|
+
# 汎用JSONコマンドチャネル (`<base>/cmd` -> `<base>/cmd/result`)
|
|
139
|
+
# ------------------------------------------------------------------
|
|
140
|
+
def command(self, cmd: str, **kwargs: Any) -> Dict[str, Any]:
|
|
141
|
+
"""任意のコマンドを送信し、JSON応答を返す低レベルAPI"""
|
|
142
|
+
payload = {"cmd": cmd}
|
|
143
|
+
payload.update(kwargs)
|
|
144
|
+
return self._execute(payload)
|
|
145
|
+
|
|
146
|
+
def _execute(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
147
|
+
cmd_name = payload.get("cmd", "unknown")
|
|
148
|
+
with self._cmd_lock:
|
|
149
|
+
self._cmd_response = None
|
|
150
|
+
self._cmd_response_event.clear()
|
|
151
|
+
|
|
152
|
+
line = json.dumps(payload, separators=(",", ":"))
|
|
153
|
+
self._log(f"SEND: {line}")
|
|
154
|
+
info = self._client.publish(f"{self.base}/cmd", line, qos=0)
|
|
155
|
+
if info.rc != mqtt.MQTT_ERR_SUCCESS:
|
|
156
|
+
raise ESP32IOMqttError(f"Publish failed for '{cmd_name}': rc={info.rc}")
|
|
157
|
+
|
|
158
|
+
if not self._cmd_response_event.wait(self.cmd_timeout):
|
|
159
|
+
raise ESP32IOTimeoutError(f"Timeout waiting for response to '{cmd_name}'")
|
|
160
|
+
|
|
161
|
+
response = self._cmd_response
|
|
162
|
+
assert response is not None
|
|
163
|
+
return self._process_response(response, cmd_name)
|
|
164
|
+
|
|
165
|
+
def _process_response(self, data: Dict[str, Any], cmd: str) -> Dict[str, Any]:
|
|
166
|
+
if data.get("status") == "error":
|
|
167
|
+
code = data.get("code", "UNKNOWN")
|
|
168
|
+
detail = data.get("detail", "")
|
|
169
|
+
raise ESP32IODeviceError(f"Device error on '{cmd}': {code} ({detail})")
|
|
170
|
+
return data
|
|
171
|
+
|
|
172
|
+
def ping(self) -> bool:
|
|
173
|
+
"""疎通確認"""
|
|
174
|
+
res = self.command(protocol.CMD_PING)
|
|
175
|
+
return res.get("message") == "pong"
|
|
176
|
+
|
|
177
|
+
def get_status(self) -> Dict[str, Any]:
|
|
178
|
+
"""デバイスの稼働状況(起動時間、ヒープ、IP等)を取得"""
|
|
179
|
+
return self.command(protocol.CMD_GET_STATUS)
|
|
180
|
+
|
|
181
|
+
def get_io_state(self) -> Dict[str, Any]:
|
|
182
|
+
"""DI, DO, ADC, PWM の全状態を一括取得"""
|
|
183
|
+
res = self.command(protocol.CMD_GET_IO_STATE)
|
|
184
|
+
return {
|
|
185
|
+
"dio_in": list(res.get("dio_in", [])),
|
|
186
|
+
"dio_out": list(res.get("dio_out", [])),
|
|
187
|
+
"adc": list(res.get("adc", [])),
|
|
188
|
+
"pwm": list(res.get("pwm", [])),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
def read_di(self, pin_id: int) -> int:
|
|
192
|
+
"""デジタル入力の読み取り"""
|
|
193
|
+
if not isinstance(pin_id, int):
|
|
194
|
+
raise ValueError("pin_id must be an integer")
|
|
195
|
+
res = self.command(protocol.CMD_READ_DI, pin_id=pin_id)
|
|
196
|
+
val = res.get("value")
|
|
197
|
+
if not isinstance(val, int):
|
|
198
|
+
raise ESP32IOProtocolError(f"Invalid read_di response: {res}")
|
|
199
|
+
return val
|
|
200
|
+
|
|
201
|
+
def set_do(self, pin_id: int, value: int) -> bool:
|
|
202
|
+
"""デジタル出力の設定 (0 or 1)"""
|
|
203
|
+
if value not in (0, 1):
|
|
204
|
+
raise ValueError("value must be 0 or 1")
|
|
205
|
+
res = self.command(protocol.CMD_SET_DO, pin_id=pin_id, value=value)
|
|
206
|
+
return res.get("status") == "ok"
|
|
207
|
+
|
|
208
|
+
def read_adc(self, pin_id: int) -> int:
|
|
209
|
+
"""アナログ入力の読み取り"""
|
|
210
|
+
if not isinstance(pin_id, int):
|
|
211
|
+
raise ValueError("pin_id must be an integer")
|
|
212
|
+
res = self.command(protocol.CMD_READ_ADC, pin_id=pin_id)
|
|
213
|
+
val = res.get("value")
|
|
214
|
+
if not isinstance(val, int):
|
|
215
|
+
raise ESP32IOProtocolError(f"Invalid read_adc response: {res}")
|
|
216
|
+
return val
|
|
217
|
+
|
|
218
|
+
def set_pwm(self, pin_id: int, duty: int) -> bool:
|
|
219
|
+
"""PWM出力の設定"""
|
|
220
|
+
if duty < 0:
|
|
221
|
+
raise ValueError("duty must be 0 or greater")
|
|
222
|
+
res = self.command(protocol.CMD_SET_PWM, pin_id=pin_id, duty=duty)
|
|
223
|
+
return res.get("status") == "ok"
|
|
224
|
+
|
|
225
|
+
def get_pwm_config(self) -> Dict[str, int]:
|
|
226
|
+
"""現在のPWM周波数、分解能、および最大デューティを取得"""
|
|
227
|
+
res = self.command(protocol.CMD_GET_PWM_CONFIG)
|
|
228
|
+
if not all(isinstance(res.get(k), int) for k in ("freq", "res", "max_duty")):
|
|
229
|
+
raise ESP32IOProtocolError(f"Invalid get_pwm_config response: {res}")
|
|
230
|
+
return {"freq": res["freq"], "res": res["res"], "max_duty": res["max_duty"]}
|
|
231
|
+
|
|
232
|
+
def set_pwm_config(self, freq: int, res_bit: int) -> bool:
|
|
233
|
+
"""PWMの周波数と分解能を動的に変更"""
|
|
234
|
+
res = self.command(protocol.CMD_SET_PWM_CONFIG, freq=freq, res=res_bit)
|
|
235
|
+
return res.get("status") == "ok"
|
|
236
|
+
|
|
237
|
+
def set_rgb(self, r: int, g: int, b: int, brightness: Optional[int] = None) -> bool:
|
|
238
|
+
"""内蔵RGB LEDの制御"""
|
|
239
|
+
if any(not (0 <= c <= 255) for c in (r, g, b)):
|
|
240
|
+
raise ValueError("RGB values must be between 0 and 255")
|
|
241
|
+
kwargs = {"r": r, "g": g, "b": b}
|
|
242
|
+
if brightness is not None:
|
|
243
|
+
kwargs["brightness"] = brightness
|
|
244
|
+
res = self.command(protocol.CMD_SET_RGB, **kwargs)
|
|
245
|
+
return res.get("status") == "ok"
|
|
246
|
+
|
|
247
|
+
def led_off(self) -> bool:
|
|
248
|
+
"""RGB LEDの消灯"""
|
|
249
|
+
res = self.command(protocol.CMD_LED_OFF)
|
|
250
|
+
return res.get("status") == "ok"
|
|
251
|
+
|
|
252
|
+
def set_led_mode(self, mode: Literal["status", "manual"]) -> bool:
|
|
253
|
+
"""LEDの動作モードを設定 ('status' または 'manual')"""
|
|
254
|
+
res = self.command(protocol.CMD_SET_LED_MODE, mode=mode)
|
|
255
|
+
return res.get("status") == "ok"
|
|
256
|
+
|
|
257
|
+
def get_led_state(self) -> Dict[str, Any]:
|
|
258
|
+
"""現在のLED設定値を取得"""
|
|
259
|
+
return self.command(protocol.CMD_GET_LED_STATE)
|
|
260
|
+
|
|
261
|
+
def help(self) -> List[str]:
|
|
262
|
+
"""デバイスがサポートするコマンド一覧を取得"""
|
|
263
|
+
res = self.command(protocol.CMD_HELP)
|
|
264
|
+
return res.get("commands", [])
|
|
265
|
+
|
|
266
|
+
def i2c_scan(self) -> List[int]:
|
|
267
|
+
"""I2C バスをスキャンし、応答のあったデバイスのアドレス一覧を返す"""
|
|
268
|
+
res = self.command(protocol.CMD_I2C_SCAN)
|
|
269
|
+
return res.get("devices", [])
|
|
270
|
+
|
|
271
|
+
def i2c_read(self, addr: int, length: int) -> List[int]:
|
|
272
|
+
"""I2C デバイスからデータを読み取る"""
|
|
273
|
+
if not isinstance(addr, int) or not isinstance(length, int):
|
|
274
|
+
raise ValueError("addr and length must be integers")
|
|
275
|
+
res = self.command(protocol.CMD_I2C_READ, addr=addr, len=length)
|
|
276
|
+
data = res.get("data")
|
|
277
|
+
if not isinstance(data, list):
|
|
278
|
+
raise ESP32IOProtocolError(f"Invalid i2c_read response: {res}")
|
|
279
|
+
return data
|
|
280
|
+
|
|
281
|
+
def i2c_write(self, addr: int, data: List[int]) -> bool:
|
|
282
|
+
"""I2C デバイスにデータを書き込む"""
|
|
283
|
+
if not isinstance(addr, int) or not isinstance(data, list):
|
|
284
|
+
raise ValueError("addr must be int and data must be a list of integers")
|
|
285
|
+
res = self.command(protocol.CMD_I2C_WRITE, addr=addr, data=data)
|
|
286
|
+
return res.get("status") == "ok"
|
|
287
|
+
|
|
288
|
+
def get_sensors(self) -> Dict[str, Any]:
|
|
289
|
+
"""BME280, MPU6050, VL53L1X などの接続済みセンサー値を一括取得"""
|
|
290
|
+
return self.command(protocol.CMD_GET_SENSORS)
|
|
291
|
+
|
|
292
|
+
def set_oled(self, text: str, x: int = 0, y: int = 0, size: int = 1, clear: bool = True) -> bool:
|
|
293
|
+
"""SSD1306 OLED ディスプレイにテキストを表示"""
|
|
294
|
+
res = self.command(protocol.CMD_SET_OLED, text=text, x=x, y=y, size=size, clear=clear)
|
|
295
|
+
return res.get("status") == "ok"
|
|
296
|
+
|
|
297
|
+
# ------------------------------------------------------------------
|
|
298
|
+
# 個別エンティティの直接publish (応答待ちなし。Home Assistant側の操作と同等)
|
|
299
|
+
# ------------------------------------------------------------------
|
|
300
|
+
def publish_do(self, pin_id: int, value: int) -> None:
|
|
301
|
+
"""`<base>/do/<pin_id>/set` へ直接publishする(応答は待たない)"""
|
|
302
|
+
if value not in (0, 1):
|
|
303
|
+
raise ValueError("value must be 0 or 1")
|
|
304
|
+
self._client.publish(f"{self.base}/do/{pin_id}/set", "ON" if value else "OFF")
|
|
305
|
+
|
|
306
|
+
def publish_pwm(self, pin_id: int, duty: int) -> None:
|
|
307
|
+
"""`<base>/pwm/<pin_id>/set` へ直接publishする(応答は待たない)"""
|
|
308
|
+
if duty < 0:
|
|
309
|
+
raise ValueError("duty must be 0 or greater")
|
|
310
|
+
self._client.publish(f"{self.base}/pwm/{pin_id}/set", str(duty))
|
|
311
|
+
|
|
312
|
+
def publish_rgb(self, r: int, g: int, b: int, brightness: Optional[int] = None) -> None:
|
|
313
|
+
"""`<base>/rgb/set` へ直接publishする(応答は待たない)"""
|
|
314
|
+
if any(not (0 <= c <= 255) for c in (r, g, b)):
|
|
315
|
+
raise ValueError("RGB values must be between 0 and 255")
|
|
316
|
+
payload: Dict[str, Any] = {"state": "ON", "color": {"r": r, "g": g, "b": b}}
|
|
317
|
+
if brightness is not None:
|
|
318
|
+
payload["brightness"] = brightness
|
|
319
|
+
self._client.publish(f"{self.base}/rgb/set", json.dumps(payload))
|
|
320
|
+
|
|
321
|
+
def publish_led_off(self) -> None:
|
|
322
|
+
"""`<base>/rgb/set` へ消灯を直接publishする(応答は待たない)"""
|
|
323
|
+
self._client.publish(f"{self.base}/rgb/set", json.dumps({"state": "OFF"}))
|
|
324
|
+
|
|
325
|
+
def publish_led_mode(self, mode: Literal["status", "manual"]) -> None:
|
|
326
|
+
"""`<base>/led_mode/set` へ直接publishする(応答は待たない)"""
|
|
327
|
+
if mode not in ("status", "manual"):
|
|
328
|
+
raise ValueError('mode must be "status" or "manual"')
|
|
329
|
+
self._client.publish(f"{self.base}/led_mode/set", mode)
|
|
330
|
+
|
|
331
|
+
def publish_oled(self, text: str) -> None:
|
|
332
|
+
"""`<base>/oled/set` へ直接publishする(応答は待たない)"""
|
|
333
|
+
self._client.publish(f"{self.base}/oled/set", text)
|
|
334
|
+
|
|
335
|
+
# ------------------------------------------------------------------
|
|
336
|
+
def close(self):
|
|
337
|
+
"""MQTT接続を終了する"""
|
|
338
|
+
self._client.loop_stop()
|
|
339
|
+
self._client.disconnect()
|
|
340
|
+
|
|
341
|
+
def __del__(self):
|
|
342
|
+
try:
|
|
343
|
+
self.close()
|
|
344
|
+
except Exception:
|
|
345
|
+
pass
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class ESP32IOError(Exception):
|
|
2
|
+
"""esp32io_mqtt ライブラリの基底例外"""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
class ESP32IODeviceError(ESP32IOError):
|
|
6
|
+
"""ESP32 が status=error を返したときの例外 (ロジックエラー)"""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
class ESP32IOMqttError(ESP32IOError, RuntimeError):
|
|
10
|
+
"""MQTTブローカーとの接続・通信に失敗したときの例外"""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class ESP32IOTimeoutError(ESP32IOMqttError):
|
|
14
|
+
"""cmd/result 応答の待機がタイムアウトしたときの例外"""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class ESP32IOProtocolError(ESP32IOError, RuntimeError):
|
|
18
|
+
"""デバイスから不正な JSON や予期しない応答が返ったときの例外"""
|
|
19
|
+
pass
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""ESP32IO-MQTT プロトコル定義
|
|
2
|
+
|
|
3
|
+
ベーストピックは `<topic_root>/<device_id>` (既定 topic_root="esp32io")。
|
|
4
|
+
汎用JSONコマンドチャネルは `<base>/cmd` に送信し、`<base>/cmd/result` で応答を受け取る。
|
|
5
|
+
個別エンティティのトピックは ENTITY_TOPICS を参照。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
DEFAULT_TOPIC_ROOT = "esp32io"
|
|
9
|
+
|
|
10
|
+
# --- 汎用JSONコマンドチャネル (esp32io/<device_id>/cmd -> cmd/result) ---
|
|
11
|
+
CMD_PING = "ping"
|
|
12
|
+
CMD_GET_STATUS = "get_status"
|
|
13
|
+
CMD_GET_IO_STATE = "get_io_state"
|
|
14
|
+
CMD_READ_DI = "read_di"
|
|
15
|
+
CMD_SET_DO = "set_do"
|
|
16
|
+
CMD_READ_ADC = "read_adc"
|
|
17
|
+
CMD_SET_PWM = "set_pwm"
|
|
18
|
+
CMD_GET_PWM_CONFIG = "get_pwm_config"
|
|
19
|
+
CMD_SET_PWM_CONFIG = "set_pwm_config"
|
|
20
|
+
CMD_SET_RGB = "set_rgb"
|
|
21
|
+
CMD_LED_OFF = "led_off"
|
|
22
|
+
CMD_SET_LED_MODE = "set_led_mode"
|
|
23
|
+
CMD_GET_LED_STATE = "get_led_state"
|
|
24
|
+
CMD_HELP = "help"
|
|
25
|
+
CMD_I2C_SCAN = "i2c_scan"
|
|
26
|
+
CMD_I2C_READ = "i2c_read"
|
|
27
|
+
CMD_I2C_WRITE = "i2c_write"
|
|
28
|
+
CMD_GET_SENSORS = "get_sensors"
|
|
29
|
+
CMD_SET_OLED = "set_oled"
|
|
30
|
+
|
|
31
|
+
ALL_COMMANDS = (
|
|
32
|
+
CMD_PING,
|
|
33
|
+
CMD_GET_STATUS,
|
|
34
|
+
CMD_GET_IO_STATE,
|
|
35
|
+
CMD_READ_DI,
|
|
36
|
+
CMD_SET_DO,
|
|
37
|
+
CMD_READ_ADC,
|
|
38
|
+
CMD_SET_PWM,
|
|
39
|
+
CMD_GET_PWM_CONFIG,
|
|
40
|
+
CMD_SET_PWM_CONFIG,
|
|
41
|
+
CMD_SET_RGB,
|
|
42
|
+
CMD_LED_OFF,
|
|
43
|
+
CMD_SET_LED_MODE,
|
|
44
|
+
CMD_GET_LED_STATE,
|
|
45
|
+
CMD_HELP,
|
|
46
|
+
CMD_I2C_SCAN,
|
|
47
|
+
CMD_I2C_READ,
|
|
48
|
+
CMD_I2C_WRITE,
|
|
49
|
+
CMD_GET_SENSORS,
|
|
50
|
+
CMD_SET_OLED,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# --- 個別エンティティ (Home Assistant MQTT Discovery互換) のトピック購読パターン ---
|
|
54
|
+
# "<base>" を実際のベーストピックに置換して subscribe/publish する。
|
|
55
|
+
ENTITY_SUBSCRIBE_TOPICS = (
|
|
56
|
+
"{base}/status",
|
|
57
|
+
"{base}/di/+/state",
|
|
58
|
+
"{base}/do/+/state",
|
|
59
|
+
"{base}/adc/+/state",
|
|
60
|
+
"{base}/pwm/+/state",
|
|
61
|
+
"{base}/rgb/state",
|
|
62
|
+
"{base}/led_mode/state",
|
|
63
|
+
"{base}/oled/state",
|
|
64
|
+
"{base}/bme280/#",
|
|
65
|
+
"{base}/mpu6050/#",
|
|
66
|
+
"{base}/vl53l1x/#",
|
|
67
|
+
"{base}/diag/#",
|
|
68
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: esp32io-mqtt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight Python MQTT client for controlling ESP32IO-MQTT devices (Home Assistant compatible).
|
|
5
|
+
Author: Noritama-Lab
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/Noritama-Lab/esp32io-mqtt-api
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
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
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: paho-mqtt<2.0,>=1.6
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# ESP32IO-MQTT Python MQTT Client
|
|
26
|
+
|
|
27
|
+
ESP32IO-MQTT is a lightweight Python **MQTT** client for controlling [ESP32IO-MQTT](https://github.com/Noritama-Lab/esp32io-mqtt-firmware) devices.
|
|
28
|
+
|
|
29
|
+
日本語 / English
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 日本語
|
|
34
|
+
|
|
35
|
+
ESP32IO-MQTT Pythonクライアントは、**MQTTブローカー** 経由で ESP32IO-MQTT ファームウェアを制御するための軽量Pythonクライアントです。
|
|
36
|
+
|
|
37
|
+
ESP32IO-MQTT ファームウェアはデバイス制御を **MQTT** 経由で行います。本クライアントもMQTT専用です。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 特徴
|
|
42
|
+
|
|
43
|
+
- MQTTブローカー経由での接続のみをサポート(HTTP/シリアルは非対応)
|
|
44
|
+
- 汎用JSONコマンドチャネル (`<base>/cmd` → `<base>/cmd/result`) を使った同期API (`read_di()`, `set_do()`, `get_io_state()` 等)
|
|
45
|
+
- 個別エンティティのトピック (Home Assistant MQTT Discovery互換) にも対応
|
|
46
|
+
- `get_cached_state()` で直近受信した状態のスナップショットを取得
|
|
47
|
+
- `on_state_change()` で状態変化のコールバックを登録
|
|
48
|
+
- `publish_do()` / `publish_pwm()` / `publish_rgb()` / `publish_led_off()` / `publish_led_mode()` / `publish_oled()` で応答待ちなしに直接publish
|
|
49
|
+
- I2Cスキャン、リード/ライト、BME280/MPU6050/VL53L1Xなどのセンサー一括取得、OLED表示をサポート
|
|
50
|
+
- PWM周波数・分解能の取得/更新に対応
|
|
51
|
+
- 通信エラー・タイムアウト・プロトコル不整合・デバイスエラーなど統一例外を定義
|
|
52
|
+
- 任意JSONを送れる低レベルAPI `command()` を提供
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 動作環境
|
|
57
|
+
|
|
58
|
+
- Python 3.8 以上
|
|
59
|
+
- `paho-mqtt>=1.6,<2.0`
|
|
60
|
+
- MQTTブローカー (Mosquitto等)
|
|
61
|
+
- ESP32IO-MQTT ファームウェア(MQTT対応)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## インストール
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install -r requirements.txt
|
|
69
|
+
# または
|
|
70
|
+
pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## クイックスタート
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from esp32io_mqtt import ESP32S3IOMqtt
|
|
79
|
+
|
|
80
|
+
# DEVICE_ID は設定ポータル (http://<device_ip>/) のページタイトルや
|
|
81
|
+
# mDNSホスト名 (ESP32IO_MQTT_XXXXXX) で確認できます。
|
|
82
|
+
with ESP32S3IOMqtt("ESP32IO_MQTT_XXXXXX", "192.168.1.5", debug=False) as esp:
|
|
83
|
+
print("ping =", esp.ping())
|
|
84
|
+
print("di0 =", esp.read_di(0))
|
|
85
|
+
print("adc0 =", esp.read_adc(0))
|
|
86
|
+
print("pwm config =", esp.get_pwm_config())
|
|
87
|
+
|
|
88
|
+
esp.set_do(0, 1)
|
|
89
|
+
esp.set_pwm(0, 128)
|
|
90
|
+
|
|
91
|
+
print(esp.get_io_state())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
サンプル実行:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
py -m examples.mqtt_example
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 通信方式
|
|
103
|
+
|
|
104
|
+
### 1. 汎用JSONコマンドチャネル (同期API)
|
|
105
|
+
|
|
106
|
+
`command()` および `read_di()` / `set_do()` などの高レベルメソッドは、`<base>/cmd` にJSONをpublishし `<base>/cmd/result` の応答を待つ同期方式です。戻り値やエラー(`ESP32IODeviceError`)で結果を確認できます。
|
|
107
|
+
|
|
108
|
+
### 2. 個別エンティティのトピック (Home Assistant MQTT Discovery互換)
|
|
109
|
+
|
|
110
|
+
接続時に `di/+/state` `do/+/state` `pwm/+/state` `rgb/state` `led_mode/state` `oled/state` `bme280/#` `mpu6050/#` `vl53l1x/#` `diag/#` `status` を自動的にsubscribeし、受信するたびに内部キャッシュへ保存します。
|
|
111
|
+
|
|
112
|
+
- `get_cached_state()` — 直近に受信した値のスナップショット (dict) を取得
|
|
113
|
+
- `on_state_change(callback)` — 状態トピックを受信するたびに `callback(topic_suffix, payload)` を呼び出す
|
|
114
|
+
- `is_online` — LWTトピック (`<base>/status`) が `online` かどうか
|
|
115
|
+
- `publish_do(pin_id, value)` / `publish_pwm(pin_id, duty)` / `publish_rgb(r, g, b, brightness)` / `publish_led_off()` / `publish_led_mode(mode)` / `publish_oled(text)` — 応答を待たずに直接publish(Home Assistant側からの操作と同じ経路)
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## API 一覧(主要メソッド)
|
|
120
|
+
|
|
121
|
+
- `ESP32S3IOMqtt(device_id, host, port=1883, user=None, password=None, topic_root="esp32io", keepalive=30, cmd_timeout=5.0, connect_timeout=10.0, debug=False)`
|
|
122
|
+
- `ping()`
|
|
123
|
+
- `read_di(pin_id)` / `set_do(pin_id, value)`
|
|
124
|
+
- `read_adc(pin_id)`
|
|
125
|
+
- `set_pwm(pin_id, duty)`
|
|
126
|
+
- `get_pwm_config()` / `set_pwm_config(freq, res)`
|
|
127
|
+
- `set_led_mode(mode)` / `set_rgb(r, g, b, brightness)` / `led_off()` / `get_led_state()`
|
|
128
|
+
- `i2c_scan()` / `i2c_read(addr, length)` / `i2c_write(addr, data)`
|
|
129
|
+
- `get_sensors()`
|
|
130
|
+
- `set_oled(text, x, y, size, clear)`
|
|
131
|
+
- `get_io_state()` / `get_status()`
|
|
132
|
+
- `command(cmd, **kwargs)`
|
|
133
|
+
- `help()`
|
|
134
|
+
- `get_cached_state()` / `on_state_change(callback)` / `is_online`
|
|
135
|
+
- `publish_do()` / `publish_pwm()` / `publish_rgb()` / `publish_led_off()` / `publish_led_mode()` / `publish_oled()`
|
|
136
|
+
- `close()`
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 例外
|
|
141
|
+
|
|
142
|
+
- `ESP32IOError`(基底クラス)
|
|
143
|
+
- `ESP32IOMqttError` — ブローカー接続・publish失敗
|
|
144
|
+
- `ESP32IOTimeoutError` — `cmd/result` 応答待ちのタイムアウト(`ESP32IOMqttError`のサブクラス)
|
|
145
|
+
- `ESP32IOProtocolError` — デバイスから不正な応答
|
|
146
|
+
- `ESP32IODeviceError` — デバイスが `status=error` を返した場合
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## プロジェクト構成
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
.
|
|
154
|
+
├── esp32io_mqtt/
|
|
155
|
+
│ ├── __init__.py
|
|
156
|
+
│ ├── client.py
|
|
157
|
+
│ ├── exceptions.py
|
|
158
|
+
│ └── protocol.py
|
|
159
|
+
├── examples/
|
|
160
|
+
│ ├── mqtt_example.py
|
|
161
|
+
│ └── i2c_example.py
|
|
162
|
+
├── pyproject.toml
|
|
163
|
+
├── requirements.txt
|
|
164
|
+
├── LICENSE
|
|
165
|
+
└── README.md
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## ファームウェア
|
|
171
|
+
|
|
172
|
+
対応ファームウェア: [ESP32IO-MQTT](https://github.com/Noritama-Lab/esp32io-mqtt-firmware)(MQTT + Home Assistant MQTT Discovery対応)。
|
|
173
|
+
トピック構成・汎用JSONコマンド一覧は同リポジトリの `COMMAND_REFERENCE.md` を参照してください。
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## ライセンス
|
|
178
|
+
|
|
179
|
+
MIT License
|
|
180
|
+
Copyright (c) 2026 Noritama-Lab
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## English
|
|
185
|
+
|
|
186
|
+
ESP32IO-MQTT Python client is a lightweight **MQTT** client for controlling [ESP32IO-MQTT](https://github.com/Noritama-Lab/esp32io-mqtt-firmware) firmware.
|
|
187
|
+
|
|
188
|
+
ESP32IO-MQTT firmware controls devices over **MQTT**. This client is MQTT-only.
|
|
189
|
+
|
|
190
|
+
### Features
|
|
191
|
+
|
|
192
|
+
- MQTT broker connection only (no HTTP/Serial support)
|
|
193
|
+
- Synchronous API over the generic JSON command channel (`<base>/cmd` → `<base>/cmd/result`) (`read_di()`, `set_do()`, `get_io_state()`, etc.)
|
|
194
|
+
- Support for individual entity topics (Home Assistant MQTT Discovery compatible)
|
|
195
|
+
- `get_cached_state()` for a snapshot of the latest received state
|
|
196
|
+
- `on_state_change()` to register a callback for state changes
|
|
197
|
+
- `publish_do()` / `publish_pwm()` / `publish_rgb()` / `publish_led_off()` / `publish_led_mode()` / `publish_oled()` to publish directly without waiting for a response
|
|
198
|
+
- I2C scanning, raw read/write, sensor batch reads (BME280/MPU6050/VL53L1X), and OLED control
|
|
199
|
+
- PWM frequency/resolution read & update
|
|
200
|
+
- Unified exceptions for MQTT, protocol, timeout, and device errors
|
|
201
|
+
- Low-level `command()` API for custom JSON commands
|
|
202
|
+
|
|
203
|
+
### Requirements
|
|
204
|
+
|
|
205
|
+
- Python 3.8+
|
|
206
|
+
- `paho-mqtt>=1.6,<2.0`
|
|
207
|
+
- An MQTT broker (e.g. Mosquitto)
|
|
208
|
+
- ESP32IO-MQTT firmware (MQTT-based)
|
|
209
|
+
|
|
210
|
+
### Installation
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pip install -r requirements.txt
|
|
214
|
+
# or
|
|
215
|
+
pip install -e .
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Quick Start
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from esp32io_mqtt import ESP32S3IOMqtt
|
|
222
|
+
|
|
223
|
+
with ESP32S3IOMqtt("ESP32IO_MQTT_XXXXXX", "192.168.1.5", debug=False) as esp:
|
|
224
|
+
print("ping =", esp.ping())
|
|
225
|
+
print("di0 =", esp.read_di(0))
|
|
226
|
+
esp.set_do(0, 1)
|
|
227
|
+
print(esp.get_io_state())
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### License
|
|
231
|
+
|
|
232
|
+
MIT License
|
|
233
|
+
Copyright (c) 2026 Noritama-Lab
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
esp32io_mqtt/__init__.py
|
|
5
|
+
esp32io_mqtt/client.py
|
|
6
|
+
esp32io_mqtt/exceptions.py
|
|
7
|
+
esp32io_mqtt/protocol.py
|
|
8
|
+
esp32io_mqtt/py.typed
|
|
9
|
+
esp32io_mqtt.egg-info/PKG-INFO
|
|
10
|
+
esp32io_mqtt.egg-info/SOURCES.txt
|
|
11
|
+
esp32io_mqtt.egg-info/dependency_links.txt
|
|
12
|
+
esp32io_mqtt.egg-info/requires.txt
|
|
13
|
+
esp32io_mqtt.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
paho-mqtt<2.0,>=1.6
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
esp32io_mqtt
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "esp32io-mqtt"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A lightweight Python MQTT client for controlling ESP32IO-MQTT devices (Home Assistant compatible)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Noritama-Lab"},
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"paho-mqtt>=1.6,<2.0",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.8",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Topic :: System :: Hardware",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Repository = "https://github.com/Noritama-Lab/esp32io-mqtt-api"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
include = ["esp32io_mqtt"]
|
|
37
|
+
exclude = ["examples*", "tests*"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.package-data]
|
|
40
|
+
esp32io_mqtt = ["py.typed"]
|