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.
- dbus2mqtt-0.2.0/.github/release-drafter.yml +51 -0
- dbus2mqtt-0.2.0/.github/scripts/release-versions.py +66 -0
- dbus2mqtt-0.1.2/.github/workflows/docker-latest.yml → dbus2mqtt-0.2.0/.github/workflows/docker-dev.yml +2 -2
- dbus2mqtt-0.1.2/.github/workflows/docker-publish-pypi-release.yml → dbus2mqtt-0.2.0/.github/workflows/docker-stable.yml +35 -7
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.pre-commit-config.yaml +1 -1
- dbus2mqtt-0.2.0/.python-version +1 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/PKG-INFO +4 -9
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/README.md +3 -8
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docker/Dockerfile.latest +1 -1
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docker/Dockerfile.pypi +2 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples/home_assistant_media_player.yaml +16 -10
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples/linux_desktop.yaml +5 -4
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/renovate.json +5 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/config.py +8 -7
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/dbus/dbus_client.py +1 -1
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/flow/__init__.py +22 -3
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/flow/actions/context_set.py +2 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/flow/actions/mqtt_publish.py +12 -6
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/main.py +0 -9
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/mqtt/mqtt_client.py +9 -5
- dbus2mqtt-0.2.0/src/dbus2mqtt/template/templating.py +103 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/flow/actions/test_context_set.py +1 -0
- dbus2mqtt-0.2.0/tests/template/test_templating.py +141 -0
- dbus2mqtt-0.2.0/tests/template/test_templating_config.py +87 -0
- dbus2mqtt-0.1.2/.github/release-drafter.yml +0 -34
- dbus2mqtt-0.1.2/.python-version +0 -1
- dbus2mqtt-0.1.2/config-test.yaml +0 -141
- dbus2mqtt-0.1.2/src/dbus2mqtt/template/templating.py +0 -129
- dbus2mqtt-0.1.2/tests/template/test_templating.py +0 -55
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.dockerignore +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.env.example +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/workflows/ci.yml +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/workflows/pre-commit.yml +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/workflows/publish.yml +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.github/workflows/release-drafter.yml +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.gitignore +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.vscode/launch.json +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.vscode/settings.json +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/.yamllint.yml +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/LICENSE +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/debugging.md +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples/home_assistant_media_player.md +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples/linux_desktop.md +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/docs/examples.md +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/pyproject.toml +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/__init__.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/__main__.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/dbus/dbus_types.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/dbus/dbus_util.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/event_broker.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/flow/flow_processor.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/src/dbus2mqtt/template/dbus_template_functions.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/__init__.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/config/test_examples.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/dbus/test_dbus_client.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/flow/actions/test_mqtt_publish.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/flow/test_flow_processor.py +0 -0
- {dbus2mqtt-0.1.2 → dbus2mqtt-0.2.0}/tests/flow/triggers/test_dbus_client_triggers.py +0 -0
- {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:
|
|
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=
|
|
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:
|
|
1
|
+
name: docker-stable releases
|
|
2
2
|
|
|
3
3
|
"on":
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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 }}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbus2mqtt
|
|
3
|
-
Version: 0.
|
|
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'])
|
|
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
|
-
|
|
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'])
|
|
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
|
-
|
|
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
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
|
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') }}"
|
|
@@ -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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
38
|
-
|
|
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.
|
|
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
|
-
"""
|
|
17
|
-
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
27
|
+
if self.config.payload_type == "text":
|
|
28
|
+
res_type = str
|
|
29
|
+
else:
|
|
30
|
+
res_type = dict
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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:
|