pythonhere 0.2.2__py3-none-any.whl → 0.3.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.
- pythonhere/.agents/skills/pythonhere/SKILL.md +230 -0
- pythonhere/.agents/skills/pythonhere/agents/openai.yaml +4 -0
- pythonhere/.agents/skills/pythonhere/references/able.md +554 -0
- pythonhere/.agents/skills/pythonhere/references/android-media.md +130 -0
- pythonhere/.agents/skills/pythonhere/references/android-packages.md +69 -0
- pythonhere/.agents/skills/pythonhere/references/android-permissions.md +195 -0
- pythonhere/.agents/skills/pythonhere/references/android-runtime.md +34 -0
- pythonhere/.agents/skills/pythonhere/references/jnius.md +432 -0
- pythonhere/.agents/skills/pythonhere/references/kivy-kv.md +241 -0
- pythonhere/.agents/skills/pythonhere/references/kivy-runtime.md +305 -0
- pythonhere/.agents/skills/pythonhere/references/midi.md +248 -0
- pythonhere/.agents/skills/pythonhere/references/plyer.md +202 -0
- pythonhere/magic_here/shortcuts.py +2 -2
- pythonhere/server_here.py +1 -1
- pythonhere/tools_here.py +359 -0
- pythonhere/version_here.py +1 -1
- pythonhere/window_here.py +24 -9
- {pythonhere-0.2.2.dist-info → pythonhere-0.3.0.dist-info}/METADATA +11 -2
- {pythonhere-0.2.2.dist-info → pythonhere-0.3.0.dist-info}/RECORD +22 -9
- {pythonhere-0.2.2.dist-info → pythonhere-0.3.0.dist-info}/WHEEL +1 -1
- {pythonhere-0.2.2.dist-info → pythonhere-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {pythonhere-0.2.2.dist-info → pythonhere-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -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 `there` commands:
|
|
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 `there` commands 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 `there run` 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 `there run` command returns.
|
|
120
|
+
- Do not schedule delayed functions whose purpose is to print scan summaries.
|
|
121
|
+
A delayed `Clock.schedule_once(...)` callback may run after `there run` 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 `there` commands can use them.
|
|
127
|
+
- Do not print huge advertisement dumps or service trees directly.
|
|
128
|
+
- Print compact immediate summaries from the initiating `there run` command 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 `there run` command 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, run a minimal Able scan program 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.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
## Android Media And Files
|
|
2
|
+
|
|
3
|
+
Use this section when the user asks to browse, display, scan, filter, or build a
|
|
4
|
+
gallery from Android photos, videos, downloads, `/sdcard`, DCIM, Pictures,
|
|
5
|
+
Movies, Music, or other shared-storage paths.
|
|
6
|
+
|
|
7
|
+
Permissions and storage access:
|
|
8
|
+
|
|
9
|
+
- Always check actual runtime permission state before assuming media or storage access exists.
|
|
10
|
+
- For Android 13+ media-library access, use granular media permissions such as
|
|
11
|
+
`android.permission.READ_MEDIA_IMAGES` and/or
|
|
12
|
+
`android.permission.READ_MEDIA_VIDEO`.
|
|
13
|
+
- For older Android versions, `READ_EXTERNAL_STORAGE` may still be relevant.
|
|
14
|
+
- Do not rely on `WRITE_EXTERNAL_STORAGE` for reading photos on modern Android.
|
|
15
|
+
- Do not call `check_permission("android.permission.MANAGE_EXTERNAL_STORAGE")`.
|
|
16
|
+
It is a special app-access setting, not a normal runtime permission.
|
|
17
|
+
- On Android 11+ broad all-files access can be checked with
|
|
18
|
+
`Environment.isExternalStorageManager()`, but a false result does not by
|
|
19
|
+
itself prove every media path is unreadable. Probe the requested path and
|
|
20
|
+
report both facts.
|
|
21
|
+
- Do not assume `MANAGE_EXTERNAL_STORAGE` is available just because it is present
|
|
22
|
+
in the manifest. The user usually must enable “All files access” in system
|
|
23
|
+
settings, and Play policy restricts this permission.
|
|
24
|
+
- Prefer privacy-friendly media access through MediaStore or the system photo
|
|
25
|
+
picker unless the user specifically asks for direct filesystem browsing.
|
|
26
|
+
- Store the error/status and show errors.
|
|
27
|
+
|
|
28
|
+
Path vs MediaStore access:
|
|
29
|
+
|
|
30
|
+
- For a concrete path such as `/sdcard/DCIM/Camera`, first probe that path
|
|
31
|
+
directly before deciding the app lacks access.
|
|
32
|
+
- For media-library/gallery access, prefer MediaStore queries.
|
|
33
|
+
- Do not depend on the MediaStore `_data` column. It can be missing, deprecated,
|
|
34
|
+
inaccessible, or point to a file path the app cannot decode directly.
|
|
35
|
+
- Prefer querying `_id`, building a `content://` URI with
|
|
36
|
+
`ContentUris.withAppendedId(...)`, and reading through
|
|
37
|
+
`context.getContentResolver().openInputStream(uri)`.
|
|
38
|
+
- Decode MediaStore images from a valid Android `Uri` or input stream, not from
|
|
39
|
+
guessed filesystem paths.
|
|
40
|
+
- If direct path access is used as a fallback, treat failure to read/decode as a
|
|
41
|
+
normal state and continue.
|
|
42
|
+
|
|
43
|
+
MediaStore querying:
|
|
44
|
+
|
|
45
|
+
- Always handle `cursor is None`.
|
|
46
|
+
- Always check `cursor.getCount()` and show an empty-state UI when it is zero.
|
|
47
|
+
- Use `moveToFirst()` safely before reading rows.
|
|
48
|
+
- Always close the cursor in `finally`.
|
|
49
|
+
- Avoid `while cursor.isAfterLast() is False`; prefer clearer logic such as:
|
|
50
|
+
`if cursor.moveToFirst(): ... while not cursor.isAfterLast(): ...`.
|
|
51
|
+
- Log counts separately:
|
|
52
|
+
- rows found
|
|
53
|
+
- thumbnails attempted
|
|
54
|
+
- thumbnails decoded
|
|
55
|
+
- thumbnails failed
|
|
56
|
+
- permission/access state
|
|
57
|
+
|
|
58
|
+
HEIC and thumbnails:
|
|
59
|
+
|
|
60
|
+
- Kivy `Image` may not load `.heic` directly on Android.
|
|
61
|
+
- Use Android image decoding APIs such as `android.graphics.BitmapFactory` or
|
|
62
|
+
`android.graphics.ImageDecoder` for HEIC thumbnails when available.
|
|
63
|
+
- Import nested Android classes with `$`. For bitmap decoding options, define
|
|
64
|
+
`BitmapFactoryOptions = autoclass("android.graphics.BitmapFactory$Options")`
|
|
65
|
+
and use `BitmapFactoryOptions()`. Do not access it as `BitmapFactory.Options()`.
|
|
66
|
+
- With `ImageDecoder` and a Java `File`, prefer:
|
|
67
|
+
`source = ImageDecoder.createSource(java_file)`.
|
|
68
|
+
- Do not call:
|
|
69
|
+
`ImageDecoder.createSource(context.getContentResolver(), java_file)`,
|
|
70
|
+
because the `ContentResolver` overload expects a `Uri`, not a `File`.
|
|
71
|
+
- If using a `ContentResolver` with `ImageDecoder`, pass a valid Android `Uri`,
|
|
72
|
+
not a filesystem path or Java `File`.
|
|
73
|
+
- Avoid `ImageDecoder.decodeBitmap(source, python_lambda)` unless a correct Java
|
|
74
|
+
listener interface is implemented. Through Pyjnius, prefer:
|
|
75
|
+
`bitmap = ImageDecoder.decodeBitmap(source)`,
|
|
76
|
+
then scale/compress the decoded bitmap.
|
|
77
|
+
- Import nested Android classes with `$`. For bitmap compression, define:
|
|
78
|
+
`CompressFormat = autoclass("android.graphics.Bitmap$CompressFormat")`
|
|
79
|
+
and use `CompressFormat.JPEG` or `CompressFormat.PNG`.
|
|
80
|
+
- Do not access compression format as `Bitmap.CompressFormat`.
|
|
81
|
+
- Do not assume Kivy `Image.source` accepts `data:image/...;base64,...` URIs.
|
|
82
|
+
Prefer writing thumbnails to small temporary files in the app cache directory
|
|
83
|
+
and setting `Image.source` to those file paths.
|
|
84
|
+
- Use `context.getCacheDir().getAbsolutePath()` for thumbnail cache files.
|
|
85
|
+
- Avoid writing into `/sdcard` unless the user asks for exported files.
|
|
86
|
+
- Keep Android bitmap dimensions as plain Python `int` pixel values.
|
|
87
|
+
- Do not pass Kivy `dp(...)` float values directly to Android bitmap APIs such
|
|
88
|
+
as `Bitmap.createScaledBitmap(...)`.
|
|
89
|
+
- Use separate constants for UI size and decode size, for example:
|
|
90
|
+
`THUMB_UI_DP = dp(120)` for widgets and `THUMB_PX = 240` for Android bitmap
|
|
91
|
+
scaling.
|
|
92
|
+
- Recycle Android `Bitmap` objects after thumbnail compression when possible.
|
|
93
|
+
|
|
94
|
+
Generated-code defaults:
|
|
95
|
+
|
|
96
|
+
- For “show my gallery/photos” code, default to:
|
|
97
|
+
MediaStore query → `_id` → content URI → openInputStream/decode → cache
|
|
98
|
+
thumbnail file → Kivy Image source = cache file path.
|
|
99
|
+
- Avoid defaulting to:
|
|
100
|
+
MediaStore `_data` → raw filesystem path → `BitmapFactory.decodeFile(...)`.
|
|
101
|
+
- Include a visible debug/status label during development.
|
|
102
|
+
- Include enough logging to distinguish:
|
|
103
|
+
permission not granted,
|
|
104
|
+
MediaStore returned no rows,
|
|
105
|
+
rows found but decode failed,
|
|
106
|
+
thumbnails decoded but widget display failed.
|
|
107
|
+
|
|
108
|
+
Android 14+ partial photo/video access:
|
|
109
|
+
|
|
110
|
+
- On Android 14+ (API 34+), photo/video access may be partial because the user
|
|
111
|
+
selected only some media. Generated gallery code must report whether access
|
|
112
|
+
appears full, partial, denied, or unknown when permission information is
|
|
113
|
+
available.
|
|
114
|
+
- If only partial access is available, continue with MediaStore and show the
|
|
115
|
+
accessible subset instead of treating the result as a failure.
|
|
116
|
+
- When the user wants to choose media rather than browse the whole library,
|
|
117
|
+
prefer a system picker/user-selection flow over broad storage/media
|
|
118
|
+
permissions.
|
|
119
|
+
- Keep the debug/status label explicit: distinguish `permission_denied`,
|
|
120
|
+
`partial_media_access`, `mediastore_empty`, `decode_failed`, and
|
|
121
|
+
`display_ready`.
|
|
122
|
+
|
|
123
|
+
Query defaults:
|
|
124
|
+
|
|
125
|
+
- For general gallery requests, query images first unless the user asked for
|
|
126
|
+
videos or audio too. Avoid scanning every media type by default.
|
|
127
|
+
- Limit initial thumbnail queries to a reasonable count such as 50-100 items,
|
|
128
|
+
store full metadata in a global, and render a small preview first.
|
|
129
|
+
- Do not assume an empty MediaStore result means there are no photos on the
|
|
130
|
+
device; report permission/access state and query filters too.
|