Bridge the browser's raw WebSocket to the sensing-server's /ws/sensing.
()
| 169 | import ble_provisioning |
| 170 | # Retire any previous peripheral BEFORE building a new one. Overwriting |
| 171 | # the reference used to drop a still-registered server on the floor — |
| 172 | # its GLib thread and, worse, its BlueZ registration of the fixed path |
| 173 | # /one/gode/ragnar/ble survived, so the new server's |
| 174 | # RegisterApplication came back org.bluez.Error.AlreadyExists. |
| 175 | old = None |
| 176 | with _ble_lock: |
| 177 | if _ble_server is not None: |
| 178 | if _ble_server.status().get('running'): |
| 179 | return True |
| 180 | old, _ble_server = _ble_server, None |
| 181 | if old is not None: |
| 182 | # Outside the lock: stop() joins the loop thread and would otherwise |
| 183 | # stall the status endpoint the UI polls. |
| 184 | try: |
| 185 | old.stop() |
| 186 | except Exception: |
| 187 | pass |
| 188 | |
| 189 | base_dir = os.path.dirname(os.path.abspath(__file__)) |
| 190 | with _ble_lock: |
| 191 | # Another caller may have started one while we were stopping. |
| 192 | if _ble_server is not None and _ble_server.status().get('running'): |
| 193 | return True |
| 194 | _ble_server = ble_provisioning.build_server( |
| 195 | base_dir, |
| 196 | get_config=lambda: shared_data.config, |
| 197 | get_ap_state=_ble_ap_state, |
| 198 | set_ap=_ble_set_ap, |
| 199 | logger=logger, |
| 200 | ) |
| 201 | if _ble_server is None: |
| 202 | return False |
| 203 | server = _ble_server |
| 204 | # start() blocks until BlueZ registers or the wait times out. Do that |
| 205 | # OUTSIDE the lock: GET /api/ble/provisioning takes the same lock, so |
| 206 | # holding it here made the status the UI polls hang for the full wait — |
| 207 | # exactly while the user is watching for it to come up. |
| 208 | return server.start() |
| 209 | except Exception as e: # pragma: no cover - hardware/D-Bus dependent |
| 210 | logger.error(f"BLE provisioning failed to start: {e}") |
| 211 | return False |
| 212 | |
| 213 | |
| 214 | def _stop_ble_provisioning(): |
| 215 | global _ble_server |
| 216 | with _ble_lock: |
| 217 | if _ble_server is not None: |
| 218 | _ble_server.stop() |
| 219 | _ble_server = None |
| 220 | |
| 221 | |
| 222 | def _ble_adapters(): |
| 223 | """Available Bluetooth controllers for the config picker. Never raises.""" |
| 224 | try: |
| 225 | import ble_provisioning |
| 226 | return ble_provisioning.list_controllers() |
| 227 | except Exception: |
| 228 | return [] |