updates2mqtt 1.6.0__py3-none-any.whl → 1.7.0__py3-none-any.whl
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.
- updates2mqtt/app.py +10 -7
- updates2mqtt/config.py +30 -19
- updates2mqtt/hass_formatter.py +7 -17
- updates2mqtt/integrations/docker.py +148 -149
- updates2mqtt/integrations/docker_enrich.py +344 -0
- updates2mqtt/model.py +134 -16
- updates2mqtt/mqtt.py +26 -5
- {updates2mqtt-1.6.0.dist-info → updates2mqtt-1.7.0.dist-info}/METADATA +9 -13
- updates2mqtt-1.7.0.dist-info/RECORD +16 -0
- {updates2mqtt-1.6.0.dist-info → updates2mqtt-1.7.0.dist-info}/WHEEL +2 -2
- updates2mqtt-1.6.0.dist-info/RECORD +0 -15
- {updates2mqtt-1.6.0.dist-info → updates2mqtt-1.7.0.dist-info}/entry_points.txt +0 -0
updates2mqtt/app.py
CHANGED
|
@@ -14,7 +14,7 @@ import structlog
|
|
|
14
14
|
import updates2mqtt
|
|
15
15
|
from updates2mqtt.model import Discovery, ReleaseProvider
|
|
16
16
|
|
|
17
|
-
from .config import Config,
|
|
17
|
+
from .config import Config, PublishPolicy, UpdatePolicy, load_app_config
|
|
18
18
|
from .integrations.docker import DockerProvider
|
|
19
19
|
from .mqtt import MqttPublisher
|
|
20
20
|
|
|
@@ -49,7 +49,6 @@ class App:
|
|
|
49
49
|
|
|
50
50
|
structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(getattr(logging, str(self.cfg.log.level))))
|
|
51
51
|
log.debug("Logging initialized", level=self.cfg.log.level)
|
|
52
|
-
self.common_pkg: dict[str, PackageUpdateInfo] = load_package_info(PKG_INFO_FILE)
|
|
53
52
|
|
|
54
53
|
self.publisher = MqttPublisher(self.cfg.mqtt, self.cfg.node, self.cfg.homeassistant)
|
|
55
54
|
|
|
@@ -57,7 +56,7 @@ class App:
|
|
|
57
56
|
self.scan_count: int = 0
|
|
58
57
|
self.last_scan: str | None = None
|
|
59
58
|
if self.cfg.docker.enabled:
|
|
60
|
-
self.scanners.append(DockerProvider(self.cfg.docker, self.
|
|
59
|
+
self.scanners.append(DockerProvider(self.cfg.docker, self.cfg.node, self.self_bounce))
|
|
61
60
|
self.stopped = Event()
|
|
62
61
|
self.healthcheck_topic = self.cfg.node.healthcheck.topic_template.format(node_name=self.cfg.node.name)
|
|
63
62
|
|
|
@@ -104,6 +103,7 @@ class App:
|
|
|
104
103
|
)
|
|
105
104
|
|
|
106
105
|
for scanner in self.scanners:
|
|
106
|
+
scanner.initialize()
|
|
107
107
|
self.publisher.subscribe_hass_command(scanner)
|
|
108
108
|
|
|
109
109
|
while not self.stopped.is_set() and self.publisher.is_available():
|
|
@@ -122,12 +122,15 @@ class App:
|
|
|
122
122
|
async def on_discovery(self, discovery: Discovery) -> None:
|
|
123
123
|
dlog = log.bind(name=discovery.name)
|
|
124
124
|
try:
|
|
125
|
-
if self.cfg.homeassistant.discovery.enabled:
|
|
125
|
+
if discovery.publish_policy == PublishPolicy.HOMEASSISTANT and self.cfg.homeassistant.discovery.enabled:
|
|
126
|
+
# Switch off MQTT discovery if not Home Assistant enabled
|
|
126
127
|
self.publisher.publish_hass_config(discovery)
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT):
|
|
129
|
+
self.publisher.publish_hass_state(discovery)
|
|
130
|
+
if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
|
|
131
|
+
self.publisher.publish_discovery(discovery)
|
|
129
132
|
if (
|
|
130
|
-
discovery.update_policy ==
|
|
133
|
+
discovery.update_policy == UpdatePolicy.AUTO
|
|
131
134
|
and discovery.can_update
|
|
132
135
|
and discovery.latest_version != discovery.current_version
|
|
133
136
|
):
|
updates2mqtt/config.py
CHANGED
|
@@ -9,6 +9,20 @@ from omegaconf import MISSING, DictConfig, MissingMandatoryValue, OmegaConf, Val
|
|
|
9
9
|
|
|
10
10
|
log = structlog.get_logger()
|
|
11
11
|
|
|
12
|
+
PKG_INFO_FILE = Path("./common_packages.yaml")
|
|
13
|
+
NO_KNOWN_IMAGE = "UNKNOWN"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UpdatePolicy(StrEnum):
|
|
17
|
+
AUTO = "Auto"
|
|
18
|
+
PASSIVE = "Passive"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PublishPolicy(StrEnum):
|
|
22
|
+
HOMEASSISTANT = "HomeAssistant"
|
|
23
|
+
MQTT = "MQTT"
|
|
24
|
+
SILENT = "Silent"
|
|
25
|
+
|
|
12
26
|
|
|
13
27
|
class LogLevel(StrEnum):
|
|
14
28
|
DEBUG = "DEBUG"
|
|
@@ -18,6 +32,13 @@ class LogLevel(StrEnum):
|
|
|
18
32
|
CRITICAL = "CRITICAL"
|
|
19
33
|
|
|
20
34
|
|
|
35
|
+
class VersionType:
|
|
36
|
+
SHORT_SHA = "short_sha"
|
|
37
|
+
FULL_SHA = "full_sha"
|
|
38
|
+
VERSION_REVISION = "version_revision"
|
|
39
|
+
VERSION = "version"
|
|
40
|
+
|
|
41
|
+
|
|
21
42
|
@dataclass
|
|
22
43
|
class MqttConfig:
|
|
23
44
|
host: str = "${oc.env:MQTT_HOST,localhost}"
|
|
@@ -34,6 +55,12 @@ class MetadataSourceConfig:
|
|
|
34
55
|
cache_ttl: int = 60 * 60 * 24 * 7 # 1 week
|
|
35
56
|
|
|
36
57
|
|
|
58
|
+
@dataclass
|
|
59
|
+
class Selector:
|
|
60
|
+
include: list[str] | None = None
|
|
61
|
+
exclude: list[str] | None = None
|
|
62
|
+
|
|
63
|
+
|
|
37
64
|
@dataclass
|
|
38
65
|
class DockerConfig:
|
|
39
66
|
enabled: bool = True
|
|
@@ -47,7 +74,8 @@ class DockerConfig:
|
|
|
47
74
|
discover_metadata: dict[str, MetadataSourceConfig] = field(
|
|
48
75
|
default_factory=lambda: {"linuxserver.io": MetadataSourceConfig(enabled=True)}
|
|
49
76
|
)
|
|
50
|
-
|
|
77
|
+
default_api_backoff: int = 60 * 15
|
|
78
|
+
image_ref_select: Selector = field(default_factory=lambda: Selector())
|
|
51
79
|
|
|
52
80
|
|
|
53
81
|
@dataclass
|
|
@@ -62,6 +90,7 @@ class HomeAssistantConfig:
|
|
|
62
90
|
state_topic_suffix: str = "state"
|
|
63
91
|
device_creation: bool = True
|
|
64
92
|
force_command_topic: bool = False
|
|
93
|
+
extra_attributes: bool = True
|
|
65
94
|
area: str | None = None
|
|
66
95
|
|
|
67
96
|
|
|
@@ -115,24 +144,6 @@ class IncompleteConfigException(BaseException):
|
|
|
115
144
|
pass
|
|
116
145
|
|
|
117
146
|
|
|
118
|
-
def load_package_info(pkginfo_file_path: Path) -> dict[str, PackageUpdateInfo]:
|
|
119
|
-
if pkginfo_file_path.exists():
|
|
120
|
-
log.debug("Loading common package update info", path=pkginfo_file_path)
|
|
121
|
-
cfg = OmegaConf.load(pkginfo_file_path)
|
|
122
|
-
else:
|
|
123
|
-
log.warn("No common package update info found", path=pkginfo_file_path)
|
|
124
|
-
cfg = OmegaConf.structured(UpdateInfoConfig)
|
|
125
|
-
try:
|
|
126
|
-
# omegaconf broken-ness on optional fields and converting to backclasses
|
|
127
|
-
pkg_conf: dict[str, PackageUpdateInfo] = {
|
|
128
|
-
pkg: PackageUpdateInfo(**pkg_cfg) for pkg, pkg_cfg in cfg.common_packages.items()
|
|
129
|
-
}
|
|
130
|
-
return pkg_conf
|
|
131
|
-
except (MissingMandatoryValue, ValidationError) as e:
|
|
132
|
-
log.error("Configuration error %s", e, path=pkginfo_file_path.as_posix())
|
|
133
|
-
raise
|
|
134
|
-
|
|
135
|
-
|
|
136
147
|
def is_autogen_config() -> bool:
|
|
137
148
|
env_var: str | None = os.environ.get("U2M_AUTOGEN_CONFIG")
|
|
138
149
|
return not (env_var and env_var.lower() in ("no", "0", "false"))
|
updates2mqtt/hass_formatter.py
CHANGED
|
@@ -23,28 +23,26 @@ def hass_format_config(
|
|
|
23
23
|
object_id: str,
|
|
24
24
|
state_topic: str,
|
|
25
25
|
command_topic: str | None,
|
|
26
|
+
attrs_topic: str | None,
|
|
26
27
|
force_command_topic: bool | None,
|
|
27
28
|
device_creation: bool = True,
|
|
28
29
|
area: str | None = None,
|
|
29
|
-
session: str | None = None,
|
|
30
30
|
) -> dict[str, Any]:
|
|
31
31
|
config: dict[str, Any] = {
|
|
32
32
|
"name": discovery.title,
|
|
33
33
|
"device_class": None, # not firmware, so defaults to null
|
|
34
34
|
"unique_id": object_id,
|
|
35
35
|
"state_topic": state_topic,
|
|
36
|
-
"source_session": session,
|
|
37
36
|
"supported_features": discovery.features,
|
|
38
|
-
"
|
|
39
|
-
"can_build": discovery.can_build,
|
|
40
|
-
"can_restart": discovery.can_restart,
|
|
41
|
-
"update_policy": discovery.update_policy,
|
|
37
|
+
"default_entity_id": f"update.{discovery.node}_{discovery.provider.source_type}_{discovery.name}",
|
|
42
38
|
"origin": {
|
|
43
39
|
"name": f"{discovery.node} updates2mqtt",
|
|
44
40
|
"sw_version": updates2mqtt.version, # pyright: ignore[reportAttributeAccessIssue]
|
|
45
41
|
"support_url": "https://github.com/rhizomatics/updates2mqtt/issues",
|
|
46
42
|
},
|
|
47
43
|
}
|
|
44
|
+
if attrs_topic:
|
|
45
|
+
config["json_attributes_topic"] = attrs_topic
|
|
48
46
|
if discovery.entity_picture_url:
|
|
49
47
|
config["entity_picture"] = discovery.entity_picture_url
|
|
50
48
|
if discovery.device_icon:
|
|
@@ -62,14 +60,12 @@ def hass_format_config(
|
|
|
62
60
|
config["command_topic"] = command_topic
|
|
63
61
|
if discovery.can_update:
|
|
64
62
|
config["payload_install"] = f"{discovery.source_type}|{discovery.name}|install"
|
|
65
|
-
|
|
66
|
-
config["custom"][discovery.source_type] = discovery.custom
|
|
67
|
-
config.update(discovery.provider.hass_config_format(discovery))
|
|
63
|
+
|
|
68
64
|
return config
|
|
69
65
|
|
|
70
66
|
|
|
71
67
|
def hass_format_state(discovery: Discovery, session: str, in_progress: bool = False) -> dict[str, Any]: # noqa: ARG001
|
|
72
|
-
state = {
|
|
68
|
+
state: dict[str, str | dict | list | bool | None] = {
|
|
73
69
|
"installed_version": discovery.current_version,
|
|
74
70
|
"latest_version": discovery.latest_version,
|
|
75
71
|
"title": discovery.title,
|
|
@@ -79,11 +75,5 @@ def hass_format_state(discovery: Discovery, session: str, in_progress: bool = Fa
|
|
|
79
75
|
state["release_summary"] = discovery.release_summary
|
|
80
76
|
if discovery.release_url:
|
|
81
77
|
state["release_url"] = discovery.release_url
|
|
82
|
-
|
|
83
|
-
if custom_state:
|
|
84
|
-
state.update(custom_state)
|
|
85
|
-
invalid_keys = [k for k in state if k not in HASS_UPDATE_SCHEMA]
|
|
86
|
-
if invalid_keys:
|
|
87
|
-
log.warning(f"Invalid keys in state: {invalid_keys}")
|
|
88
|
-
state = {k: v for k, v in state.items() if k in HASS_UPDATE_SCHEMA}
|
|
78
|
+
|
|
89
79
|
return state
|