SolarManager unter Versionsverwaltung
Erster Stand der Hintergrundprozesse, die auf der Synology unter /volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins Web-Verzeichnis. Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot), zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat. Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py, gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte. Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein pip, sie muessen neben den Skripten liegen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
# Copyright (c) 2014 Roger Light <roger@atchoo.org>
|
||||
#
|
||||
# All rights reserved. This program and the accompanying materials
|
||||
# are made available under the terms of the Eclipse Public License v2.0
|
||||
# and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
#
|
||||
# The Eclipse Public License is available at
|
||||
# http://www.eclipse.org/legal/epl-v20.html
|
||||
# and the Eclipse Distribution License is available at
|
||||
# http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
#
|
||||
# Contributors:
|
||||
# Roger Light - initial API and implementation
|
||||
|
||||
"""
|
||||
This module provides some helper functions to allow straightforward publishing
|
||||
of messages in a one-shot manner. In other words, they are useful for the
|
||||
situation where you have a single/multiple messages you want to publish to a
|
||||
broker, then disconnect and nothing else is required.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Any, List, Tuple, Union
|
||||
|
||||
from paho.mqtt.enums import CallbackAPIVersion, MQTTProtocolVersion
|
||||
from paho.mqtt.properties import Properties
|
||||
from paho.mqtt.reasoncodes import ReasonCode
|
||||
|
||||
from .. import mqtt
|
||||
from . import client as paho
|
||||
|
||||
if TYPE_CHECKING:
|
||||
try:
|
||||
from typing import NotRequired, Required, TypedDict # type: ignore
|
||||
except ImportError:
|
||||
from typing_extensions import NotRequired, Required, TypedDict
|
||||
|
||||
try:
|
||||
from typing import Literal
|
||||
except ImportError:
|
||||
from typing_extensions import Literal # type: ignore
|
||||
|
||||
|
||||
|
||||
class AuthParameter(TypedDict, total=False):
|
||||
username: Required[str]
|
||||
password: NotRequired[str]
|
||||
|
||||
|
||||
class TLSParameter(TypedDict, total=False):
|
||||
ca_certs: Required[str]
|
||||
certfile: NotRequired[str]
|
||||
keyfile: NotRequired[str]
|
||||
tls_version: NotRequired[int]
|
||||
ciphers: NotRequired[str]
|
||||
insecure: NotRequired[bool]
|
||||
|
||||
|
||||
class MessageDict(TypedDict, total=False):
|
||||
topic: Required[str]
|
||||
payload: NotRequired[paho.PayloadType]
|
||||
qos: NotRequired[int]
|
||||
retain: NotRequired[bool]
|
||||
|
||||
MessageTuple = Tuple[str, paho.PayloadType, int, bool]
|
||||
|
||||
MessagesList = List[Union[MessageDict, MessageTuple]]
|
||||
|
||||
|
||||
def _do_publish(client: paho.Client):
|
||||
"""Internal function"""
|
||||
|
||||
message = client._userdata.popleft()
|
||||
|
||||
if isinstance(message, dict):
|
||||
client.publish(**message)
|
||||
elif isinstance(message, (tuple, list)):
|
||||
client.publish(*message)
|
||||
else:
|
||||
raise TypeError('message must be a dict, tuple, or list')
|
||||
|
||||
|
||||
def _on_connect(client: paho.Client, userdata: MessagesList, flags, reason_code, properties):
|
||||
"""Internal v5 callback"""
|
||||
if reason_code == 0:
|
||||
if len(userdata) > 0:
|
||||
_do_publish(client)
|
||||
else:
|
||||
raise mqtt.MQTTException(paho.connack_string(reason_code))
|
||||
|
||||
|
||||
def _on_publish(
|
||||
client: paho.Client, userdata: collections.deque[MessagesList], mid: int, reason_codes: ReasonCode, properties: Properties,
|
||||
) -> None:
|
||||
"""Internal callback"""
|
||||
#pylint: disable=unused-argument
|
||||
|
||||
if len(userdata) == 0:
|
||||
client.disconnect()
|
||||
else:
|
||||
_do_publish(client)
|
||||
|
||||
|
||||
def multiple(
|
||||
msgs: MessagesList,
|
||||
hostname: str = "localhost",
|
||||
port: int = 1883,
|
||||
client_id: str = "",
|
||||
keepalive: int = 60,
|
||||
will: MessageDict | None = None,
|
||||
auth: AuthParameter | None = None,
|
||||
tls: TLSParameter | None = None,
|
||||
protocol: MQTTProtocolVersion = paho.MQTTv311,
|
||||
transport: Literal["tcp", "websockets"] = "tcp",
|
||||
proxy_args: Any | None = None,
|
||||
) -> None:
|
||||
"""Publish multiple messages to a broker, then disconnect cleanly.
|
||||
|
||||
This function creates an MQTT client, connects to a broker and publishes a
|
||||
list of messages. Once the messages have been delivered, it disconnects
|
||||
cleanly from the broker.
|
||||
|
||||
:param msgs: a list of messages to publish. Each message is either a dict or a
|
||||
tuple.
|
||||
|
||||
If a dict, only the topic must be present. Default values will be
|
||||
used for any missing arguments. The dict must be of the form:
|
||||
|
||||
msg = {'topic':"<topic>", 'payload':"<payload>", 'qos':<qos>,
|
||||
'retain':<retain>}
|
||||
topic must be present and may not be empty.
|
||||
If payload is "", None or not present then a zero length payload
|
||||
will be published.
|
||||
If qos is not present, the default of 0 is used.
|
||||
If retain is not present, the default of False is used.
|
||||
|
||||
If a tuple, then it must be of the form:
|
||||
("<topic>", "<payload>", qos, retain)
|
||||
|
||||
:param str hostname: the address of the broker to connect to.
|
||||
Defaults to localhost.
|
||||
|
||||
:param int port: the port to connect to the broker on. Defaults to 1883.
|
||||
|
||||
:param str client_id: the MQTT client id to use. If "" or None, the Paho library will
|
||||
generate a client id automatically.
|
||||
|
||||
:param int keepalive: the keepalive timeout value for the client. Defaults to 60
|
||||
seconds.
|
||||
|
||||
:param will: a dict containing will parameters for the client: will = {'topic':
|
||||
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
||||
Topic is required, all other parameters are optional and will
|
||||
default to None, 0 and False respectively.
|
||||
Defaults to None, which indicates no will should be used.
|
||||
|
||||
:param auth: a dict containing authentication parameters for the client:
|
||||
auth = {'username':"<username>", 'password':"<password>"}
|
||||
Username is required, password is optional and will default to None
|
||||
if not provided.
|
||||
Defaults to None, which indicates no authentication is to be used.
|
||||
|
||||
:param tls: a dict containing TLS configuration parameters for the client:
|
||||
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
||||
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
||||
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
||||
ca_certs is required, all other parameters are optional and will
|
||||
default to None if not provided, which results in the client using
|
||||
the default behaviour - see the paho.mqtt.client documentation.
|
||||
Alternatively, tls input can be an SSLContext object, which will be
|
||||
processed using the tls_set_context method.
|
||||
Defaults to None, which indicates that TLS should not be used.
|
||||
|
||||
:param str transport: set to "tcp" to use the default setting of transport which is
|
||||
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
||||
|
||||
:param proxy_args: a dictionary that will be given to the client.
|
||||
"""
|
||||
|
||||
if not isinstance(msgs, Iterable):
|
||||
raise TypeError('msgs must be an iterable')
|
||||
if len(msgs) == 0:
|
||||
raise ValueError('msgs is empty')
|
||||
|
||||
client = paho.Client(
|
||||
CallbackAPIVersion.VERSION2,
|
||||
client_id=client_id,
|
||||
userdata=collections.deque(msgs),
|
||||
protocol=protocol,
|
||||
transport=transport,
|
||||
)
|
||||
|
||||
client.enable_logger()
|
||||
client.on_publish = _on_publish
|
||||
client.on_connect = _on_connect # type: ignore
|
||||
|
||||
if proxy_args is not None:
|
||||
client.proxy_set(**proxy_args)
|
||||
|
||||
if auth:
|
||||
username = auth.get('username')
|
||||
if username:
|
||||
password = auth.get('password')
|
||||
client.username_pw_set(username, password)
|
||||
else:
|
||||
raise KeyError("The 'username' key was not found, this is "
|
||||
"required for auth")
|
||||
|
||||
if will is not None:
|
||||
client.will_set(**will)
|
||||
|
||||
if tls is not None:
|
||||
if isinstance(tls, dict):
|
||||
insecure = tls.pop('insecure', False)
|
||||
# mypy don't get that tls no longer contains the key insecure
|
||||
client.tls_set(**tls) # type: ignore[misc]
|
||||
if insecure:
|
||||
# Must be set *after* the `client.tls_set()` call since it sets
|
||||
# up the SSL context that `client.tls_insecure_set` alters.
|
||||
client.tls_insecure_set(insecure)
|
||||
else:
|
||||
# Assume input is SSLContext object
|
||||
client.tls_set_context(tls)
|
||||
|
||||
client.connect(hostname, port, keepalive)
|
||||
client.loop_forever()
|
||||
|
||||
|
||||
def single(
|
||||
topic: str,
|
||||
payload: paho.PayloadType = None,
|
||||
qos: int = 0,
|
||||
retain: bool = False,
|
||||
hostname: str = "localhost",
|
||||
port: int = 1883,
|
||||
client_id: str = "",
|
||||
keepalive: int = 60,
|
||||
will: MessageDict | None = None,
|
||||
auth: AuthParameter | None = None,
|
||||
tls: TLSParameter | None = None,
|
||||
protocol: MQTTProtocolVersion = paho.MQTTv311,
|
||||
transport: Literal["tcp", "websockets"] = "tcp",
|
||||
proxy_args: Any | None = None,
|
||||
) -> None:
|
||||
"""Publish a single message to a broker, then disconnect cleanly.
|
||||
|
||||
This function creates an MQTT client, connects to a broker and publishes a
|
||||
single message. Once the message has been delivered, it disconnects cleanly
|
||||
from the broker.
|
||||
|
||||
:param str topic: the only required argument must be the topic string to which the
|
||||
payload will be published.
|
||||
|
||||
:param payload: the payload to be published. If "" or None, a zero length payload
|
||||
will be published.
|
||||
|
||||
:param int qos: the qos to use when publishing, default to 0.
|
||||
|
||||
:param bool retain: set the message to be retained (True) or not (False).
|
||||
|
||||
:param str hostname: the address of the broker to connect to.
|
||||
Defaults to localhost.
|
||||
|
||||
:param int port: the port to connect to the broker on. Defaults to 1883.
|
||||
|
||||
:param str client_id: the MQTT client id to use. If "" or None, the Paho library will
|
||||
generate a client id automatically.
|
||||
|
||||
:param int keepalive: the keepalive timeout value for the client. Defaults to 60
|
||||
seconds.
|
||||
|
||||
:param will: a dict containing will parameters for the client: will = {'topic':
|
||||
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
||||
Topic is required, all other parameters are optional and will
|
||||
default to None, 0 and False respectively.
|
||||
Defaults to None, which indicates no will should be used.
|
||||
|
||||
:param auth: a dict containing authentication parameters for the client:
|
||||
Username is required, password is optional and will default to None
|
||||
auth = {'username':"<username>", 'password':"<password>"}
|
||||
if not provided.
|
||||
Defaults to None, which indicates no authentication is to be used.
|
||||
|
||||
:param tls: a dict containing TLS configuration parameters for the client:
|
||||
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
||||
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
||||
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
||||
ca_certs is required, all other parameters are optional and will
|
||||
default to None if not provided, which results in the client using
|
||||
the default behaviour - see the paho.mqtt.client documentation.
|
||||
Defaults to None, which indicates that TLS should not be used.
|
||||
Alternatively, tls input can be an SSLContext object, which will be
|
||||
processed using the tls_set_context method.
|
||||
|
||||
:param transport: set to "tcp" to use the default setting of transport which is
|
||||
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
||||
|
||||
:param proxy_args: a dictionary that will be given to the client.
|
||||
"""
|
||||
|
||||
msg: MessageDict = {'topic':topic, 'payload':payload, 'qos':qos, 'retain':retain}
|
||||
|
||||
multiple([msg], hostname, port, client_id, keepalive, will, auth, tls,
|
||||
protocol, transport, proxy_args)
|
||||
Reference in New Issue
Block a user