dbus2mqtt 0.1.2__tar.gz → 0.2.0__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.

Potentially problematic release.


This version of dbus2mqtt might be problematic. Click here for more details.

Files changed (61) hide show
  1. dbus2mqtt-0.2.0/.github/release-drafter.yml +51 -0
  2. dbus2mqtt-0.2.0/.github/scripts/release-versions.py +66 -0
  3. dbus2mqtt-0.1.2/.github/workflows/docker-latest.yml → dbus2mqtt-0.2.0/.github/workflows/docker-dev.yml +2 -2
  4. dbus2mqtt-0.1.2/.github/workflows/docker-publish-pypi-release.yml → dbus2mqtt-0.2.0/.github/workflows/docker-stable.yml +35 -7
  5. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.pre-commit-config.yaml +1 -1
  6. dbus2mqtt-0.2.0/.python-version +1 -0
  7. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/PKG-INFO +4 -9
  8. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/README.md +3 -8
  9. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docker/Dockerfile.latest +1 -1
  10. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docker/Dockerfile.pypi +2 -0
  11. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples/home_assistant_media_player.yaml +16 -10
  12. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples/linux_desktop.yaml +5 -4
  13. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/renovate.json +5 -0
  14. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/config.py +8 -7
  15. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/dbus/dbus_client.py +1 -1
  16. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/flow/__init__.py +22 -3
  17. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/flow/actions/context_set.py +2 -0
  18. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/flow/actions/mqtt_publish.py +12 -6
  19. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/main.py +0 -9
  20. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/mqtt/mqtt_client.py +9 -5
  21. dbus2mqtt-0.2.0/src/dbus2mqtt/template/templating.py +103 -0
  22. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/flow/actions/test_context_set.py +1 -0
  23. dbus2mqtt-0.2.0/tests/template/test_templating.py +141 -0
  24. dbus2mqtt-0.2.0/tests/template/test_templating_config.py +87 -0
  25. dbus2mqtt-0.1.2/.github/release-drafter.yml +0 -34
  26. dbus2mqtt-0.1.2/.python-version +0 -1
  27. dbus2mqtt-0.1.2/config-test.yaml +0 -141
  28. dbus2mqtt-0.1.2/src/dbus2mqtt/template/templating.py +0 -129
  29. dbus2mqtt-0.1.2/tests/template/test_templating.py +0 -55
  30. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.dockerignore +0 -0
  31. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.env.example +0 -0
  32. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  33. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  34. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/workflows/ci.yml +0 -0
  35. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/workflows/pre-commit.yml +0 -0
  36. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/workflows/publish.yml +0 -0
  37. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/workflows/release-drafter.yml +0 -0
  38. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.gitignore +0 -0
  39. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.vscode/launch.json +0 -0
  40. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.vscode/settings.json +0 -0
  41. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.yamllint.yml +0 -0
  42. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/LICENSE +0 -0
  43. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/debugging.md +0 -0
  44. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples/home_assistant_media_player.md +0 -0
  45. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples/linux_desktop.md +0 -0
  46. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples.md +0 -0
  47. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/pyproject.toml +0 -0
  48. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/__init__.py +0 -0
  49. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/__main__.py +0 -0
  50. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/dbus/dbus_types.py +0 -0
  51. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/dbus/dbus_util.py +0 -0
  52. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/event_broker.py +0 -0
  53. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/flow/flow_processor.py +0 -0
  54. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/template/dbus_template_functions.py +0 -0
  55. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/__init__.py +0 -0
  56. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/config/test_examples.py +0 -0
  57. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/dbus/test_dbus_client.py +0 -0
  58. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/flow/actions/test_mqtt_publish.py +0 -0
  59. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/flow/test_flow_processor.py +0 -0
  60. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/flow/triggers/test_dbus_client_triggers.py +0 -0
  61. {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/uv.lock +0 -0
@@ -0,0 +1,51 @@
1
+ name-template: 'v$RESOLVED_VERSION 🌈'
2
+ tag-template: 'v$RESOLVED_VERSION'
3
+ exclude-labels:
4
+ - dependencies
5
+ categories:
6
+ - title: 💥 Breaking changes
7
+ labels:
8
+ - breaking-change
9
+ - title: 🚀 Features
10
+ labels:
11
+ - feature
12
+ - enhancement
13
+ - title: 🐛 Bug Fixes
14
+ labels:
15
+ - fix
16
+ - bugfix
17
+ - bug
18
+ - title: 🧰 Maintenance
19
+ labels:
20
+ - chore
21
+ - documentation
22
+ exclude-contributors:
23
+ - jwnmulder
24
+ autolabeler:
25
+ - label: enhancement
26
+ branch:
27
+ - '/feature\/.+/'
28
+ - label: fix
29
+ branch:
30
+ - '/fix\/.+/'
31
+ title:
32
+ - '/fix/i'
33
+ version-resolver:
34
+ major:
35
+ labels:
36
+ - major
37
+ minor:
38
+ labels:
39
+ - minor
40
+ - feature
41
+ - enhancement
42
+ - breaking-change # minor until we are on 1.x
43
+ patch:
44
+ labels:
45
+ - patch
46
+ default: patch
47
+ change-template: '* $TITLE (#$NUMBER)'
48
+ template: |
49
+ ## Changes
50
+
51
+ $CHANGES
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import subprocess
4
+ import sys
5
+
6
+ from packaging import version
7
+
8
+
9
+ def get_versions_from_git_tags() -> list[version.Version]:
10
+ result = subprocess.run(
11
+ ["git", "tag"],
12
+ capture_output=True, text=True, check=True
13
+ )
14
+ tags = result.stdout.strip().splitlines()
15
+ versions = [t.lstrip("v") for t in tags if t.startswith("v")]
16
+
17
+ parsed_versions = []
18
+ for v in versions:
19
+ try:
20
+ parsed = version.parse(v)
21
+ parsed_versions.append(parsed)
22
+ except Exception as e:
23
+ print(f"Error parsing version {v}: {e}")
24
+
25
+ return parsed_versions
26
+
27
+ def latest_version_by_cycle(versions: list[version.Version], cycles: list[str]) -> dict[str, version.Version]:
28
+ res = {}
29
+ for cycle in cycles:
30
+ parsed_cycle = version.parse(cycle)
31
+
32
+ for v in versions:
33
+ # check if version is in cycle range
34
+ # cycle can be major or major.minor or major.minor.patch
35
+ if len(v.release) > len(parsed_cycle.release):
36
+ # update if version is later within cycle
37
+ if cycle not in cycles or v > parsed_cycle:
38
+ res[cycle] = v
39
+ return res
40
+
41
+ def main():
42
+ args = sys.argv[1:]
43
+ if not args:
44
+ print("Usage: release-versions.py 0.1 0.2 1.0 ...")
45
+ sys.exit(1)
46
+
47
+ versions = get_versions_from_git_tags()
48
+ cycles = latest_version_by_cycle(versions, args)
49
+
50
+ # Detect the overall latest version
51
+ overall_latest = max(versions) if versions else None
52
+
53
+ cycle_details = []
54
+ for cycle in args:
55
+ latest = cycles.get(cycle)
56
+ if latest:
57
+ cycle_details.append({
58
+ "cycle": cycle,
59
+ "latestVersion": latest.base_version,
60
+ "isLatestStable": latest == overall_latest
61
+ })
62
+
63
+ print(json.dumps(cycle_details))
64
+
65
+ if __name__ == "__main__":
66
+ main()
@@ -1,4 +1,4 @@
1
- name: Build and Push Docker latest
1
+ name: docker-dev
2
2
 
3
3
  "on":
4
4
  push:
@@ -37,7 +37,7 @@ jobs:
37
37
  jwnmulder/dbus2mqtt
38
38
  ghcr.io/jwnmulder/dbus2mqtt
39
39
  tags: |
40
- type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
40
+ type=raw,value=dev,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
41
41
  labels: |
42
42
  org.opencontainers.image.source=https://github.com/jwnmulder/dbus2mqtt
43
43
 
@@ -1,20 +1,46 @@
1
- name: Build and Push Docker Images for Released Versions
1
+ name: docker-stable releases
2
2
 
3
3
  "on":
4
- # push:
5
- # branches:
6
- # - main
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - "docker/**"
9
+ release:
10
+ types: [published]
7
11
  workflow_dispatch:
8
12
 
9
13
  jobs:
14
+ setup:
15
+ runs-on: ubuntu-latest
16
+ outputs:
17
+ versions_matrix: ${{ steps.supported_release_versions.outputs.versions_matrix }}
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0
23
+
24
+ - name: Determine supported release versions
25
+ id: supported_release_versions
26
+ run: |
27
+ SUPPORTED_CYCLES=("0.1" "0.2" "0.3" "0.4" "0.5" "0.6" "0.7" "0.8" "0.9" "1.0" "2.0")
28
+
29
+ VERSIONS_JSON=$(python3 .github/scripts/release-versions.py "${SUPPORTED_CYCLES[@]}")
30
+
31
+ # Wrap the output into matrix include
32
+ VERSIONS_MATRIX=$(echo "$VERSIONS_JSON" | jq -c '{include: [.[] | {cycle: .cycle, version: .latestVersion, isLatestStable: .isLatestStable}]}')
33
+
34
+ echo "VERSIONS_MATRIX=$VERSIONS_MATRIX"
35
+ echo "versions_matrix=$VERSIONS_MATRIX" >> $GITHUB_OUTPUT
10
36
  docker:
11
37
  runs-on: ubuntu-latest
38
+ needs: setup
12
39
  permissions:
13
40
  packages: write
14
41
  strategy:
15
- matrix:
16
- version:
17
- - 0.1
42
+ fail-fast: false
43
+ matrix: ${{ fromJson(needs.setup.outputs.versions_matrix) }}
18
44
 
19
45
  steps:
20
46
  - name: Checkout
@@ -28,8 +54,10 @@ jobs:
28
54
  jwnmulder/dbus2mqtt
29
55
  ghcr.io/jwnmulder/dbus2mqtt
30
56
  tags: |
57
+ type=raw,value=latest,enable=${{ matrix.isLatestStable }}
31
58
  type=pep440,pattern={{major}},value=${{ matrix.version }},enable=${{ !startsWith(matrix.version, '0.') }}
32
59
  type=pep440,pattern={{major}}.{{minor}},value=${{ matrix.version }}
60
+ type=pep440,pattern={{major}}.{{minor}}.{{patch}},value=${{ matrix.version }}
33
61
  # type=ref,event=branch
34
62
  # type=ref,event=pr
35
63
  # type=semver,pattern={{version}},value=${{ matrix.version }}
@@ -42,7 +42,7 @@ repos:
42
42
  - I
43
43
 
44
44
  - repo: https://github.com/astral-sh/uv-pre-commit
45
- rev: 0.6.14
45
+ rev: 0.6.16
46
46
  hooks:
47
47
  - id: uv-lock
48
48
 
@@ -0,0 +1 @@
1
+ 3.10
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbus2mqtt
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: A Python tool to expose Linux D-Bus signals, methods and properties over MQTT - featuring templating, payload enrichment and Home Assistant-ready examples
5
5
  Project-URL: Repository, https://github.com/jwnmulder/dbus2mqtt.git
6
6
  Project-URL: Issues, https://github.com/jwnmulder/dbus2mqtt/issues
@@ -51,12 +51,6 @@ This makes it easy to integrate Linux desktop services or system signals into MQ
51
51
 
52
52
  Initial testing has focused on MPRIS integration. A table of tested MPRIS players and their supported methods can be found here: [home_assistant_media_player.md](https://github.com/jwnmulder/dbus2mqtt/blob/main/docs/examples/home_assistant_media_player.md)
53
53
 
54
-
55
- TODO list
56
-
57
- * Improve error handling when deleting message with 'retain' set. WARNING:dbus2mqtt.mqtt_client:on_message: Unexpected payload, expecting json, topic=dbus2mqtt/org.mpris.MediaPlayer2/command, payload=, error=Expecting value: line 1 column 1 (char 0)
58
- * Property set only works the first time, need to restart after which the first set will work again
59
-
60
54
  ## Getting started with dbus2mqtt
61
55
 
62
56
  Create a `config.yaml` file with the contents shown below. This configuration will expose all bus properties from the `org.mpris.MediaPlayer2.Player` interface to MQTT on the `dbus2mqtt/org.mpris.MediaPlayer2/state` topic. Have a look at [docs/examples](docs/examples.md) for more examples
@@ -86,7 +80,7 @@ dbus:
86
80
  topic: dbus2mqtt/org.mpris.MediaPlayer2/state
87
81
  payload_type: json
88
82
  payload_template: |
89
- {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) | to_yaml }}
83
+ {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) }}
90
84
  ```
91
85
 
92
86
  MQTT connection details can be configured in that same `config.yaml` file or via environment variables. For now create a `.env` file with the following contents.
@@ -117,6 +111,7 @@ cp docs/examples/home_assistant_media_player.yaml $HOME/.config/dbus2mqtt/config
117
111
  cp .env.example $HOME/.config/dbus2mqtt/.env
118
112
 
119
113
  # run image and automatically start on reboot
114
+ sudo docker pull jwnmulder/dbus2mqtt
120
115
  docker run --detach --name dbus2mqtt \
121
116
  --volume "$HOME"/.config/dbus2mqtt:"$HOME"/.config/dbus2mqtt \
122
117
  --volume /run/user:/run/user \
@@ -170,7 +165,7 @@ dbus:
170
165
  path: /org/mpris/MediaPlayer2
171
166
  interfaces:
172
167
  - interface: org.mpris.MediaPlayer2.Player
173
- mqtt_call_method_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
168
+ mqtt_command_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
174
169
  methods:
175
170
  - method: Pause
176
171
  - method: Play
@@ -19,12 +19,6 @@ This makes it easy to integrate Linux desktop services or system signals into MQ
19
19
 
20
20
  Initial testing has focused on MPRIS integration. A table of tested MPRIS players and their supported methods can be found here: [home_assistant_media_player.md](https://github.com/jwnmulder/dbus2mqtt/blob/main/docs/examples/home_assistant_media_player.md)
21
21
 
22
-
23
- TODO list
24
-
25
- * Improve error handling when deleting message with 'retain' set. WARNING:dbus2mqtt.mqtt_client:on_message: Unexpected payload, expecting json, topic=dbus2mqtt/org.mpris.MediaPlayer2/command, payload=, error=Expecting value: line 1 column 1 (char 0)
26
- * Property set only works the first time, need to restart after which the first set will work again
27
-
28
22
  ## Getting started with dbus2mqtt
29
23
 
30
24
  Create a `config.yaml` file with the contents shown below. This configuration will expose all bus properties from the `org.mpris.MediaPlayer2.Player` interface to MQTT on the `dbus2mqtt/org.mpris.MediaPlayer2/state` topic. Have a look at [docs/examples](docs/examples.md) for more examples
@@ -54,7 +48,7 @@ dbus:
54
48
  topic: dbus2mqtt/org.mpris.MediaPlayer2/state
55
49
  payload_type: json
56
50
  payload_template: |
57
- {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) | to_yaml }}
51
+ {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) }}
58
52
  ```
59
53
 
60
54
  MQTT connection details can be configured in that same `config.yaml` file or via environment variables. For now create a `.env` file with the following contents.
@@ -85,6 +79,7 @@ cp docs/examples/home_assistant_media_player.yaml $HOME/.config/dbus2mqtt/config
85
79
  cp .env.example $HOME/.config/dbus2mqtt/.env
86
80
 
87
81
  # run image and automatically start on reboot
82
+ sudo docker pull jwnmulder/dbus2mqtt
88
83
  docker run --detach --name dbus2mqtt \
89
84
  --volume "$HOME"/.config/dbus2mqtt:"$HOME"/.config/dbus2mqtt \
90
85
  --volume /run/user:/run/user \
@@ -138,7 +133,7 @@ dbus:
138
133
  path: /org/mpris/MediaPlayer2
139
134
  interfaces:
140
135
  - interface: org.mpris.MediaPlayer2.Player
141
- mqtt_call_method_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
136
+ mqtt_command_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
142
137
  methods:
143
138
  - method: Pause
144
139
  - method: Play
@@ -20,7 +20,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
20
20
  --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
21
21
  uv sync --frozen --no-install-project --no-dev
22
22
 
23
- ADD src/ pyproject.toml uv.lock .python-version README.md /app
23
+ ADD src/ pyproject.toml uv.lock README.md /app
24
24
  RUN --mount=type=cache,target=/root/.cache/uv \
25
25
  uv sync --frozen --no-dev
26
26
 
@@ -7,4 +7,6 @@ ARG DBUS2MQTT_VERSION=latest
7
7
  RUN pip install --no-cache-dir dbus2mqtt=="$DBUS2MQTT_VERSION"
8
8
 
9
9
  ENTRYPOINT ["python", "-m", "dbus2mqtt"]
10
+
11
+ # show help message by default
10
12
  CMD ["--help"]
@@ -13,18 +13,17 @@ dbus:
13
13
  - interface: org.freedesktop.DBus.Properties
14
14
  signals:
15
15
  - signal: PropertiesChanged
16
- # TODO: Determine if we should filter here or in flows? The only consumers right now are the flows
17
16
  filter: "{{ args[0] == 'org.mpris.MediaPlayer2.Player' }}"
18
17
  methods:
19
18
  - method: GetAll
20
19
 
21
20
  - interface: org.mpris.MediaPlayer2
22
- mqtt_call_method_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
21
+ mqtt_command_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
23
22
  methods:
24
23
  - method: Quit
25
24
 
26
25
  - interface: org.mpris.MediaPlayer2.Player
27
- mqtt_call_method_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
26
+ mqtt_command_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
28
27
  methods:
29
28
  - method: Pause
30
29
  - method: Play
@@ -35,6 +34,8 @@ dbus:
35
34
  - method: SetPosition
36
35
  - method: PlayPause
37
36
  - method: OpenUri
37
+ properties:
38
+ - property: Volume
38
39
 
39
40
  flows:
40
41
  - name: "MPRIS publish player state"
@@ -45,22 +46,27 @@ dbus:
45
46
  - type: dbus_signal
46
47
  interface: org.freedesktop.DBus.Properties
47
48
  signal: PropertiesChanged
48
- # filter: "{{ args[0] == 'org.mpris.MediaPlayer2.Player' }}"
49
+ # filter: "{{ args[0] == 'org.mpris.MediaPlayer2.Player' }}"
49
50
  actions:
50
51
  - type: context_set
51
52
  context:
52
53
  mpris_bus_name: '{{ dbus_list("org.mpris.MediaPlayer2.*") | first }}'
53
- # TODO: This would be a nice addition to avoid repetition
54
- # player_properties: |
55
- # {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) | to_yaml }}
56
54
  path: /org/mpris/MediaPlayer2
55
+ - type: context_set
56
+ context:
57
+ player_properties: |
58
+ {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) }}
59
+ volume: |
60
+ {{ dbus_property_get(mpris_bus_name, path, 'org.mpris.MediaPlayer2.Player', 'Volume', 0) }}
57
61
  - type: mqtt_publish
58
62
  topic: dbus2mqtt/org.mpris.MediaPlayer2/state
59
63
  payload_type: json
60
64
  payload_template: |
61
- bus_name: {{ mpris_bus_name }}
62
- {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) | to_yaml }}
63
- Volume: {{ dbus_property_get(mpris_bus_name, path, 'org.mpris.MediaPlayer2.Player', 'Volume', 0) }}
65
+ {{
66
+ { 'bus_name': mpris_bus_name }
67
+ | combine(player_properties)
68
+ | combine({ 'Volume': volume })
69
+ }}
64
70
  - name: "MPRIS player removed"
65
71
  triggers:
66
72
  - type: bus_name_removed
@@ -17,19 +17,20 @@ dbus:
17
17
  - method: GetAll
18
18
 
19
19
  - interface: org.gnome.SessionManager
20
- mqtt_call_method_topic: dbus2mqtt/SessionManager/command
20
+ mqtt_command_topic: dbus2mqtt/SessionManager/command
21
21
  methods:
22
22
  - method: Logout
23
23
  - method: Reboot # reboot dialog
24
24
 
25
25
  flows:
26
- - name: "publish state on schedule"
26
+ - name: "publish state at startup and schedule"
27
27
  triggers:
28
+ - type: bus_name_added
28
29
  - type: schedule
29
30
  interval: {seconds: 5}
30
31
  actions:
31
32
  - type: mqtt_publish
32
33
  topic: dbus2mqtt/SessionManager
33
34
  payload_type: json
34
- payload_template: |
35
- clients: {{ dbus_call('org.gnome.SessionManager', '/org/gnome/SessionManager', 'org.gnome.SessionManager', 'GetClients') }}
35
+ payload_template:
36
+ clients: "{{ dbus_call('org.gnome.SessionManager', '/org/gnome/SessionManager', 'org.gnome.SessionManager', 'GetClients') }}"
@@ -12,6 +12,11 @@
12
12
  "matchUpdateTypes": ["minor", "patch"],
13
13
  "automerge": true,
14
14
  "addLabels": ["automerge"]
15
+ },
16
+ {
17
+ "description": "Disable Renovate for .python-version",
18
+ "matchFileNames": [".python-version"],
19
+ "enabled": false
15
20
  }
16
21
  ]
17
22
  }
@@ -15,8 +15,9 @@ class SignalConfig:
15
15
  filter: str | None = None
16
16
 
17
17
  def matches_filter(self, template_engine: TemplateEngine, *args) -> bool:
18
- res = template_engine.render_template(self.filter, str, { "args": args })
19
- return res == "True"
18
+ if self.filter:
19
+ return template_engine.render_template(self.filter, bool, { "args": args })
20
+ return True
20
21
 
21
22
  @dataclass
22
23
  class MethodConfig:
@@ -29,13 +30,15 @@ class PropertyConfig:
29
30
  @dataclass
30
31
  class InterfaceConfig:
31
32
  interface: str
32
- mqtt_call_method_topic: str | None = None
33
+ mqtt_command_topic: str | None = None
33
34
  signals: list[SignalConfig] = field(default_factory=list)
34
35
  methods: list[MethodConfig] = field(default_factory=list)
35
36
  properties: list[PropertyConfig] = field(default_factory=list)
36
37
 
37
- def render_mqtt_call_method_topic(self, template_engine: TemplateEngine, context: dict[str, Any]) -> Any:
38
- return template_engine.render_template(self.mqtt_call_method_topic, str, context)
38
+ def render_mqtt_command_topic(self, template_engine: TemplateEngine, context: dict[str, Any]) -> Any:
39
+ if self.mqtt_command_topic:
40
+ return template_engine.render_template(self.mqtt_command_topic, str, context)
41
+ return None
39
42
 
40
43
  @dataclass
41
44
  class FlowTriggerMqttConfig:
@@ -84,8 +87,6 @@ class FlowActionContextSetConfig:
84
87
  class FlowActionMqttPublishConfig:
85
88
  topic: str
86
89
  payload_template: str | dict[str, Any]
87
- """should be a dict if payload_type is json/yaml
88
- or a string if payload_type is text"""
89
90
  type: Literal["mqtt_publish"] = "mqtt_publish"
90
91
  payload_type: Literal["json", "yaml", "text"] = "json"
91
92
 
@@ -389,7 +389,7 @@ class DbusClient:
389
389
  for subscription_configs in self.config.subscriptions:
390
390
  for interface_config in subscription_configs.interfaces:
391
391
  # TODO, performance improvement
392
- mqtt_topic = interface_config.render_mqtt_call_method_topic(self.templating, {})
392
+ mqtt_topic = interface_config.render_mqtt_command_topic(self.templating, {})
393
393
  found_matching_topic |= mqtt_topic == msg.topic
394
394
 
395
395
  if not found_matching_topic:
@@ -6,15 +6,34 @@ class FlowExecutionContext:
6
6
 
7
7
  def __init__(self, name: str | None, global_flows_context: dict[str, Any], flow_context: dict[str, Any]):
8
8
  self.name = name
9
+
9
10
  self.global_flows_context = global_flows_context
11
+ """
12
+ Global flows context which is shared across all flows.
13
+ Modifiable by user.
14
+ **Not** cleaned up after flow execution.
15
+ """
16
+
10
17
  self.flow_context = flow_context
18
+ """
19
+ Flow context which contains flow specific context like 'subscription_bus_name'.
20
+ **Not** modifiable by user.
21
+ **Not** cleaned up after flow execution.
22
+ """
11
23
 
12
- # per flow execution context
13
24
  self.context: dict[str, Any] = {}
25
+ """
26
+ Per flow execution context.
27
+ Modifiable by user.
28
+ Cleaned up after each flow execution
29
+ """
14
30
 
15
31
  def get_aggregated_context(self) -> dict[str, Any]:
16
- """Get the aggregated context for the flow execution."""
17
- # Merge global flows context, flow context, and local context
32
+ """
33
+ Get the aggregated context for the flow execution.
34
+ Merges global flows context, flow context, and local context
35
+ """
36
+
18
37
  context = {}
19
38
  if self.global_flows_context:
20
39
  context.update(self.global_flows_context)
@@ -15,12 +15,14 @@ class ContextSetAction(FlowAction):
15
15
  async def execute(self, context: FlowExecutionContext):
16
16
 
17
17
  aggregated_context = context.get_aggregated_context()
18
+
18
19
  if self.config.global_context:
19
20
  context_new = await self.templating.async_render_template(self.config.global_context, dict, aggregated_context)
20
21
  logger.debug(f"Update global_context with: {context_new}")
21
22
  context.global_flows_context.update(context_new)
22
23
 
23
24
  if self.config.context:
25
+
24
26
  context_new = await self.templating.async_render_template(self.config.context, dict, aggregated_context)
25
27
  logger.debug(f"Update context with: {context_new}")
26
28
  context.context.update(context_new)
@@ -1,7 +1,7 @@
1
1
 
2
2
  import logging
3
3
 
4
- from jinja2.exceptions import TemplateRuntimeError
4
+ from jinja2.exceptions import TemplateError
5
5
 
6
6
  from dbus2mqtt import AppContext
7
7
  from dbus2mqtt.config import FlowActionMqttPublishConfig
@@ -24,14 +24,20 @@ class MqttPublishAction(FlowAction):
24
24
  try:
25
25
  mqtt_topic = await self.templating.async_render_template(self.config.topic, str, render_context)
26
26
 
27
- payload_res_type = str if self.config.payload_type == "text" else dict
28
- payload = await self.templating.async_render_template(self.config.payload_template, payload_res_type, render_context)
27
+ if self.config.payload_type == "text":
28
+ res_type = str
29
+ else:
30
+ res_type = dict
29
31
 
30
- except TemplateRuntimeError as e:
31
- logger.warning(f"Error rendering jinja template, flow: '{context.name}', error: {str(e)}. render_context={render_context}", exc_info=True)
32
+ payload = await self.templating.async_render_template(self.config.payload_template, res_type, render_context)
33
+
34
+ except TemplateError as e:
35
+ logger.warning(f"Error rendering jinja template, flow: '{context.name or ''}', msg={e}, payload_template={self.config.payload_template}, render_context={render_context}", exc_info=True)
32
36
  return
33
37
  except Exception as e:
34
- logger.warning(f"Error rendering jinja template, flow: '{context.name}', error: {str(e)}. render_context={render_context}", exc_info=False)
38
+ # Dont log full exception info to avoid log spamming on dbus errors
39
+ # due to clients disconnecting
40
+ logger.warning(f"Error rendering jinja template, flow: '{context.name or ''}', msg={e} payload_template={self.config.payload_template}, render_context={render_context}")
35
41
  return
36
42
 
37
43
  logger.debug(f"public_mqtt: flow={context.name}, payload={payload}")
@@ -121,15 +121,6 @@ def main():
121
121
  apscheduler_logger = logging.getLogger("apscheduler")
122
122
  apscheduler_logger.setLevel(logging.WARNING)
123
123
 
124
-
125
- # handler.setFormatter(colorlog.ColoredFormatter('%(log_color)s%(levelname)s:%(name)s:%(message)s'))
126
-
127
- # logger = colorlog.getLogger('')
128
- # for handler in logger.handlers:
129
- # print(handler.st)
130
- # if isinstance(handler, colorlog.StreamHandler):
131
- # handler.setFormatter(colorlog.ColoredFormatter('%(log_color)s%(levelname)s:%(name)s:%(message)s'))
132
-
133
124
  logger.debug(f"config: {config}")
134
125
 
135
126
  asyncio.run(run(config))
@@ -43,15 +43,14 @@ class MqttClient:
43
43
  port=self.config.port
44
44
  )
45
45
 
46
- # def on_dbus_signal(self, bus_name: str, path: str, interface: str, signal: str, topic, msg: dict[str, Any]):
47
- # payload = json.dumps(msg)
48
- # logger.debug(f"on_dbus_signal: payload={payload}")
49
- # self.client.publish(topic=topic, payload=payload)
50
-
51
46
  async def mqtt_publish_queue_processor_task(self):
47
+
48
+ first_message = True
49
+
52
50
  """Continuously processes messages from the async queue."""
53
51
  while True:
54
52
  msg = await self.event_broker.mqtt_publish_queue.async_q.get() # Wait for a message
53
+
55
54
  try:
56
55
  payload = msg.payload
57
56
  type = msg.payload_serialization_type
@@ -65,6 +64,11 @@ class MqttClient:
65
64
 
66
65
  logger.debug(f"mqtt_publish_queue_processor_task: payload={payload}")
67
66
  self.client.publish(topic=msg.topic, payload=payload)
67
+
68
+ if first_message:
69
+ logger.info(f"First message published: topic={msg.topic}, payload={payload}")
70
+ first_message = False
71
+
68
72
  except Exception as e:
69
73
  logger.warning(f"mqtt_publish_queue_processor_task: Exception {e}", exc_info=True)
70
74
  finally: