qbusmqttapi 0.2.7__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.
- qbusmqttapi-0.2.7/LICENSE +21 -0
- qbusmqttapi-0.2.7/PKG-INFO +18 -0
- qbusmqttapi-0.2.7/README.md +2 -0
- qbusmqttapi-0.2.7/pyproject.toml +26 -0
- qbusmqttapi-0.2.7/setup.cfg +7 -0
- qbusmqttapi-0.2.7/setup.py +27 -0
- qbusmqttapi-0.2.7/src/qbusmqttapi/__init__.py +1 -0
- qbusmqttapi-0.2.7/src/qbusmqttapi/const.py +14 -0
- qbusmqttapi-0.2.7/src/qbusmqttapi/discovery.py +160 -0
- qbusmqttapi-0.2.7/src/qbusmqttapi/factory.py +172 -0
- qbusmqttapi-0.2.7/src/qbusmqttapi/state.py +140 -0
- qbusmqttapi-0.2.7/src/qbusmqttapi.egg-info/PKG-INFO +18 -0
- qbusmqttapi-0.2.7/src/qbusmqttapi.egg-info/SOURCES.txt +14 -0
- qbusmqttapi-0.2.7/src/qbusmqttapi.egg-info/dependency_links.txt +1 -0
- qbusmqttapi-0.2.7/src/qbusmqttapi.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Qbus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qbusmqttapi
|
|
3
|
+
Version: 0.2.7
|
|
4
|
+
Summary: MQTT API for Qbus Home Automation.
|
|
5
|
+
Home-page: https://github.com/Qbus-iot/qbusmqttapi
|
|
6
|
+
Author: Koen Schockaert
|
|
7
|
+
Author-email: ks@qbus.be
|
|
8
|
+
License: MIT License 2024
|
|
9
|
+
Project-URL: Source Code, https://github.com/Qbus-iot/qbusmqttapi
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
|
|
17
|
+
# qbusmqttapi
|
|
18
|
+
Python MQTT API for Qbus Home Automation
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[tool.ruff]
|
|
2
|
+
exclude = [".git", ".vscode", ".pytest_cache", ".mypy_cache", ".env"]
|
|
3
|
+
line-length = 125
|
|
4
|
+
|
|
5
|
+
[tool.ruff.lint]
|
|
6
|
+
ignore = ["B008", "ISC001", "E501", "W191"]
|
|
7
|
+
select = [
|
|
8
|
+
"B",
|
|
9
|
+
"E",
|
|
10
|
+
"F",
|
|
11
|
+
"W",
|
|
12
|
+
"I",
|
|
13
|
+
"N",
|
|
14
|
+
"C4",
|
|
15
|
+
"EXE",
|
|
16
|
+
"ISC",
|
|
17
|
+
"ICN",
|
|
18
|
+
"PIE",
|
|
19
|
+
"PT",
|
|
20
|
+
"RET",
|
|
21
|
+
"SIM",
|
|
22
|
+
"ERA",
|
|
23
|
+
"PLC",
|
|
24
|
+
"RUF",
|
|
25
|
+
"ARG",
|
|
26
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import setuptools
|
|
4
|
+
|
|
5
|
+
VERSION = "0.2.7" # PEP-440
|
|
6
|
+
|
|
7
|
+
setuptools.setup(
|
|
8
|
+
name="qbusmqttapi",
|
|
9
|
+
version=VERSION,
|
|
10
|
+
description="MQTT API for Qbus Home Automation.",
|
|
11
|
+
url="https://github.com/Qbus-iot/qbusmqttapi",
|
|
12
|
+
project_urls={
|
|
13
|
+
"Source Code": "https://github.com/Qbus-iot/qbusmqttapi",
|
|
14
|
+
},
|
|
15
|
+
author="Koen Schockaert",
|
|
16
|
+
author_email="ks@qbus.be",
|
|
17
|
+
license="MIT License 2024",
|
|
18
|
+
classifiers=[
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
],
|
|
23
|
+
python_requires=">=3.8",
|
|
24
|
+
# Requirements
|
|
25
|
+
long_description=Path("README.md").read_text(),
|
|
26
|
+
long_description_content_type="text/markdown",
|
|
27
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""QBUS MQTT API."""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Qbus MQTT API constants."""
|
|
2
|
+
|
|
3
|
+
TOPIC_PREFIX = "cloudapp/QBUSMQTTGW"
|
|
4
|
+
|
|
5
|
+
KEY_OUTPUT_ACTION = "action"
|
|
6
|
+
KEY_OUTPUT_ACTIONS = "actions"
|
|
7
|
+
KEY_OUTPUT_ID = "id"
|
|
8
|
+
KEY_OUTPUT_NAME = "name"
|
|
9
|
+
KEY_OUTPUT_PROPERTIES = "properties"
|
|
10
|
+
KEY_OUTPUT_REF_ID = "refId"
|
|
11
|
+
KEY_OUTPUT_TYPE = "type"
|
|
12
|
+
|
|
13
|
+
KEY_PROPERTIES_AUTHKEY = "authKey"
|
|
14
|
+
KEY_PROPERTIES_VALUE = "value"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Qbis discovery models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .const import (
|
|
6
|
+
KEY_OUTPUT_ACTIONS,
|
|
7
|
+
KEY_OUTPUT_ID,
|
|
8
|
+
KEY_OUTPUT_NAME,
|
|
9
|
+
KEY_OUTPUT_PROPERTIES,
|
|
10
|
+
KEY_OUTPUT_REF_ID,
|
|
11
|
+
KEY_OUTPUT_TYPE,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
KEY_DEVICES = "devices"
|
|
16
|
+
|
|
17
|
+
KEY_DEVICE_FUNCTIONBLOCKS = "functionBlocks"
|
|
18
|
+
KEY_DEVICE_ID = "id"
|
|
19
|
+
KEY_DEVICE_IP = "ip"
|
|
20
|
+
KEY_DEVICE_MAC = "mac"
|
|
21
|
+
KEY_DEVICE_NAME = "name"
|
|
22
|
+
KEY_DEVICE_SERIAL_NR = "serialNr"
|
|
23
|
+
KEY_DEVICE_TYPE = "type"
|
|
24
|
+
KEY_DEVICE_VERSION = "version"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class QbusMqttOutput:
|
|
28
|
+
"""MQTT representation of a Qbus output."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, data: dict, device: QbusMqttDevice) -> None:
|
|
31
|
+
"""Initialize based on a json loaded dictionary."""
|
|
32
|
+
self._data = data
|
|
33
|
+
self._device = device
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def id(self) -> str:
|
|
37
|
+
"""Return the id."""
|
|
38
|
+
return self._data.get(KEY_OUTPUT_ID) or ""
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def type(self) -> str:
|
|
42
|
+
"""Return the type."""
|
|
43
|
+
return self._data.get(KEY_OUTPUT_TYPE) or ""
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def name(self) -> str:
|
|
47
|
+
"""Return the name."""
|
|
48
|
+
return self._data.get(KEY_OUTPUT_NAME) or ""
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def ref_id(self) -> str:
|
|
52
|
+
"""Return the ref id."""
|
|
53
|
+
return self._data.get(KEY_OUTPUT_REF_ID) or ""
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def properties(self) -> dict:
|
|
57
|
+
"""Return the properties."""
|
|
58
|
+
return self._data.get(KEY_OUTPUT_PROPERTIES) or {}
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def actions(self) -> dict:
|
|
62
|
+
"""Return the actions."""
|
|
63
|
+
return self._data.get(KEY_OUTPUT_ACTIONS) or {}
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def device(self) -> QbusMqttDevice:
|
|
67
|
+
"""Return the device."""
|
|
68
|
+
return self._device
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class QbusMqttDevice:
|
|
72
|
+
"""MQTT representation of a Qbus device."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, data: dict) -> None:
|
|
75
|
+
"""Initialize based on a json loaded dictionary."""
|
|
76
|
+
self._data = data
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def id(self) -> str:
|
|
80
|
+
"""Return the id."""
|
|
81
|
+
return self._data.get(KEY_DEVICE_ID) or ""
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def ip(self) -> str:
|
|
85
|
+
"""Return the ip address."""
|
|
86
|
+
return self._data.get(KEY_DEVICE_IP) or ""
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def mac(self) -> str:
|
|
90
|
+
"""Return the ip address."""
|
|
91
|
+
return self._data.get(KEY_DEVICE_MAC) or ""
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def name(self) -> str:
|
|
95
|
+
"""Return the ip address."""
|
|
96
|
+
return self._data.get(KEY_DEVICE_NAME) or ""
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def serial_number(self) -> str:
|
|
100
|
+
"""Return the serial number."""
|
|
101
|
+
return self._data.get(KEY_DEVICE_SERIAL_NR) or ""
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def type(self) -> str:
|
|
105
|
+
"""Return the mac address."""
|
|
106
|
+
return self._data.get(KEY_DEVICE_TYPE) or ""
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def version(self) -> str:
|
|
110
|
+
"""Return the version."""
|
|
111
|
+
return self._data.get(KEY_DEVICE_VERSION) or ""
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def outputs(self) -> list[QbusMqttOutput]:
|
|
115
|
+
"""Return the outputs."""
|
|
116
|
+
|
|
117
|
+
outputs: list[QbusMqttOutput] = []
|
|
118
|
+
|
|
119
|
+
if self._data.get(KEY_DEVICE_FUNCTIONBLOCKS):
|
|
120
|
+
outputs = [
|
|
121
|
+
QbusMqttOutput(x, self) for x in self._data[KEY_DEVICE_FUNCTIONBLOCKS]
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
return outputs
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class QbusDiscovery:
|
|
128
|
+
"""MQTT representation of a Qbus config."""
|
|
129
|
+
|
|
130
|
+
def __init__(self, data: dict) -> None:
|
|
131
|
+
"""Initialize based on a json loaded dictionary."""
|
|
132
|
+
if KEY_DEVICES in data:
|
|
133
|
+
self._devices = [QbusMqttDevice(x) for x in data[KEY_DEVICES]]
|
|
134
|
+
|
|
135
|
+
self._name = data["app"]
|
|
136
|
+
|
|
137
|
+
def get_device_by_id(self, id: str) -> QbusMqttDevice | None:
|
|
138
|
+
"""Get the device by id."""
|
|
139
|
+
return next((x for x in self.devices if x.id.casefold() == id.casefold()), None)
|
|
140
|
+
|
|
141
|
+
def get_device_by_serial(self, serial: str) -> QbusMqttDevice | None:
|
|
142
|
+
"""Get the device by serial."""
|
|
143
|
+
return next(
|
|
144
|
+
(
|
|
145
|
+
x
|
|
146
|
+
for x in self.devices
|
|
147
|
+
if x.serial_number.casefold() == serial.casefold()
|
|
148
|
+
),
|
|
149
|
+
None,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def devices(self) -> list[QbusMqttDevice]:
|
|
154
|
+
"""Return the devices."""
|
|
155
|
+
return self._devices
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def name(self):
|
|
159
|
+
"""Return device name."""
|
|
160
|
+
return self._name
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Qbus MQTT factory."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, TypeVar
|
|
7
|
+
|
|
8
|
+
from .const import (
|
|
9
|
+
KEY_PROPERTIES_AUTHKEY,
|
|
10
|
+
TOPIC_PREFIX,
|
|
11
|
+
)
|
|
12
|
+
from .discovery import QbusDiscovery, QbusMqttDevice
|
|
13
|
+
from .state import (
|
|
14
|
+
QbusMqttControllerState,
|
|
15
|
+
QbusMqttState,
|
|
16
|
+
StateAction,
|
|
17
|
+
StateType,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
_LOGGER = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class QbusMqttRequestMessage:
|
|
25
|
+
"""Qbus MQTT request data class."""
|
|
26
|
+
|
|
27
|
+
topic: str
|
|
28
|
+
payload: str | bytes
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class QbusMqttMessageFactory:
|
|
32
|
+
"""Factory methods for Qbus MQTT messages."""
|
|
33
|
+
|
|
34
|
+
T = TypeVar("T", bound="QbusMqttState")
|
|
35
|
+
|
|
36
|
+
def __init__(self) -> None:
|
|
37
|
+
self._topic_factory = QbusMqttTopicFactory()
|
|
38
|
+
|
|
39
|
+
def parse_discovery(self, payload: str | bytes) -> QbusDiscovery | None:
|
|
40
|
+
"""Parse an MQTT message and return an instance
|
|
41
|
+
of QbusDiscovery if successful, otherwise None."""
|
|
42
|
+
|
|
43
|
+
discovery: QbusDiscovery | None = self.deserialize(QbusDiscovery, payload)
|
|
44
|
+
|
|
45
|
+
# Discovery data must include the Qbus device type and name.
|
|
46
|
+
if discovery is not None and len(discovery.devices) == 0:
|
|
47
|
+
_LOGGER.error("Incomplete discovery payload: %s", payload)
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
return discovery
|
|
51
|
+
|
|
52
|
+
def parse_controller_state(
|
|
53
|
+
self, payload: str | bytes
|
|
54
|
+
) -> QbusMqttControllerState | None:
|
|
55
|
+
"""Parse an MQTT message and return an instance
|
|
56
|
+
of QbusMqttControllerState if successful, otherwise None."""
|
|
57
|
+
|
|
58
|
+
return self.deserialize(QbusMqttControllerState, payload)
|
|
59
|
+
|
|
60
|
+
def parse_output_state(self, cls: type[T], payload: str | bytes) -> T | None:
|
|
61
|
+
"""Parse an MQTT message and return an instance
|
|
62
|
+
of T if successful, otherwise None."""
|
|
63
|
+
|
|
64
|
+
return self.deserialize(cls, payload)
|
|
65
|
+
|
|
66
|
+
def create_device_activate_request(
|
|
67
|
+
self, device: QbusMqttDevice, prefix: str = TOPIC_PREFIX
|
|
68
|
+
) -> QbusMqttRequestMessage:
|
|
69
|
+
"""Create a message to request device activation."""
|
|
70
|
+
state = QbusMqttState(
|
|
71
|
+
id=device.id, type=StateType.ACTION, action=StateAction.ACTIVATE
|
|
72
|
+
)
|
|
73
|
+
state.write_property(KEY_PROPERTIES_AUTHKEY, "ubielite")
|
|
74
|
+
|
|
75
|
+
return QbusMqttRequestMessage(
|
|
76
|
+
self._topic_factory.get_device_command_topic(device.id, prefix),
|
|
77
|
+
self.serialize(state),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def create_device_state_request(
|
|
81
|
+
self, device: QbusMqttDevice, prefix: str = TOPIC_PREFIX
|
|
82
|
+
) -> QbusMqttRequestMessage:
|
|
83
|
+
"""Create a message to request a device state."""
|
|
84
|
+
return QbusMqttRequestMessage(
|
|
85
|
+
self._topic_factory.get_get_state_topic(prefix), json.dumps([device.id])
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def create_state_request(
|
|
89
|
+
self, entity_ids: list[str], prefix: str = TOPIC_PREFIX
|
|
90
|
+
) -> QbusMqttRequestMessage:
|
|
91
|
+
"""Create a message to request entity states."""
|
|
92
|
+
return QbusMqttRequestMessage(
|
|
93
|
+
self._topic_factory.get_get_state_topic(prefix), json.dumps(entity_ids)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def create_set_output_state_request(
|
|
97
|
+
self, device: QbusMqttDevice, state: QbusMqttState, prefix: str = TOPIC_PREFIX
|
|
98
|
+
) -> QbusMqttRequestMessage:
|
|
99
|
+
"""Create a message to update the output state."""
|
|
100
|
+
return QbusMqttRequestMessage(
|
|
101
|
+
self._topic_factory.get_output_command_topic(device.id, state.id, prefix),
|
|
102
|
+
self.serialize(state),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def serialize(self, obj: Any) -> str:
|
|
106
|
+
"""Convert an object to json payload."""
|
|
107
|
+
return json.dumps(obj, cls=IgnoreNoneJsonEncoder)
|
|
108
|
+
|
|
109
|
+
def deserialize(self, state_cls: type[Any], payload: str | bytes) -> Any | None:
|
|
110
|
+
"""Parse an MQTT message and return the requested type if successful, otherwise None."""
|
|
111
|
+
|
|
112
|
+
if payload is None:
|
|
113
|
+
_LOGGER.warning("Empty state payload for %s", state_cls.__name__)
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
data = json.loads(payload)
|
|
118
|
+
except ValueError:
|
|
119
|
+
_LOGGER.error(
|
|
120
|
+
"Invalid state payload for %s: %s", state_cls.__name__, payload
|
|
121
|
+
)
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
return state_cls(data)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class QbusMqttTopicFactory:
|
|
128
|
+
"""Factory methods for topics of the Qbus MQTT API."""
|
|
129
|
+
|
|
130
|
+
def get_get_config_topic(self, prefix: str = TOPIC_PREFIX) -> str:
|
|
131
|
+
"""Return the getConfig topic."""
|
|
132
|
+
return f"{prefix}/getConfig"
|
|
133
|
+
|
|
134
|
+
def get_config_topic(self, prefix: str = TOPIC_PREFIX) -> str:
|
|
135
|
+
"""Return the config topic."""
|
|
136
|
+
return f"{prefix}/config"
|
|
137
|
+
|
|
138
|
+
def get_get_state_topic(self, prefix: str = TOPIC_PREFIX) -> str:
|
|
139
|
+
"""Return the getState topic."""
|
|
140
|
+
return f"{prefix}/getState"
|
|
141
|
+
|
|
142
|
+
def get_device_state_topic(self, device_id: str, prefix: str = TOPIC_PREFIX) -> str:
|
|
143
|
+
"""Return the state topic."""
|
|
144
|
+
return f"{prefix}/{device_id}/state"
|
|
145
|
+
|
|
146
|
+
def get_device_command_topic(
|
|
147
|
+
self, device_id: str, prefix: str = TOPIC_PREFIX
|
|
148
|
+
) -> str:
|
|
149
|
+
"""Return the 'set state' topic."""
|
|
150
|
+
return f"{prefix}/{device_id}/setState"
|
|
151
|
+
|
|
152
|
+
def get_output_command_topic(
|
|
153
|
+
self, device_id: str, entity_id: str, prefix: str = TOPIC_PREFIX
|
|
154
|
+
) -> str:
|
|
155
|
+
"""Return the 'set state' topic of an output."""
|
|
156
|
+
return f"{prefix}/{device_id}/{entity_id}/setState"
|
|
157
|
+
|
|
158
|
+
def get_output_state_topic(
|
|
159
|
+
self, device_id: str, entity_id: str, prefix: str = TOPIC_PREFIX
|
|
160
|
+
) -> str:
|
|
161
|
+
"""Return the state topic of an output."""
|
|
162
|
+
return f"{prefix}/{device_id}/{entity_id}/state"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class IgnoreNoneJsonEncoder(json.JSONEncoder):
|
|
166
|
+
"""A json encoder to ignore None values when serializing."""
|
|
167
|
+
|
|
168
|
+
def default(self, o):
|
|
169
|
+
if hasattr(o, "__dict__"):
|
|
170
|
+
# Filter out None values
|
|
171
|
+
return {k: v for k, v in o.__dict__.items() if v is not None}
|
|
172
|
+
return super().default(o)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Qbus state models."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from typing import Any
|
|
5
|
+
from .const import (
|
|
6
|
+
KEY_OUTPUT_ACTION,
|
|
7
|
+
KEY_OUTPUT_ID,
|
|
8
|
+
KEY_OUTPUT_PROPERTIES,
|
|
9
|
+
KEY_OUTPUT_TYPE,
|
|
10
|
+
KEY_PROPERTIES_VALUE,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
KEY_CONTROLLER_CONNECTABLE = "connectable"
|
|
14
|
+
KEY_CONTROLLER_CONNECTED = "connected"
|
|
15
|
+
KEY_CONTROLLER_ID = "id"
|
|
16
|
+
KEY_CONTROLLER_STATE_PROPERTIES = "properties"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StateType(StrEnum):
|
|
20
|
+
"""Values to be used as state type."""
|
|
21
|
+
|
|
22
|
+
ACTION = "action"
|
|
23
|
+
STATE = "state"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StateAction(StrEnum):
|
|
27
|
+
"""Values to be used as state action."""
|
|
28
|
+
|
|
29
|
+
ACTIVATE = "activate"
|
|
30
|
+
ACTIVE = "active"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class QbusMqttControllerStateProperties:
|
|
34
|
+
"""MQTT representation a Qbus controller its state properties."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, data: dict) -> None:
|
|
37
|
+
"""Initialize based on a json loaded dictionary."""
|
|
38
|
+
self.connectable: bool | None = data.get(KEY_CONTROLLER_CONNECTABLE)
|
|
39
|
+
self.connected: bool | None = data.get(KEY_CONTROLLER_CONNECTED)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class QbusMqttControllerState:
|
|
43
|
+
"""MQTT representation of a Qbus controller state."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, data: dict) -> None:
|
|
46
|
+
"""Initialize based on a json loaded dictionary."""
|
|
47
|
+
self.id: str | None = data.get(KEY_CONTROLLER_ID)
|
|
48
|
+
|
|
49
|
+
properties = data.get(KEY_CONTROLLER_STATE_PROPERTIES)
|
|
50
|
+
self.properties: QbusMqttControllerStateProperties | None = (
|
|
51
|
+
QbusMqttControllerStateProperties(properties)
|
|
52
|
+
if properties is not None
|
|
53
|
+
else None
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class QbusMqttState:
|
|
58
|
+
"""MQTT representation of a Qbus state."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
data: dict | None = None,
|
|
63
|
+
*,
|
|
64
|
+
id: str | None = None,
|
|
65
|
+
type: str | None = None,
|
|
66
|
+
action: str | None = None,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Initialize state."""
|
|
69
|
+
self.id: str = ""
|
|
70
|
+
self.type: str = ""
|
|
71
|
+
self.action: str | None = None
|
|
72
|
+
self.properties: dict | None = None
|
|
73
|
+
|
|
74
|
+
if data is not None:
|
|
75
|
+
self.id = data.get(KEY_OUTPUT_ID, "")
|
|
76
|
+
self.type = data.get(KEY_OUTPUT_TYPE, "")
|
|
77
|
+
self.action = data.get(KEY_OUTPUT_ACTION)
|
|
78
|
+
self.properties = data.get(KEY_OUTPUT_PROPERTIES)
|
|
79
|
+
|
|
80
|
+
if id is not None:
|
|
81
|
+
self.id = id
|
|
82
|
+
|
|
83
|
+
if type is not None:
|
|
84
|
+
self.type = type
|
|
85
|
+
|
|
86
|
+
if action is not None:
|
|
87
|
+
self.action = action
|
|
88
|
+
|
|
89
|
+
def read_property(self, key: str, default: Any) -> Any:
|
|
90
|
+
"""Read a property."""
|
|
91
|
+
return self.properties.get(key, default) if self.properties else default
|
|
92
|
+
|
|
93
|
+
def write_property(self, key: str, value: Any) -> None:
|
|
94
|
+
"""Add or update a property."""
|
|
95
|
+
if self.properties is None:
|
|
96
|
+
self.properties = {}
|
|
97
|
+
|
|
98
|
+
self.properties[key] = value
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class QbusMqttOnOffState(QbusMqttState):
|
|
102
|
+
"""MQTT representation of a Qbus on/off output."""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
data: dict | None = None,
|
|
107
|
+
*,
|
|
108
|
+
id: str | None = None,
|
|
109
|
+
type: str | None = None,
|
|
110
|
+
) -> None:
|
|
111
|
+
super().__init__(data, id=id, type=type)
|
|
112
|
+
|
|
113
|
+
def read_value(self) -> bool:
|
|
114
|
+
"""Read the value of the Qbus output."""
|
|
115
|
+
return self.read_property(KEY_PROPERTIES_VALUE, False)
|
|
116
|
+
|
|
117
|
+
def write_value(self, value: bool) -> None:
|
|
118
|
+
"""Set the value of the Qbus output."""
|
|
119
|
+
self.write_property(KEY_PROPERTIES_VALUE, value)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class QbusMqttAnalogState(QbusMqttState):
|
|
123
|
+
"""MQTT representation of a Qbus analog output."""
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
data: dict | None = None,
|
|
128
|
+
*,
|
|
129
|
+
id: str | None = None,
|
|
130
|
+
type: str | None = None,
|
|
131
|
+
) -> None:
|
|
132
|
+
super().__init__(data, id=id, type=type)
|
|
133
|
+
|
|
134
|
+
def read_percentage(self) -> float:
|
|
135
|
+
"""Read the value of the Qbus output."""
|
|
136
|
+
return self.read_property(KEY_PROPERTIES_VALUE, 0)
|
|
137
|
+
|
|
138
|
+
def write_percentage(self, percentage: float) -> None:
|
|
139
|
+
"""Set the value of the Qbus output."""
|
|
140
|
+
self.write_property(KEY_PROPERTIES_VALUE, percentage)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qbusmqttapi
|
|
3
|
+
Version: 0.2.7
|
|
4
|
+
Summary: MQTT API for Qbus Home Automation.
|
|
5
|
+
Home-page: https://github.com/Qbus-iot/qbusmqttapi
|
|
6
|
+
Author: Koen Schockaert
|
|
7
|
+
Author-email: ks@qbus.be
|
|
8
|
+
License: MIT License 2024
|
|
9
|
+
Project-URL: Source Code, https://github.com/Qbus-iot/qbusmqttapi
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
|
|
17
|
+
# qbusmqttapi
|
|
18
|
+
Python MQTT API for Qbus Home Automation
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.cfg
|
|
5
|
+
setup.py
|
|
6
|
+
src/qbusmqttapi/__init__.py
|
|
7
|
+
src/qbusmqttapi/const.py
|
|
8
|
+
src/qbusmqttapi/discovery.py
|
|
9
|
+
src/qbusmqttapi/factory.py
|
|
10
|
+
src/qbusmqttapi/state.py
|
|
11
|
+
src/qbusmqttapi.egg-info/PKG-INFO
|
|
12
|
+
src/qbusmqttapi.egg-info/SOURCES.txt
|
|
13
|
+
src/qbusmqttapi.egg-info/dependency_links.txt
|
|
14
|
+
src/qbusmqttapi.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
qbusmqttapi
|