{"id":34082512,"url":"https://github.com/sivulich/mqttasgi","last_synced_at":"2026-04-01T19:18:16.258Z","repository":{"id":39630025,"uuid":"263043292","full_name":"sivulich/mqttasgi","owner":"sivulich","description":"MQTT ASGI Protocol Server","archived":false,"fork":false,"pushed_at":"2026-02-21T15:50:55.000Z","size":153,"stargazers_count":40,"open_issues_count":0,"forks_count":17,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-28T00:58:03.953Z","etag":null,"topics":["asgi","asgi-server","asyncio","diy-iot","django","django-channels","djangochannels","iot","mqtt","python","real-time","real-time-data","websockets"],"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/sivulich.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,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-05-11T12:59:44.000Z","updated_at":"2026-02-21T15:50:58.000Z","dependencies_parsed_at":"2024-03-07T21:48:37.032Z","dependency_job_id":null,"html_url":"https://github.com/sivulich/mqttasgi","commit_stats":{"total_commits":45,"total_committers":5,"mean_commits":9.0,"dds":0.6222222222222222,"last_synced_commit":"939bb971f381d1bf6a76d82b0547b5d12ca1fa76"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/sivulich/mqttasgi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivulich%2Fmqttasgi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivulich%2Fmqttasgi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivulich%2Fmqttasgi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivulich%2Fmqttasgi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sivulich","download_url":"https://codeload.github.com/sivulich/mqttasgi/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sivulich%2Fmqttasgi/sbom","scorecard":{"id":828329,"data":{"date":"2025-08-11","repo":{"name":"github.com/sivulich/mqttasgi","commit":"6b770c18ca2dba48b2855b91e86af6c964ca3f9a"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":3,"reason":"Found 7/18 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v1.2.3 not signed: https://api.github.com/repos/sivulich/mqttasgi/releases/115376593","Warn: release artifact v1.2.2 not signed: https://api.github.com/repos/sivulich/mqttasgi/releases/103690829","Warn: release artifact v1.2.3 does not have provenance: https://api.github.com/repos/sivulich/mqttasgi/releases/115376593","Warn: release artifact v1.2.2 does not have provenance: https://api.github.com/repos/sivulich/mqttasgi/releases/103690829"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 19 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T17:05:35.073Z","repository_id":39630025,"created_at":"2025-08-23T17:05:35.073Z","updated_at":"2025-08-23T17:05:35.073Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31291117,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: 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":["asgi","asgi-server","asyncio","diy-iot","django","django-channels","djangochannels","iot","mqtt","python","real-time","real-time-data","websockets"],"created_at":"2025-12-14T12:18:16.764Z","updated_at":"2026-04-01T19:18:16.231Z","avatar_url":"https://github.com/sivulich.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mqttasgi - MQTT ASGI Protocol Server for Django\nmqttasgi is an ASGI protocol server that implements a complete interface for MQTT for the Django development framework. Built following [daphne](https://github.com/django/daphne) protocol server.\n\n![Downloads Shield Count](https://img.shields.io/pypi/dm/mqttasgi)\n![GitHub License](https://img.shields.io/github/license/sivulich/mqttasgi)\n![GitHub contributors](https://img.shields.io/github/contributors/sivulich/mqttasgi)\n\n# Features\n- Publish / Subscribe to any topic\n- Multiple workers to handle different topics / subscriptions.\n- Full Django ORM support within consumers.\n- Full Channel Layers support.\n- Full testing support to enable TDD (no broker required for unit tests).\n- Lightweight.\n- Django 3.2+ / Django 4.x / Django 5.x support\n- Channels 3.x / Channels 4.x support\n- paho-mqtt 1.x and 2.x support\n- Python 3.9 – 3.13 support\n\n# Installation\n```bash\npip install mqttasgi\n```\n\n**IMPORTANT NOTE:** If legacy support for Django 2.x is required install the latest 0.x mqttasgi release.\n\n\n\n# Usage\n## Running the server\nMqttasgi provides a CLI to run the protocol server.\n```bash\nmqttasgi -H localhost -p 1883 my_application.asgi:application\n```\n\n| Parameter | Explanation | Environment variable | Default |\n|-----------|-------------|:--------------------:|:-------:|\n| -H / --host | MQTT broker host | MQTT_HOSTNAME | localhost |\n| -p / --port | MQTT broker port | MQTT_PORT | 1883 |\n| -c / --cleansession | MQTT Clean Session | MQTT_CLEAN | True |\n| -v / --verbosity | Logging verbosity (0-2) | VERBOSITY | 0 |\n| -U / --username | MQTT Username | MQTT_USERNAME | |\n| -P / --password | MQTT Password | MQTT_PASSWORD | |\n| -i / --id | MQTT Client ID | MQTT_CLIENT_ID | |\n| -C / --cert | TLS Certificate | TLS_CERT | |\n| -K / --key | TLS Key | TLS_KEY | |\n| -S / --cacert | TLS CA Certificate | TLS_CA | |\n| -SSL / --use-ssl | Use SSL (no certificate auth) | MQTT_USE_SSL | False |\n| -T / --transport | Transport type (tcp or websockets) | MQTT_TRANSPORT | tcp |\n| -r / --retries | Retries on disconnect (0 = unlimited) | MQTT_RETRIES | 3 |\n| Last argument | ASGI Application | | |\n\nEnvironment variables are supported via a `.env` file at the project root. A CLI argument always takes precedence over the corresponding environment variable.\n\n## Consumer\n\nRegister your consumer in `asgi.py`:\n```python\nimport os\nimport django\nfrom channels.routing import ProtocolTypeRouter\nfrom my_application.consumers import MyMqttConsumer\nfrom django.core.asgi import get_asgi_application\n\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'my_application.settings')\n\ndjango.setup()\n\napplication = ProtocolTypeRouter({\n    'http': get_asgi_application(),\n    'mqtt': MyMqttConsumer.as_asgi(),\n})\n```\n\nYour consumer inherits from `MqttConsumer` and overrides three lifecycle methods:\n\n```python\nfrom mqttasgi.consumers import MqttConsumer\n\nclass MyMqttConsumer(MqttConsumer):\n\n    async def connect(self):\n        await self.subscribe('my/testing/topic', qos=2)\n\n    async def receive(self, mqtt_message):\n        print('Received at topic:', mqtt_message['topic'])\n        print('Payload:', mqtt_message['payload'])\n        print('QoS:', mqtt_message['qos'])\n\n    async def disconnect(self):\n        await self.unsubscribe('my/testing/topic')\n```\n\n## Consumer API\n\n### MQTT\n\n#### Publish\n\n```python\nawait self.publish(topic, payload, qos=1, retain=False)\n```\n\n#### Subscribe\n\n```python\nawait self.subscribe(topic, qos)\n```\n\n#### Unsubscribe\n\n```python\nawait self.unsubscribe(topic)\n```\n\n### Worker API — Experimental\n\nAllows running multiple consumers inside the same mqttasgi instance. Only the master consumer (the one started automatically, `instance_type='master'`) may spawn or kill workers.\n\n#### Spawn Worker\n\n`app_id` is a unique identifier, `consumer_path` is the dotted import path to the consumer class, and `consumer_params` is a dict merged into the consumer scope.\n\n```python\nawait self.spawn_worker(app_id, consumer_path, consumer_params)\n```\n\n#### Kill Worker\n\n```python\nawait self.kill_worker(app_id)\n```\n\n## Channel Layers\n\nmqttasgi supports Django Channels layer communications and group messages following the [Channel Layers](https://channels.readthedocs.io/en/stable/topics/channel_layers.html) spec.\n\nOutside the consumer:\n```python\nfrom channels.layers import get_channel_layer\nfrom asgiref.sync import async_to_sync\n\nchannel_layer = get_channel_layer()\nasync_to_sync(channel_layer.group_send)(\n    \"my.group\",\n    {\"type\": \"my.custom.message\", \"text\": \"Hi from outside the consumer\"}\n)\n```\n\nInside the consumer:\n```python\nfrom mqttasgi.consumers import MqttConsumer\n\nclass MyMqttConsumer(MqttConsumer):\n\n    async def connect(self):\n        await self.subscribe('my/testing/topic', qos=2)\n        await self.channel_layer.group_add(\"my.group\", self.channel_name)\n\n    async def receive(self, mqtt_message):\n        print('Received at topic:', mqtt_message['topic'])\n\n    async def my_custom_message(self, event):\n        print('Channel layer message:', event)\n\n    async def disconnect(self):\n        await self.unsubscribe('my/testing/topic')\n```\n\n## Testing\n\nmqttasgi ships with `MqttComunicator`, an ASGI test helper that drives your consumer directly without a running MQTT broker — perfect for fast, isolated unit tests.\n\n### Setup\n\nInstall test dependencies:\n```bash\npip install pytest pytest-asyncio django channels\n```\n\nCreate `pytest.ini` at the project root:\n```ini\n[pytest]\nasyncio_mode = auto\n```\n\nCreate `tests/conftest.py` to bootstrap Django before the tests run:\n```python\nimport django\nfrom django.conf import settings\n\ndef pytest_configure(config):\n    if not settings.configured:\n        settings.configure(\n            SECRET_KEY='test-secret-key',\n            INSTALLED_APPS=['channels'],\n            DATABASES={},\n            CHANNEL_LAYERS={\n                'default': {\n                    'BACKEND': 'channels.layers.InMemoryChannelLayer',\n                }\n            },\n        )\n        django.setup()\n```\n\n### Testing consumers\n\n`MqttComunicator` simulates the full ASGI lifecycle: it sends events to your consumer and captures what the consumer sends back, with no broker involved.\n\n```python\n# tests/test_consumers.py\nimport pytest\nfrom mqttasgi.testing import MqttComunicator\nfrom mqttasgi.consumers import MqttConsumer\n\n\nclass EchoConsumer(MqttConsumer):\n    async def connect(self):\n        await self.subscribe('test/topic', qos=1)\n\n    async def receive(self, mqtt_message):\n        await self.publish('test/response', mqtt_message['payload'], qos=1)\n\n    async def disconnect(self):\n        await self.unsubscribe('test/topic')\n\n\nasync def test_connect_sends_subscribe():\n    \"\"\"connect() should subscribe to the expected topic.\"\"\"\n    comm = MqttComunicator(EchoConsumer.as_asgi(), app_id=1)\n    response = await comm.connect()\n    assert response['type'] == 'mqtt.sub'\n    assert response['mqtt']['topic'] == 'test/topic'\n    assert response['mqtt']['qos'] == 1\n    await comm.disconnect()\n\n\nasync def test_disconnect_sends_unsubscribe():\n    \"\"\"disconnect() should unsubscribe from all topics.\"\"\"\n    comm = MqttComunicator(EchoConsumer.as_asgi(), app_id=1)\n    await comm.connect()\n    await comm.disconnect()\n    response = await comm.receive_from()\n    assert response['type'] == 'mqtt.usub'\n    assert response['mqtt']['topic'] == 'test/topic'\n\n\nasync def test_echo():\n    \"\"\"Consumer should publish a response for each received message.\"\"\"\n    comm = MqttComunicator(EchoConsumer.as_asgi(), app_id=1)\n    await comm.connect()\n    await comm.publish('test/topic', b'hello', qos=1)\n    response = await comm.receive_from()\n    assert response['type'] == 'mqtt.pub'\n    assert response['mqtt']['topic'] == 'test/response'\n    assert response['mqtt']['payload'] == b'hello'\n    await comm.disconnect()\n\n\nasync def test_consumer_params_passed_to_scope():\n    \"\"\"Custom parameters should be available in the consumer scope.\"\"\"\n    received = {}\n\n    class ParamConsumer(MqttConsumer):\n        async def connect(self):\n            received.update(self.scope)\n            await self.subscribe('dummy', 1)\n        async def receive(self, mqtt_message): pass\n        async def disconnect(self): pass\n\n    comm = MqttComunicator(\n        ParamConsumer.as_asgi(),\n        app_id=5,\n        consumer_parameters={'device_id': 'sensor-01'},\n    )\n    await comm.connect()\n    assert received['device_id'] == 'sensor-01'\n    assert received['app_id'] == 5\n    await comm.disconnect()\n```\n\n### MqttComunicator API\n\n| Method | Description |\n|--------|-------------|\n| `MqttComunicator(app, app_id, instance_type='worker', consumer_parameters=None)` | Create a communicator for the given ASGI app |\n| `await comm.connect(timeout=1)` | Send `mqtt.connect` to the consumer and return the first response |\n| `await comm.publish(topic, payload, qos)` | Send an `mqtt.msg` event to the consumer |\n| `await comm.receive_from(timeout=1)` | Receive the next message the consumer sent (e.g. `mqtt.pub`, `mqtt.sub`) |\n| `await comm.disconnect(code=1000, timeout=1)` | Send `mqtt.disconnect` and wait for the consumer to close |\n\n### Integration tests (optional, requires a broker)\n\nFor end-to-end tests against a real MQTT broker, start mosquitto and run:\n\n```bash\n# macOS\nbrew install mosquitto\n\n# Run only integration tests\npytest tests/test_integration.py -v\n```\n\nIntegration tests are automatically skipped when no broker is available, so they never break CI in environments without one.\n\n# What's new in 2.0.0\n\n- **paho-mqtt 2.x compatibility** — automatically detects the installed paho-mqtt version and uses the correct `CallbackAPIVersion` (2.x) or legacy API (1.x). Both versions are supported with no code changes required.\n- **Python 3.10 – 3.13 compatibility** — removed deprecated `asyncio.ensure_future(loop=...)` calls, replaced with `loop.create_task()`. Removed Python \u003c 3.9 compatibility shims.\n- **Bug fix: integer `client_id`** — the default `client_id` was stored as an integer, causing paho-mqtt to raise `TypeError` at connection time. It is now always coerced to a string.\n- **Better error logging** — connection failures now surface the actual exception at `ERROR` level instead of being silently swallowed.\n- **Test suite** — a full pytest-based test suite is included covering server internals, consumer lifecycle, and optional broker integration tests (auto-skipped when no broker is available).\n\n# AI Assistant Skill\n\nmqttasgi is available as an [OpenClaw](https://clawhub.ai/) skill. AI assistants that support OpenClaw skills can install it to get full knowledge of the API, consumer patterns, testing utilities, and home automation use cases.\n\n- **Slug:** `mqttasgi`\n- **Display name:** `mqttasgi - MQTT ASGI for Django`\n- **Skill file:** [`claude_skill/SKILL.md`](claude_skill/SKILL.md)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsivulich%2Fmqttasgi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsivulich%2Fmqttasgi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsivulich%2Fmqttasgi/lists"}