pythonhere 0.2.1__tar.gz → 0.2.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.
Files changed (64) hide show
  1. {pythonhere-0.2.1/pythonhere.egg-info → pythonhere-0.2.2}/PKG-INFO +4 -3
  2. {pythonhere-0.2.1 → pythonhere-0.2.2}/README.rst +2 -1
  3. {pythonhere-0.2.1 → pythonhere-0.2.2}/pyproject.toml +4 -1
  4. pythonhere-0.2.2/pythonhere/__init__.py +15 -0
  5. pythonhere-0.2.2/pythonhere/magic_here/prompts/README.md +49 -0
  6. pythonhere-0.2.2/pythonhere/magic_here/prompts/able.md +554 -0
  7. pythonhere-0.2.2/pythonhere/magic_here/prompts/android-media.md +130 -0
  8. pythonhere-0.2.2/pythonhere/magic_here/prompts/android-packages.md +69 -0
  9. pythonhere-0.2.2/pythonhere/magic_here/prompts/android-permissions.md +195 -0
  10. pythonhere-0.2.2/pythonhere/magic_here/prompts/android-runtime.md +34 -0
  11. pythonhere-0.2.2/pythonhere/magic_here/prompts/jnius.md +432 -0
  12. pythonhere-0.2.2/pythonhere/magic_here/prompts/kivy-kv.md +239 -0
  13. pythonhere-0.2.2/pythonhere/magic_here/prompts/kivy-runtime.md +306 -0
  14. pythonhere-0.2.2/pythonhere/magic_here/prompts/midi.md +243 -0
  15. pythonhere-0.2.2/pythonhere/magic_here/prompts/plyer.md +202 -0
  16. pythonhere-0.2.2/pythonhere/magic_here/prompts.py +37 -0
  17. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/main.py +9 -0
  18. pythonhere-0.2.2/pythonhere/version_here.py +1 -0
  19. {pythonhere-0.2.1 → pythonhere-0.2.2/pythonhere.egg-info}/PKG-INFO +4 -3
  20. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere.egg-info/SOURCES.txt +13 -0
  21. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere.egg-info/requires.txt +1 -1
  22. pythonhere-0.2.2/tests/test_ai_prompts.py +57 -0
  23. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_main.py +32 -1
  24. pythonhere-0.2.1/pythonhere/__init__.py +0 -8
  25. pythonhere-0.2.1/pythonhere/version_here.py +0 -1
  26. {pythonhere-0.2.1 → pythonhere-0.2.2}/LICENSE +0 -0
  27. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/android_here.py +0 -0
  28. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/data/logo/logo-128.png +0 -0
  29. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/data/logo/logo-32.png +0 -0
  30. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/data/logo/logo-splash.png +0 -0
  31. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/enum_here.py +0 -0
  32. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/exception_manager_here.kv +0 -0
  33. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/exception_manager_here.py +0 -0
  34. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/launcher_here.py +0 -0
  35. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/magic_here/__init__.py +0 -0
  36. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/magic_here/shortcuts.py +0 -0
  37. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/network_here.py +0 -0
  38. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/patches_here.py +0 -0
  39. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/pythonhere.kv +0 -0
  40. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/server_here.py +0 -0
  41. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/__init__.py +0 -0
  42. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/actionbar_here.kv +0 -0
  43. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/common_here.kv +0 -0
  44. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/connection_address_here.kv +0 -0
  45. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/connection_address_here.py +0 -0
  46. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/layout_here.py +0 -0
  47. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/server_screen_here.kv +0 -0
  48. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/server_screen_here.py +0 -0
  49. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/settings_here.kv +0 -0
  50. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/ui_here/settings_here.py +0 -0
  51. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere/window_here.py +0 -0
  52. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere.egg-info/dependency_links.txt +0 -0
  53. {pythonhere-0.2.1 → pythonhere-0.2.2}/pythonhere.egg-info/top_level.txt +0 -0
  54. {pythonhere-0.2.1 → pythonhere-0.2.2}/setup.cfg +0 -0
  55. {pythonhere-0.2.1 → pythonhere-0.2.2}/setup.py +0 -0
  56. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_android_here.py +0 -0
  57. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_exception_manager_here.py +0 -0
  58. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_launcher_here.py +0 -0
  59. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_magic.py +0 -0
  60. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_network.py +0 -0
  61. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_patches.py +0 -0
  62. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_server_here.py +0 -0
  63. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_settings.py +0 -0
  64. {pythonhere-0.2.1 → pythonhere-0.2.2}/tests/test_window_here.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonhere
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Here is the Kivy based app to run code from the Jupyter magic %there
5
5
  Author-email: b3b <ash.b3b@gmail.com>
6
6
  License-Expression: MIT
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.14
16
16
  Requires-Python: >=3.10
17
17
  Description-Content-Type: text/x-rst
18
18
  License-File: LICENSE
19
- Requires-Dist: herethere[magic]>=0.2.1
19
+ Requires-Dist: herethere[magic]>=0.2.3
20
20
  Requires-Dist: ipython
21
21
  Requires-Dist: ipywidgets
22
22
  Requires-Dist: Pillow
@@ -123,6 +123,7 @@ To build with `Buildozer <https://github.com/kivy/buildozer>`_, run in the sourc
123
123
  Related resources
124
124
  -----------------
125
125
 
126
- * `Kivy Remote Shell <https://github.com/kivy/kivy-remote-shell>`_ : Remote SSH+Python interactive shell application
127
126
  * `herethere <https://github.com/b3b/herethere>`_ : Library for interactive code execution, based on AsyncSSH
128
127
  * `AsyncSSH <https://github.com/ronf/asyncssh>`_ : Asynchronous SSH for Python
128
+ * `Buildozer <https://github.com/kivy/buildozer>`_ : Tool used to build the Android APK.
129
+ * `python-for-android <https://github.com/kivy/python-for-android>`_ : Build toolchain used by Buildozer to package Python applications for Android.
@@ -99,6 +99,7 @@ To build with `Buildozer <https://github.com/kivy/buildozer>`_, run in the sourc
99
99
  Related resources
100
100
  -----------------
101
101
 
102
- * `Kivy Remote Shell <https://github.com/kivy/kivy-remote-shell>`_ : Remote SSH+Python interactive shell application
103
102
  * `herethere <https://github.com/b3b/herethere>`_ : Library for interactive code execution, based on AsyncSSH
104
103
  * `AsyncSSH <https://github.com/ronf/asyncssh>`_ : Asynchronous SSH for Python
104
+ * `Buildozer <https://github.com/kivy/buildozer>`_ : Tool used to build the Android APK.
105
+ * `python-for-android <https://github.com/kivy/python-for-android>`_ : Build toolchain used by Buildozer to package Python applications for Android.
@@ -22,7 +22,7 @@ classifiers = [
22
22
  "Programming Language :: Python :: 3.14",
23
23
  ]
24
24
  dependencies = [
25
- "herethere[magic]>=0.2.1",
25
+ "herethere[magic]>=0.2.3",
26
26
  "ipython",
27
27
  "ipywidgets",
28
28
  "Pillow",
@@ -46,6 +46,8 @@ dev = [
46
46
  ]
47
47
  docker = [
48
48
  "jupytext==1.19.3",
49
+ "pandas",
50
+ "matplotlib",
49
51
  ]
50
52
 
51
53
  [project.urls]
@@ -75,6 +77,7 @@ version = { attr = "pythonhere.version_here.__version__" }
75
77
  pythonhere = [
76
78
  "*.kv",
77
79
  "data/logo/*.png",
80
+ "magic_here/prompts/*.md",
78
81
  "ui_here/*.kv",
79
82
  ]
80
83
 
@@ -0,0 +1,15 @@
1
+ """PythonHere Jupyter magic."""
2
+
3
+ from herethere.magic import load_ipython_extension as load_herethere_extension
4
+
5
+ from .magic_here import shortcuts # noqa
6
+ from .magic_here.prompts import register_pythonhere_ai_prompts
7
+ from .version_here import __version__ # noqa
8
+
9
+ __all__ = ("load_ipython_extension",)
10
+
11
+
12
+ def load_ipython_extension(ipython):
13
+ """Hook for `%load_extension pythonhere`."""
14
+ register_pythonhere_ai_prompts()
15
+ load_herethere_extension(ipython)
@@ -0,0 +1,49 @@
1
+ # PythonHere `%%there ai` prompts
2
+
3
+ These Markdown files are PythonHere-specific prompt sections for `%%there ai`.
4
+ They are registered when the `pythonhere` IPython extension is loaded.
5
+
6
+ The prompt sections describe the live PythonHere runtime: Kivy widgets,
7
+ Android/Python-for-Android APIs, Pyjnius, Plyer, runtime permissions, installed
8
+ packages, media access, BLE, and MIDI.
9
+
10
+ ## Active by default
11
+
12
+ Normal `%%there ai` requests use these PythonHere sections together with the
13
+ generic [`default`](https://github.com/b3b/herethere/blob/master/herethere/there/ai/prompts/default.md)
14
+ prompt from `herethere`:
15
+
16
+ - [`kivy-runtime`](kivy-runtime.md)
17
+ - [`kivy-kv`](kivy-kv.md)
18
+ - [`android-runtime`](android-runtime.md)
19
+ - [`jnius`](jnius.md)
20
+ - [`android-permissions`](android-permissions.md)
21
+ - [`android-packages`](android-packages.md)
22
+ - [`android-media`](android-media.md)
23
+ - [`plyer`](plyer.md)
24
+
25
+ ## Available on request
26
+
27
+ These sections are registered but not active by default. Add them with
28
+ `%%there ai --prompts ...` when a request needs that context:
29
+
30
+ - [`able`](able.md)
31
+ - [`midi`](midi.md)
32
+
33
+ Example:
34
+
35
+ ```python
36
+ %%there ai --prompts able
37
+ Build a small BLE scanner prototype.
38
+ ```
39
+
40
+ `%%there ai --fix` also uses the `herethere`
41
+ [`fix`](https://github.com/b3b/herethere/blob/master/herethere/there/ai/prompts/fix.md)
42
+ prompt section.
43
+
44
+ ## Custom prompts
45
+
46
+ Notebook-specific prompt sections can be added with
47
+ `herethere.there.ai.register_ai_prompt(...)`. Use custom prompts for visual
48
+ style, domain vocabulary, prototype conventions, or other context that should
49
+ not be part of the built-in PythonHere prompt stack.
@@ -0,0 +1,554 @@
1
+ ## Android BLE using `able`
2
+
3
+ Use `able` package when the user asks for BLE (Bluetooth Low Energy) operations.
4
+ `able` is installed; do not need to check for import errors before normal use.
5
+
6
+ Core API:
7
+ - Import the BLE dispatcher with:
8
+ from able import BluetoothDispatcher
9
+ - Create one shared dispatcher and keep it alive while BLE operations are needed:
10
+ ble = BluetoothDispatcher()
11
+ - Reuse the shared dispatcher across cells:
12
+ if "ble" not in globals() or ble is None:
13
+ ble = BluetoothDispatcher()
14
+ - Close the current GATT client with:
15
+ ble.close_gatt()
16
+ - Start scanning with:
17
+ ble.start_scan()
18
+ - Stop scanning with:
19
+ ble.stop_scan()
20
+ - Connect to a scanned Java BluetoothDevice with:
21
+ ble.connect_gatt(device, autoconnect=False)
22
+ - Connect directly by hardware address with:
23
+ ble.connect_by_device_address(address, autoconnect=False)
24
+ - Discover services after connection with:
25
+ ble.discover_services()
26
+ - Read a characteristic with:
27
+ ble.read_characteristic(characteristic)
28
+ - Write a characteristic with:
29
+ ble.write_characteristic(characteristic, value)
30
+ - Write a descriptor with:
31
+ ble.write_descriptor(descriptor, value)
32
+ - Enable notifications with:
33
+ ble.enable_notifications(characteristic, enable=True, indication=False)
34
+ - Disable notifications with:
35
+ ble.enable_notifications(characteristic, enable=False)
36
+ - Request MTU with:
37
+ ble.request_mtu(mtu)
38
+ - Update RSSI with:
39
+ ble.update_rssi()
40
+ - Change queue timeout with:
41
+ ble.set_queue_timeout(timeout)
42
+
43
+ Important constants:
44
+ - Import common constants when needed:
45
+ from able import GATT_SUCCESS, STATE_CONNECTED, STATE_DISCONNECTED, WriteType
46
+ - `GATT_SUCCESS` is 0.
47
+ - `STATE_CONNECTED` is 2.
48
+ - `STATE_DISCONNECTED` is 0.
49
+ - `WriteType.SIGNED` can be used for signed characteristic writes when requested.
50
+
51
+ BLE dispatcher state:
52
+ - Use a global variable named `ble` for the shared `BluetoothDispatcher`.
53
+ - Do not create a new `BluetoothDispatcher` for every operation.
54
+ - Keep the dispatcher globally inspectable so later cells can run:
55
+ ble.adapter
56
+ ble.bonded_devices
57
+ ble.gatt
58
+ ble.name
59
+ ble.stop_scan()
60
+ ble.close_gatt()
61
+ - Store discovered devices in a global dictionary such as:
62
+ ble_devices_by_address
63
+ - Store the latest services in:
64
+ ble_services
65
+ - Store recent events in:
66
+ ble_events
67
+ - Store recent errors in:
68
+ ble_errors
69
+ - Store compact status messages in:
70
+ ble_status_messages
71
+ - Store the latest characteristic values in:
72
+ ble_characteristic_values
73
+ - Store the latest notification values in:
74
+ ble_notifications
75
+ - Store the latest scan summary in:
76
+ ble_scan_summary
77
+ - Store the currently connected address in:
78
+ ble_connected_address
79
+ - Store the last operation status in:
80
+ ble_last_result
81
+
82
+ Recommended shared initialization:
83
+ from able import BluetoothDispatcher
84
+
85
+ if "ble_events" not in globals():
86
+ ble_events = []
87
+
88
+ if "ble_errors" not in globals():
89
+ ble_errors = []
90
+
91
+ if "ble_status_messages" not in globals():
92
+ ble_status_messages = []
93
+
94
+ if "ble_devices_by_address" not in globals():
95
+ ble_devices_by_address = {}
96
+
97
+ if "ble_characteristic_values" not in globals():
98
+ ble_characteristic_values = {}
99
+
100
+ if "ble_notifications" not in globals():
101
+ ble_notifications = []
102
+
103
+ if "ble_scan_summary" not in globals():
104
+ ble_scan_summary = {}
105
+
106
+ if "ble" not in globals() or ble is None:
107
+ ble = BluetoothDispatcher()
108
+
109
+ Subclassing pattern:
110
+ - For scans, connections, services, reads, writes, notifications, RSSI, and MTU, prefer a small subclass of `BluetoothDispatcher` with event handlers.
111
+ - Reuse an existing subclass instance when possible.
112
+ - Keep callbacks short.
113
+ - Do not rely on `print(...)` inside BLE callbacks as the only output. BLE
114
+ callbacks may run after notebook output capture has ended. Store every event,
115
+ result, and error in globals such as `ble_events`, `ble_errors`,
116
+ `ble_devices_by_address`, `ble_services`, and `ble_notifications`.
117
+ - In BLE callbacks, prefer appending a compact tuple/dict to `ble_events` or a
118
+ feature-specific global over printing. Use `print(...)` only in immediate
119
+ synchronous code that runs before the cell returns.
120
+ - Do not schedule delayed functions whose purpose is to print scan summaries.
121
+ A delayed `Clock.schedule_once(...)` callback may run after notebook output
122
+ capture has ended. Store delayed summaries in globals such as
123
+ `ble_scan_summary` and update visible UI/status messages instead.
124
+ - If the user asked for visible progress, update a Kivy status label or popup
125
+ from callbacks using `Clock.schedule_once(...)`.
126
+ - Store Java objects by address or UUID-like key so later cells can use them.
127
+ - Do not print huge advertisement dumps or service trees directly.
128
+ - Print compact immediate summaries from the initiating cell and store full
129
+ callback results globally.
130
+
131
+ Recommended dispatcher subclass skeleton:
132
+ from able import BluetoothDispatcher, GATT_SUCCESS, STATE_CONNECTED, STATE_DISCONNECTED
133
+ from kivy.clock import Clock
134
+
135
+ def update_ble_status(message):
136
+ ble_status_messages.append(str(message))
137
+ label = globals().get("ble_status_label")
138
+ if label is not None:
139
+ Clock.schedule_once(lambda dt: setattr(label, "text", str(message)), 0)
140
+
141
+ class PythonHereBLE(BluetoothDispatcher):
142
+ def on_scan_started(self, success):
143
+ ble_events.append(("scan_started", bool(success)))
144
+ update_ble_status(f"BLE scan started: {bool(success)}")
145
+
146
+ def on_scan_completed(self):
147
+ ble_events.append(("scan_completed", None))
148
+ update_ble_status("BLE scan completed")
149
+
150
+ def on_device(self, device, rssi, advertisement):
151
+ address = str(device.getAddress())
152
+ try:
153
+ name = device.getName()
154
+ name = str(name) if name is not None else None
155
+ except Exception:
156
+ name = None
157
+
158
+ ble_devices_by_address[address] = {
159
+ "device": device,
160
+ "address": address,
161
+ "name": name,
162
+ "rssi": int(rssi),
163
+ "advertisement": advertisement,
164
+ }
165
+ ble_events.append(("device", address, int(rssi), name))
166
+ update_ble_status(f"{len(ble_devices_by_address)} BLE devices found")
167
+
168
+ def on_connection_state_change(self, status, state):
169
+ global ble_connected_address
170
+ ble_events.append(("connection_state_change", int(status), int(state)))
171
+
172
+ if int(status) == GATT_SUCCESS and int(state) == STATE_CONNECTED:
173
+ ble_connected_address = "connected"
174
+ update_ble_status("BLE connected")
175
+ self.discover_services()
176
+ elif int(state) == STATE_DISCONNECTED:
177
+ ble_connected_address = None
178
+ update_ble_status("BLE disconnected")
179
+ else:
180
+ update_ble_status(f"BLE connection state: {status}, {state}")
181
+
182
+ def on_services(self, services, status):
183
+ global ble_services
184
+ ble_events.append(("services", int(status)))
185
+ if int(status) == GATT_SUCCESS:
186
+ ble_services = services
187
+ update_ble_status("BLE services discovered; stored in ble_services")
188
+ else:
189
+ update_ble_status(f"BLE service discovery failed: {status}")
190
+
191
+ def on_characteristic_read(self, characteristic, status):
192
+ uuid = str(characteristic.getUuid())
193
+ value = list(characteristic.getValue() or [])
194
+ ble_characteristic_values[uuid] = {
195
+ "uuid": uuid,
196
+ "status": int(status),
197
+ "value": value,
198
+ "characteristic": characteristic,
199
+ }
200
+ ble_events.append(("characteristic_read", uuid, int(status), value[:32]))
201
+ update_ble_status(f"Characteristic read: {uuid} status={status}")
202
+
203
+ def on_characteristic_write(self, characteristic, status):
204
+ uuid = str(characteristic.getUuid())
205
+ ble_events.append(("characteristic_write", uuid, int(status)))
206
+ update_ble_status(f"Characteristic write: {uuid} status={status}")
207
+
208
+ def on_characteristic_changed(self, characteristic):
209
+ uuid = str(characteristic.getUuid())
210
+ value = list(characteristic.getValue() or [])
211
+ event = {
212
+ "uuid": uuid,
213
+ "value": value,
214
+ "characteristic": characteristic,
215
+ }
216
+ ble_notifications.append(event)
217
+ ble_events.append(("notification", uuid, value[:32]))
218
+ update_ble_status(f"Notification: {uuid}")
219
+
220
+ def on_descriptor_read(self, descriptor, status):
221
+ uuid = str(descriptor.getUuid())
222
+ ble_events.append(("descriptor_read", uuid, int(status)))
223
+ update_ble_status(f"Descriptor read: {uuid} status={status}")
224
+
225
+ def on_descriptor_write(self, descriptor, status):
226
+ uuid = str(descriptor.getUuid())
227
+ ble_events.append(("descriptor_write", uuid, int(status)))
228
+ update_ble_status(f"Descriptor write: {uuid} status={status}")
229
+
230
+ def on_rssi_updated(self, rssi, status):
231
+ ble_events.append(("rssi_updated", int(rssi), int(status)))
232
+ update_ble_status(f"RSSI: {int(rssi)} status={status}")
233
+
234
+ def on_mtu_changed(self, mtu, status):
235
+ ble_events.append(("mtu_changed", int(mtu), int(status)))
236
+ update_ble_status(f"MTU: {int(mtu)} status={status}")
237
+
238
+ def on_gatt_release(self):
239
+ ble_events.append(("gatt_release", None))
240
+
241
+ def on_error(self, msg):
242
+ msg = str(msg)
243
+ ble_errors.append(msg)
244
+ update_ble_status(f"BLE error: {msg}")
245
+
246
+ if "ble" not in globals() or ble is None or not isinstance(ble, PythonHereBLE):
247
+ ble = PythonHereBLE()
248
+
249
+ Scanning:
250
+ - For simple scan requests, start scanning and schedule `ble.stop_scan()` after a short timeout.
251
+ - Do not scan indefinitely unless the user explicitly asks.
252
+ - Store devices by Bluetooth address in `ble_devices_by_address`.
253
+ - Store compact scan summaries in `ble_scan_summary`.
254
+ - Do not schedule delayed print summaries after scans. Print only one immediate
255
+ line before the cell returns, naming the globals where results will appear.
256
+ - If the user gives a name/address/service/manufacturer filter, use Able filters instead of scanning everything when possible.
257
+
258
+ Recommended simple scan:
259
+ from kivy.clock import Clock
260
+
261
+ def finish_ble_scan(dt):
262
+ global ble_scan_summary
263
+ try:
264
+ ble.stop_scan()
265
+ finally:
266
+ sample = []
267
+ for address, info in list(ble_devices_by_address.items())[:10]:
268
+ sample.append({
269
+ "address": address,
270
+ "name": info.get("name"),
271
+ "rssi": info.get("rssi"),
272
+ })
273
+ ble_scan_summary = {
274
+ "device_count": len(ble_devices_by_address),
275
+ "sample": sample,
276
+ }
277
+ ble_events.append(("scan_summary", ble_scan_summary))
278
+ update_ble_status(
279
+ f"BLE scan complete: {ble_scan_summary['device_count']} device(s)"
280
+ )
281
+
282
+ ble.start_scan()
283
+ Clock.schedule_once(finish_ble_scan, 8)
284
+ print("Scanning for 8 seconds. Results will be stored in ble_devices_by_address and ble_scan_summary.")
285
+
286
+ Scan filters:
287
+ - Import filters from `able.filters` when needed:
288
+ from able.filters import (
289
+ EmptyFilter,
290
+ DeviceAddressFilter,
291
+ DeviceNameFilter,
292
+ ManufacturerDataFilter,
293
+ ServiceDataFilter,
294
+ ServiceSolicitationFilter,
295
+ ServiceUUIDFilter,
296
+ )
297
+ - Use `DeviceNameFilter(name)` for exact device-name filtering.
298
+ - Use `DeviceAddressFilter("01:02:03:AB:CD:EF")` for a specific BLE address.
299
+ - Use `ServiceUUIDFilter(uuid)` for service UUID filtering.
300
+ - Use `ManufacturerDataFilter(id, data, mask=None)` for manufacturer data filtering.
301
+ - Use `ServiceDataFilter(uuid, data, mask=None)` for service data filtering.
302
+ - Filters of different kinds can be combined with `&`.
303
+ - Do not combine two filters of the same kind; Able raises `ValueError`.
304
+ - Pass filters as a list to `ble.start_scan(filters=[...])`.
305
+
306
+ Scan settings:
307
+ - Import scan setting builders when the user asks for scan mode, match mode, callback type, or low-latency scan tuning:
308
+ from able.scan_settings import ScanSettingsBuilder, ScanSettings
309
+ - Use `ScanSettingsBuilder()` and builder methods for custom settings.
310
+ - Keep settings simple unless the user asks for advanced tuning.
311
+
312
+ Advertisement parsing:
313
+ - Use `able.Advertisement` objects received by `on_device`.
314
+ - Do not manually parse raw advertisement bytes unless the user asks.
315
+ - For readable summaries, iterate over the advertisement and store parsed AD structures.
316
+ - Keep manufacturer and service data as lists of integers or bytes-like values.
317
+
318
+ Services:
319
+ - `on_services(services, status)` receives an Able `Services` dict-like object.
320
+ - Store it globally as `ble_services`.
321
+ - Use `ble_services.search(pattern)` to find a characteristic by regex pattern.
322
+ - Do not assume a characteristic UUID exists before services are discovered.
323
+ - For user-provided UUID or partial UUID, search `ble_services` first.
324
+ - Store found characteristics in a global such as `ble_characteristics_by_name`.
325
+
326
+ Recommended characteristic search:
327
+ characteristic = ble_services.search("2a37")
328
+ if characteristic is None:
329
+ print("Characteristic not found")
330
+ else:
331
+ print("Characteristic stored in characteristic")
332
+ ble_characteristic = characteristic
333
+
334
+ Connecting:
335
+ - If the user gives a BLE address, use:
336
+ ble.connect_by_device_address(address, autoconnect=False)
337
+ - If the user picks a scanned device, retrieve it from:
338
+ ble_devices_by_address[address]["device"]
339
+ then call:
340
+ ble.connect_gatt(device, autoconnect=False)
341
+ - Validate address-like strings before direct connect when possible.
342
+ - Do not assume connection succeeds immediately; wait for `on_connection_state_change`.
343
+ - After successful connection, call `discover_services()` from the connection callback or instruct the user to run it after connection.
344
+ - Store connected state in globals.
345
+
346
+ Reads and writes:
347
+ - For reads, use a characteristic object from `ble_services.search(...)` or a previously stored characteristic.
348
+ - For writes, accept bytes, bytearray, or list of integers.
349
+ - Keep values in 0..255.
350
+ - Convert simple text only when the user clearly asks to send text.
351
+ - Do not write to characteristics discovered as read-only unless the user explicitly asks to try.
352
+ - Do not repeatedly write in a loop unless the user explicitly asks.
353
+ - Store write attempts and statuses in `ble_events`.
354
+
355
+ Notifications and indications:
356
+ - Use `ble.enable_notifications(characteristic, enable=True, indication=False)` for notifications.
357
+ - Use `indication=True` only when the user asks for indications or when the characteristic is known to require indications.
358
+ - Store received notification values in `ble_notifications`.
359
+ - Provide a disable command:
360
+ ble.enable_notifications(characteristic, enable=False)
361
+
362
+ RSSI and MTU:
363
+ - Use `ble.update_rssi()` when the user asks for signal strength.
364
+ - Use `ble.request_mtu(mtu)` when the user asks to change MTU.
365
+ - Validate MTU as a reasonable positive integer before requesting.
366
+ - Do not assume MTU changed until `on_mtu_changed` reports status.
367
+
368
+ Advertising:
369
+ - Use advertising only when the user asks to advertise, broadcast, become a BLE peripheral advertiser, or send advertisement data.
370
+ - Import advertising helpers when needed:
371
+ from able.advertising import (
372
+ Advertiser,
373
+ AdvertiseData,
374
+ DeviceName,
375
+ TXPowerLevel,
376
+ ServiceUUID,
377
+ ServiceData,
378
+ ManufacturerData,
379
+ Interval,
380
+ TXPower,
381
+ Status,
382
+ )
383
+ - Create one global advertiser reference:
384
+ ble_advertiser
385
+ - Stop existing advertising before starting a new advertiser if appropriate.
386
+ - Keep advertising payload small.
387
+ - Do not include private data in BLE advertisements.
388
+ - Store advertising status in:
389
+ ble_advertising_result
390
+ - Provide a stop helper for advertising:
391
+ def stop_ble_advertising():
392
+ ble_advertiser.stop()
393
+
394
+ Advertising example:
395
+ from able.advertising import Advertiser, AdvertiseData, DeviceName, TXPowerLevel, Interval, TXPower
396
+
397
+ ble_advertiser = Advertiser(
398
+ ble=ble,
399
+ data=AdvertiseData(DeviceName()),
400
+ scan_data=AdvertiseData(TXPowerLevel()),
401
+ interval=Interval.HIGH,
402
+ tx_power=TXPower.MEDIUM,
403
+ )
404
+ ble_advertiser.start()
405
+ print("Started BLE advertising; advertiser stored in ble_advertiser")
406
+
407
+ Permissions:
408
+ - Able methods that require the Bluetooth adapter can request runtime permissions and ask the user to enable Bluetooth.
409
+ - Target API level <= 30 commonly needs `ACCESS_FINE_LOCATION` to obtain BLE scan results.
410
+ - Target API level >= 31 commonly needs `BLUETOOTH_CONNECT`, `BLUETOOTH_SCAN`, `ACCESS_FINE_LOCATION`, and sometimes `BLUETOOTH_ADVERTISE`.
411
+ - Able permission constants are available as:
412
+ from able import Permission
413
+ Permission.ACCESS_FINE_LOCATION
414
+ Permission.ACCESS_BACKGROUND_LOCATION
415
+ Permission.BLUETOOTH_CONNECT
416
+ Permission.BLUETOOTH_SCAN
417
+ Permission.BLUETOOTH_ADVERTISE
418
+ - The requested permission list can be overridden with:
419
+ BluetoothDispatcher(runtime_permissions=[...])
420
+ - Do not duplicate generic Android permission request code here; use Able's dispatcher behavior or the always-enabled Android permissions prompt when the user explicitly asks for permission-specific checks.
421
+ - Do not claim BLE permissions are granted until the relevant operation callback or permission result confirms success.
422
+
423
+ Bluetooth adapter:
424
+ - `ble.adapter` returns the local Android BluetoothAdapter Java object or None.
425
+ - `ble.name` reads or sets the adapter name.
426
+ - `ble.bonded_devices` returns Java BluetoothDevice objects for paired devices.
427
+ - If the adapter is disabled, Able may launch the system activity to let the user enable Bluetooth.
428
+ - Do not assume BLE is available on all devices.
429
+
430
+ Diagnostics:
431
+ - For debugging, print readable compact diagnostics.
432
+ - Useful diagnostics include:
433
+ - whether the shared `ble` object exists,
434
+ - class name of `ble`,
435
+ - `ble.adapter is not None`,
436
+ - `ble.name`,
437
+ - number of `ble.bonded_devices`,
438
+ - number of discovered devices,
439
+ - discovered device addresses/names/RSSI,
440
+ - whether `ble.gatt` is not None,
441
+ - whether `ble_services` exists,
442
+ - recent `ble_events`,
443
+ - recent `ble_errors`,
444
+ - recent notifications.
445
+ - Do not print private data from arbitrary BLE payloads unless the user asks to inspect that payload.
446
+ - Do not access unrelated phone data such as contacts, SMS, call logs, files, camera, microphone, or location for BLE diagnostics.
447
+
448
+ Cleanup:
449
+ - For cleanup, prefer:
450
+ ble.stop_scan()
451
+ ble.close_gatt()
452
+ - For advertising cleanup, call:
453
+ ble_advertiser.stop()
454
+ - For notification cleanup, disable notifications on the characteristic if known.
455
+ - Keep cleanup helpers simple and globally available:
456
+ stop_ble_scan()
457
+ disconnect_ble()
458
+ stop_ble_advertising()
459
+ - Do not set `ble = None` unless the user asks to release the dispatcher itself.
460
+ - Do not leave scans or advertising running without a stop path.
461
+
462
+ Recommended cleanup helpers:
463
+ def stop_ble_scan():
464
+ try:
465
+ ble.stop_scan()
466
+ print("BLE scan stopped")
467
+ except Exception as exc:
468
+ print("Could not stop BLE scan:", repr(exc))
469
+
470
+ def disconnect_ble():
471
+ try:
472
+ ble.close_gatt()
473
+ print("BLE GATT closed")
474
+ except Exception as exc:
475
+ print("Could not close BLE GATT:", repr(exc))
476
+
477
+ def stop_ble_advertising():
478
+ if "ble_advertiser" not in globals() or ble_advertiser is None:
479
+ print("No ble_advertiser global found")
480
+ return
481
+ try:
482
+ ble_advertiser.stop()
483
+ print("BLE advertising stopped")
484
+ except Exception as exc:
485
+ print("Could not stop BLE advertising:", repr(exc))
486
+
487
+ Good command examples:
488
+ - Initialize or reuse:
489
+ from able import BluetoothDispatcher
490
+
491
+ if "ble" not in globals() or ble is None:
492
+ ble = BluetoothDispatcher()
493
+
494
+ - Scan briefly:
495
+ from kivy.clock import Clock
496
+
497
+ ble.start_scan()
498
+ Clock.schedule_once(lambda dt: ble.stop_scan(), 8)
499
+
500
+ - Scan by device name:
501
+ from able.filters import DeviceNameFilter
502
+ from kivy.clock import Clock
503
+
504
+ ble.start_scan(filters=[DeviceNameFilter("MyDevice")])
505
+ Clock.schedule_once(lambda dt: ble.stop_scan(), 8)
506
+
507
+ - Connect by address:
508
+ ble.connect_by_device_address("01:02:03:AB:CD:EF", autoconnect=False)
509
+
510
+ - Connect scanned device:
511
+ device = ble_devices_by_address["01:02:03:AB:CD:EF"]["device"]
512
+ ble.connect_gatt(device, autoconnect=False)
513
+
514
+ - Find a characteristic:
515
+ characteristic = ble_services.search("2a37")
516
+
517
+ - Read a characteristic:
518
+ ble.read_characteristic(characteristic)
519
+
520
+ - Write bytes:
521
+ ble.write_characteristic(characteristic, bytes([1, 2, 3]))
522
+
523
+ - Enable notifications:
524
+ ble.enable_notifications(characteristic, enable=True)
525
+
526
+ - Disable notifications:
527
+ ble.enable_notifications(characteristic, enable=False)
528
+
529
+ - Request MTU:
530
+ ble.request_mtu(247)
531
+
532
+ - Read RSSI:
533
+ ble.update_rssi()
534
+
535
+ Avoid:
536
+ - Do not start endless scans.
537
+ - Do not connect repeatedly in a tight loop.
538
+ - Do not write repeatedly in a tight loop.
539
+ - Do not schedule delayed `print(...)` summaries for scan results.
540
+ - Do not assume scan, connect, services, read, write, notify, RSSI, or MTU operations are synchronous.
541
+ - Do not assume a BLE address, UUID, service, characteristic, or descriptor exists before checking.
542
+ - Do not print huge advertisement dumps, service trees, or notification streams directly.
543
+ - Do not include personal or sensitive data in BLE advertisements.
544
+
545
+ For simple user requests:
546
+ - If the user asks to scan, generate a minimal Able scan cell with a shared dispatcher, event handlers, `ble_devices_by_address`, and a scheduled stop.
547
+ - If the user asks to connect, use a scanned device or direct address and store connection events.
548
+ - If the user asks to list services, call `discover_services()` after connection and store `ble_services`.
549
+ - If the user asks to read, search or use the provided characteristic and call `read_characteristic`.
550
+ - If the user asks to write, validate the value and call `write_characteristic`.
551
+ - If the user asks for notifications, enable notifications and store values in `ble_notifications`.
552
+ - If the user asks to advertise, use `able.advertising` and store `ble_advertiser`.
553
+ - If the user asks for diagnostics, print compact BLE state and recent events.
554
+ - If the user asks for cleanup, stop scan, stop advertising if active, and close GATT.