pyblufi 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyblufi/__init__.py ADDED
@@ -0,0 +1,30 @@
1
+ """PyBluFi - Python implementation of ESP32 BluFi protocol for WiFi provisioning over BLE."""
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "ESP32 Solar Manager"
5
+
6
+ from .protocol import BlufiProtocol
7
+ from .client import BlufiClient, WifiStatus, WifiNetwork
8
+ from .constants import (
9
+ BLUFI_TYPE,
10
+ BLUFI_CTRL_SUBTYPE,
11
+ BLUFI_DATA_SUBTYPE,
12
+ WIFI_OP_MODE,
13
+ WIFI_AUTH_MODE,
14
+ WIFI_STATUS,
15
+ SECURITY_MODE,
16
+ )
17
+
18
+ __all__ = [
19
+ "BlufiProtocol",
20
+ "BlufiClient",
21
+ "WifiStatus",
22
+ "WifiNetwork",
23
+ "BLUFI_TYPE",
24
+ "BLUFI_CTRL_SUBTYPE",
25
+ "BLUFI_DATA_SUBTYPE",
26
+ "WIFI_OP_MODE",
27
+ "WIFI_AUTH_MODE",
28
+ "WIFI_STATUS",
29
+ "SECURITY_MODE",
30
+ ]
pyblufi/cli.py ADDED
@@ -0,0 +1,273 @@
1
+ """Command-line interface for PyBluFi."""
2
+
3
+ import argparse
4
+ import asyncio
5
+ import sys
6
+ import textwrap
7
+ from typing import List, Optional
8
+
9
+ from .client import BlufiClient, WifiStatus
10
+ from .constants import WIFI_STATUS
11
+
12
+
13
+ def _print_wifi_status(status: WifiStatus) -> None:
14
+ """Print WiFi status in a nice format."""
15
+ status_map = {
16
+ WIFI_STATUS.STA_CONN_SUCCESS: "Connected (with IP)",
17
+ WIFI_STATUS.STA_CONN_FAIL: "Connection failed",
18
+ WIFI_STATUS.STA_CONN_CONNECTING: "Connecting...",
19
+ WIFI_STATUS.STA_CONN_NO_IP: "Connected (no IP)",
20
+ }
21
+ conn_status = status_map.get(status.sta_conn_status, f"Unknown ({status.sta_conn_status})")
22
+
23
+ print(f" Operation Mode: {status.op_mode}")
24
+ print(f" Connection: {conn_status}")
25
+ print(f" SSID: {status.sta_ssid or 'N/A'}")
26
+ print(f" IP: {status.sta_ip or 'N/A'}")
27
+ print(f" BSSID: {status.sta_bssid or 'N/A'}")
28
+ print(f" SoftAP Clients: {status.softap_conn_num}")
29
+
30
+
31
+ async def cmd_scan(args: argparse.Namespace) -> int:
32
+ """Scan for BluFi devices."""
33
+ print("Scanning for BluFi devices...")
34
+ client = BlufiClient(debug=args.debug)
35
+ devices = await client.scan(timeout=args.timeout)
36
+
37
+ if not devices:
38
+ print("No devices found.")
39
+ return 1
40
+
41
+ print(f"\nFound {len(devices)} device(s):")
42
+ print("-" * 60)
43
+ for i, dev in enumerate(devices, 1):
44
+ print(f"{i}. {dev.name or 'Unknown'} ({dev.address})")
45
+ print(f" RSSI: {dev.rssi} dBm")
46
+ print("-" * 60)
47
+ return 0
48
+
49
+
50
+ async def cmd_provision(args: argparse.Namespace) -> int:
51
+ """Provision WiFi credentials to a BluFi device."""
52
+ print(f"Provisioning device: {args.device}")
53
+ print(f" SSID: {args.ssid}")
54
+ print(f" Password: {'*' * len(args.password)}")
55
+ print()
56
+
57
+ client = BlufiClient(debug=args.debug)
58
+
59
+ # Find device
60
+ if args.address:
61
+ address = args.address
62
+ print(f"Using address: {address}")
63
+ else:
64
+ print(f"Scanning for device matching '{args.device}'...")
65
+ device = await client.scan_for_device(args.device, timeout=args.timeout)
66
+ if not device:
67
+ print(f"Device '{args.device}' not found.")
68
+ return 1
69
+ address = device.address
70
+ print(f"Found: {device.name} ({device.address})\n")
71
+
72
+ # Connect
73
+ print("Connecting...")
74
+ if not await client.connect(address, timeout=args.timeout):
75
+ print("Failed to connect.")
76
+ return 1
77
+ print("Connected!\n")
78
+
79
+ try:
80
+ # Send WiFi credentials
81
+ print("Setting STA mode...")
82
+ await client.set_sta_mode()
83
+ await asyncio.sleep(0.5)
84
+
85
+ print(f"Setting SSID: {args.ssid}")
86
+ await client.set_ssid(args.ssid)
87
+ await asyncio.sleep(0.5)
88
+
89
+ print("Setting password...")
90
+ await client.set_password(args.password)
91
+ await asyncio.sleep(0.5)
92
+
93
+ print("Connecting to AP...")
94
+ await client.connect_to_ap()
95
+ await asyncio.sleep(1.0)
96
+
97
+ # Wait for connection
98
+ print(f"\nWaiting for WiFi connection (timeout={args.wait}s)...")
99
+ status = await client.wait_for_wifi_status(timeout=args.wait)
100
+
101
+ if status and status.sta_conn_status == WIFI_STATUS.STA_CONN_SUCCESS:
102
+ print("\nWiFi connected successfully!")
103
+ _print_wifi_status(status)
104
+ return 0
105
+ else:
106
+ print("\nWiFi connection timed out or failed.")
107
+ return 1
108
+
109
+ except Exception as e:
110
+ print(f"Error during provisioning: {e}")
111
+ return 1
112
+ finally:
113
+ await client.disconnect()
114
+ print("\nDisconnected.")
115
+
116
+
117
+ async def cmd_status(args: argparse.Namespace) -> int:
118
+ """Get WiFi status from a connected device."""
119
+ client = BlufiClient(debug=args.debug)
120
+
121
+ print(f"Connecting to {args.address}...")
122
+ if not await client.connect(args.address, timeout=args.timeout):
123
+ print("Failed to connect.")
124
+ return 1
125
+
126
+ try:
127
+ print("Getting WiFi status...")
128
+ status = await client.get_wifi_status()
129
+ if status:
130
+ _print_wifi_status(status)
131
+ else:
132
+ print("No status received.")
133
+ finally:
134
+ await client.disconnect()
135
+
136
+ return 0
137
+
138
+
139
+ async def cmd_scan_wifi(args: argparse.Namespace) -> int:
140
+ """Scan for nearby WiFi networks via the device."""
141
+ client = BlufiClient(debug=args.debug)
142
+
143
+ print(f"Connecting to {args.address}...")
144
+ if not await client.connect(args.address, timeout=args.timeout):
145
+ print("Failed to connect.")
146
+ return 1
147
+
148
+ try:
149
+ print("Scanning for WiFi networks...")
150
+ networks = await client.get_wifi_list()
151
+ if networks:
152
+ print(f"\nFound {len(networks)} network(s):")
153
+ print("-" * 40)
154
+ for net in networks:
155
+ signal = ""
156
+ if net.rssi > -50:
157
+ signal = "Excellent"
158
+ elif net.rssi > -60:
159
+ signal = "Good"
160
+ elif net.rssi > -70:
161
+ signal = "Fair"
162
+ else:
163
+ signal = "Weak"
164
+ print(f" {net.ssid:<32} {net.rssi:>4} dBm ({signal})")
165
+ print("-" * 40)
166
+ else:
167
+ print("No networks found.")
168
+ finally:
169
+ await client.disconnect()
170
+
171
+ return 0
172
+
173
+
174
+ async def cmd_custom(args: argparse.Namespace) -> int:
175
+ """Send custom data to the device."""
176
+ client = BlufiClient(debug=args.debug)
177
+
178
+ print(f"Connecting to {args.address}...")
179
+ if not await client.connect(args.address, timeout=args.timeout):
180
+ print("Failed to connect.")
181
+ return 1
182
+
183
+ try:
184
+ data = args.data.encode("utf-8")
185
+ print(f"Sending custom data: {data.hex()}")
186
+ await client.send_custom_data(data)
187
+ print("Sent.")
188
+ finally:
189
+ await client.disconnect()
190
+
191
+ return 0
192
+
193
+
194
+ def main() -> int:
195
+ """Main CLI entry point."""
196
+ parser = argparse.ArgumentParser(
197
+ prog="blufi-provision",
198
+ description="PyBluFi - ESP32 WiFi provisioning via BLE",
199
+ formatter_class=argparse.RawDescriptionHelpFormatter,
200
+ epilog=textwrap.dedent("""\
201
+ Examples:
202
+ blufi-provision scan
203
+ blufi-provision provision --ssid "MyWiFi" --password "secret123" --device "BLUFI_DEVICE"
204
+ blufi-provision status --address AA:BB:CC:DD:EE:FF
205
+ blufi-provision scan-wifi --address AA:BB:CC:DD:EE:FF
206
+ blufi-provision custom --address AA:BB:CC:DD:EE:FF --data "hello"
207
+ """),
208
+ )
209
+ parser.add_argument(
210
+ "--debug", "-d",
211
+ action="store_true",
212
+ help="Enable debug output",
213
+ )
214
+
215
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
216
+
217
+ # scan
218
+ scan_parser = subparsers.add_parser("scan", help="Scan for BluFi devices")
219
+ scan_parser.add_argument(
220
+ "--timeout", "-t",
221
+ type=float,
222
+ default=5.0,
223
+ help="Scan timeout in seconds (default: 5.0)",
224
+ )
225
+
226
+ # provision
227
+ prov_parser = subparsers.add_parser("provision", help="Provision WiFi credentials")
228
+ prov_parser.add_argument("--ssid", "-s", required=True, help="WiFi SSID")
229
+ prov_parser.add_argument("--password", "-p", default="", help="WiFi password")
230
+ prov_parser.add_argument("--device", "-D", default="BLUFI", help="Device name filter")
231
+ prov_parser.add_argument("--address", "-a", help="BLE address (skip scan)")
232
+ prov_parser.add_argument("--timeout", "-t", type=float, default=10.0, help="Timeout")
233
+ prov_parser.add_argument("--wait", "-w", type=float, default=30.0, help="Wait for WiFi connection")
234
+
235
+ # status
236
+ status_parser = subparsers.add_parser("status", help="Get WiFi status")
237
+ status_parser.add_argument("--address", "-a", required=True, help="BLE address")
238
+ status_parser.add_argument("--timeout", "-t", type=float, default=10.0, help="Timeout")
239
+
240
+ # scan-wifi
241
+ scan_wifi_parser = subparsers.add_parser("scan-wifi", help="Scan for WiFi networks via device")
242
+ scan_wifi_parser.add_argument("--address", "-a", required=True, help="BLE address")
243
+ scan_wifi_parser.add_argument("--timeout", "-t", type=float, default=10.0, help="Timeout")
244
+
245
+ # custom
246
+ custom_parser = subparsers.add_parser("custom", help="Send custom data")
247
+ custom_parser.add_argument("--address", "-a", required=True, help="BLE address")
248
+ custom_parser.add_argument("--data", "-d", required=True, help="Data to send")
249
+ custom_parser.add_argument("--timeout", "-t", type=float, default=10.0, help="Timeout")
250
+
251
+ args = parser.parse_args()
252
+
253
+ if not args.command:
254
+ parser.print_help()
255
+ return 1
256
+
257
+ command_map = {
258
+ "scan": cmd_scan,
259
+ "provision": cmd_provision,
260
+ "status": cmd_status,
261
+ "scan-wifi": cmd_scan_wifi,
262
+ "custom": cmd_custom,
263
+ }
264
+
265
+ if args.command in command_map:
266
+ return asyncio.run(command_map[args.command](args))
267
+ else:
268
+ parser.print_help()
269
+ return 1
270
+
271
+
272
+ if __name__ == "__main__":
273
+ sys.exit(main())