{"id":46083893,"url":"https://github.com/tensorturtle/bleak-fsm","last_synced_at":"2026-03-01T16:17:04.365Z","repository":{"id":228829593,"uuid":"767943526","full_name":"tensorturtle/bleak-fsm","owner":"tensorturtle","description":"Finite state machine abstraction over Python bleak library.","archived":false,"fork":false,"pushed_at":"2024-03-28T05:29:42.000Z","size":373,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-03-28T06:33:36.354Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tensorturtle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-03-06T07:20:49.000Z","updated_at":"2024-03-28T06:33:36.355Z","dependencies_parsed_at":"2024-03-20T18:01:55.185Z","dependency_job_id":null,"html_url":"https://github.com/tensorturtle/bleak-fsm","commit_stats":null,"previous_names":["tensorturtle/bleak-fsm"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/tensorturtle/bleak-fsm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tensorturtle%2Fbleak-fsm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tensorturtle%2Fbleak-fsm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tensorturtle%2Fbleak-fsm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tensorturtle%2Fbleak-fsm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tensorturtle","download_url":"https://codeload.github.com/tensorturtle/bleak-fsm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tensorturtle%2Fbleak-fsm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29974700,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T15:41:30.362Z","status":"ssl_error","status_checked_at":"2026-03-01T15:37:07.343Z","response_time":124,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-01T16:17:03.708Z","updated_at":"2026-03-01T16:17:04.357Z","avatar_url":"https://github.com/tensorturtle.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bleak-FSM\n\n**A Finite State Machine abstraction over the Bleak Bluetooth library for simplified state management in production Python applications.**\n\n## Introduction\n\n[Bleak](https://github.com/hbldh/bleak) provides an excellent cross-platform API for connecting to Bluetooth devices using Python. \n\nHowever, it lacks guidance for incorporating it into a production application. Developers using the Bleak library are expected to manually keep track of the bluetooth connection status for the bluetooth adapter and for each device. This can result in applications storing bluetooth state shared between various components of the frontend and backend. We believe this to be an anti-pattern.\n\n**Bleak-FSM** makes it easy to keep track of all state in the same program that actually interfaces with bluetooth. This library is an opinionated abstraction over Bleak that uses the concept of [Finite State Machines](https://en.wikipedia.org/wiki/Finite-state_machine) to make explicit the status of scanned / connected devices across a full user application lifecycle. Basically, `bleak-fsm` defines several possible \"states\" and specific methods to transition between those states.\n\nThis library inherits all of the cross-platform compatibility of Bleak, supporting Mac, Linux, Windows, and Android. It's tested on the first three.\n\n## Concepts\n\n`BleakModel`(found in [bleak_model.py](bleak_fsm/bleak_model.py)) represents the bluetooth adapter on your system. This library supports one adapter per program. It follows that scanning is a class method of the `BleakModel`, like so: `await BleakModel.start_scan()`. You must scan before setting the connection targets for instances of the `BleakModel` class.\n\nAs a rule, transitions may fail but no runtime exceptions are raised. In other words, the worst that can happen is that the state didn't change, so you must re-try.\n\nYou are responsible for detecting failed transitions by:\n1. Checking the return value (boolean) of the transition function. E.g. `successful = await model.connect(); assert successful`\n2. Or, by checking the state of the model after the attempted transition: `await model.connect(); assert model.state==\"Connected\"`\n\nIf you fail to do the above, a `MachineError` may be thrown to prevent illegal transition attempts.\n\nInstances of `BleakModel` represents individual BLE devices that you wish to connect to. You may transition between the following states: `Init`, `TargetSet`, `Connected`, and `Streaming` using methods: `set_target()`, `connect()`, `stream()`, `disconnect()`, and `unset_target`. The `clean_up()` method can be used as a shortcut to transition instances of BleakModel in any state back to `Init` for whenever exceptions are raised or the program quits.\n\n## Installation\n\n```\npip3 install bleak-fsm\n```\n\nBleak-FSM is compatible with Python versions 3.8 to 3.12. See [pyproject.toml](pyproject.toml)\n\n## Quickstart\n\nBleak-FSM is designed for use within Python scripts. Nevertheless, the following demonstration uses the Python REPL:\n\nFrom your command line, start the interactive Python interpeter:\n```bash\npython3 # or python, depending on OS and installation.\n```\n\nAll following commands are to be copy-pasted into the Python REPL, after `\u003e\u003e\u003e`.\n\n```python\nfrom bleak_fsm import BleakModel\nimport asyncio\n```\n\nCreate asyncio event loop:\n```python\nloop = asyncio.new_event_loop()\n```\n\nStart scan\n```python\nloop.run_until_complete(BleakModel.start_scan())\n```\n\nHere, we use `loop.run_until_complete()`, but typically you would `await BleakModel.start_scan()` inside a pre-existing async function.\n\nWait for a few seconds for the scan to find some devices.\n\nEnd scan\n```python\nloop.run_until_complete(BleakModel.stop_scan())\n```\n\nList discovered devices:\n```python\nBleakModel.bt_devices\n```\n\nIt might fill up your screen in a messy way. Here's a prettier way to print it:\n\n```python\nfor k, v in BleakModel.bt_devices.items(): print(f\"Address: {k}. Name: {v[0].name}\")\n```\n\nUse one of the addresses. For this example, we'll pick a device that implement bluetooth heart rate monitoring.\n\n```python\ntarget_address = \"14863C4D-BF71-4EA3-C6B4-98001056AAF8\" # ENTER YOUR OWN HERE\n```\n\nCreate a BleakModel object representing that device.\n```python\nmodel = BleakModel(connection_timeout=10)\nloop.run_until_complete(model.set_target(target_address))\nmodel.state # prints: \"TargetSet\"\n```\n\nConnect to it:\n```python\nloop.run_until_complete(model.connect())\nmodel.state # should print: \"Connected\".\n```\nIf the state isn't \"Connected\", try increasing the `connection_timeout`.\n\nAt this point, we need to tell our `model` the details on how to connect and process the data from it.\n\nDefine the GATT characteristic UUID that you want to query:\n```python\nHEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID = \"00002a37-0000-1000-8000-00805f9b34fb\"\n```\n\nFor this example, we'll save the output to a text file.\n```python\ndef handle_hr_measurement(sender, data): open(\"hr_output.txt\", \"a\").write(f\"Data received from {sender}: {data}\\nHeart Rate: {data[1]} beats per minute\\n\")\n```\n\nDefine three important methods on our `model`. You need to pass in a Callable (a function) that takes a single `client` argument. \nCompare this code to [`bleak` examples](https://github.com/hbldh/bleak/blob/develop/examples/enable_notifications.py).\n\nIn this example, our `client` is a straightforward `BleakClient`. See [examples](/examples/) directory for using Pycycling objects and other abstractions.\n\n```python\nmodel.enable_notifications = lambda client: client.start_notify(HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID, handle_hr_measurement)\n\nmodel.disable_notifications = lambda client: client.stop_notify(HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID)\n\nmodel.set_measurement_handler = lambda client: handle_hr_measurement\n```\n\nStream the heart rate measurements from it:\n```python\nloop.run_until_complete(model.stream())\n```\n\nYou won't immediately see any output. The Python REPL doesn't really support that. But after you disconnect, you should see a new text file `hr_output.txt` that has the recorded heart rate (as defined by our `handle_hr_measurement` callback).\n\n```python\nloop.run_until_complete(model.clean_up())\n```\n\n## Examples\n\nClone this repository and check out the guides in the [examples](examples/) directory to get more familiar with Bleak-FSM.\n\nStart with the [basic Jupyter notebook tutorial](examples/single_hr_notebook_example.ipynb). It uses Heart Rate monitor as an example. For all of these demos, you need to modify the target bluetooth device address/name.\n\nIf you don't have a device with heart rate service, refer to the [Migration Guide notebook](examples/migration_guide.ipynb) to use your Bluetooth device with Bleak-FSM.\n\n[dual_notebook_example.ipynb](examples/dual_notebook_example.ipynb) builds on the basic tutorial and demonstrates simultaneous connection to two different Bluetooth devices.\n\nFor a regular python script version, see [single_hr_script_example.py](examples/single_hr_script_example.py).\n\nFor developer convenience, this library implements Python context managers (`with` blocks). See [context manager notebook example](examples/context_manager.ipynb) for a quick demonstration.\n\n\n## Migrating from Vanilla Bleak\n\nThe following is a non-functioning code snippet that shows what your `bleak` code would look like after migrating to `bleak-fsm`:\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003cth\u003eVanilla Bleak\u003c/th\u003e\n\u003cth\u003eBleak-FSM\u003c/th\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n    \n```python\n# Setup\n\nfrom bleak import BleakClient\n\ndef handle_hr_measurement(sender, data):\n    heart_rate = data[1]\n    print(f\"HR: {heart_rate}\")\n\n# Scanning process not shown\n# Somewhere in your application logic\n\nasync with BleakClient(device) as client:\n    logger.info(\"Connected\")\n\n    await client.start_notify(\n        HEART_RATE_CHARACTERISTIC,\n        handle_hr_measurement)\n    await asyncio.sleep(5.0)\n    await client.stop_notify(\n        HEART_RATE_CHARACTERISTIC)\n```\n\n\u003c/td\u003e\n\u003ctd\u003e\n    \n```python\n# Setup\n\nfrom bleak_fsm import BleakModel\n\nmodel = BleakModel()\n\ndef handle_hr_measurement(value):\n    print(f\"HR: {value}\")\n\n# pass in the same Callable as used in\n# Vanilla Bleak\nmodel.enable_notifications =\n    lambda client: client.start_notify(\n        HEART_RATE_CHARACTERISTIC,\n        handle_hr_measurement)\n\nmodel.disable_notifications =\n    lambda client: client.stop_notify(\n        HEART_RATE_CHARACTERISTIC)\n\n# Scanning process not shown\n# Somewhere in your application logic\n\nawait model.connect()\nprint(model.state) # \"Connected\"\n\nawait model.stream()\nprint(model.state) # \"Streaming\"\n\nawait asyncio.sleep(5)\nprint(model.state) # \"Streaming\"\n\nawait model.disonnect()\nprint(model.state) # \"TargetSet\"\n\n```\n\n\n\u003c/td\u003e \n\u003c/tr\u003e \n\u003c/table\u003e\n\n## Error Handling\n\nThe methods of `BleakModel` do not raise Exceptions, and only return True or False.\nThe Finite State Machine `machine` uses that Boolean to determine if transition is successful or not.\n\nTherefore, for you, the worst case scenario is that the transition fails.\nRuntime exceptions are never thrown.\n\n## Pycycling Compatibility\n\n`bleak-fsm` is designed to accomodate [`pycycling`](https://github.com/zacharyedwardbull/pycycling). The [basic tutorial notebook](examples/single_hr_notebook_example.ipynb) has parallel examples of using either raw BleakClient or a Pycycling object. \n\n## Limitations \u0026 Future Features\n\n+ Only unidirectional (peripheral to computer) communication is supported. Bidirectional support coming in a future version.\n+ While this framework enforces correct order of states and transitions, there is no mechanism for constantly checking for the validity of the current state. For example, a supposedly 'Connected' device may power down, move outside of Bluetooth range, etc. A supposedly 'Streaming' device may also power down, stop its stream, etc. This library has no-built in machinery to detect this. However, if you do want to build such a thing, this library provides good building blocks for it.\n\n## Further Resources \u0026 Recommendations\n\nThis library uses `async` Python. Familiarize yourself with the basics of async Python before using this library. \n\nIf you end up nesting asyncio (for example, if your web server is already async and you want to call this code), you may want to install and use `nest-asyncio`.\n```\nimport nest_asyncio\nnest_asyncio.apply()\n```\n\nRead the [`pytransitions` README](https://github.com/pytransitions/transitions/blob/master/README.md) for an excellent follow-along tutorial on Finite State Machines.\n\n## Bleak-FSM in the Wild\n\nA list of projects that use this library\n\n+ [react-pycycling-demo](https://github.com/tensorturtle/react-pycycling-demo) - currently private, will be made public soon.\n+ (Your Project Here) - let me know via Github issues or tensorturtle@gmail.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftensorturtle%2Fbleak-fsm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftensorturtle%2Fbleak-fsm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftensorturtle%2Fbleak-fsm/lists"}