Skip to content

Commit 5f5b8b9

Browse files
authored
Merge pull request #1468 from openWB/feature-partner-id
Feature partner id
2 parents 14029d2 + 7bd4d0f commit 5f5b8b9

File tree

267 files changed

+523
-460
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

267 files changed

+523
-460
lines changed

packages/helpermodules/setdata.py

+22-21
Original file line numberDiff line numberDiff line change
@@ -273,31 +273,30 @@ def _validate_collection_value(self, msg: mqtt.MQTTMessage, data_type, ranges=No
273273
Broker-Nachricht
274274
data_type: float, int
275275
Datentyp, den die Liste enthalten soll
276-
min_value: int/float
277-
Minimalwert, den die Elemente in der Liste nicht unterschreiten dürfen
278-
max_value= int/float
279-
Maximalwert, den die Elemente in der Liste nicht überschreiten dürfen
280-
collection = list/dict
281-
Angabe, ob und welche Kollektion erwartet wird
276+
ranges: tuple, optional
277+
(min_value, max_value), die die Minimal- und Maximalwerte angeben
278+
collection: type, optional
279+
Angabe, ob und welche Kollektion erwartet wird (list oder dict)
282280
"""
283281
try:
284282
valid = False
285283
value = decode_payload(msg.payload)
286-
if isinstance(value, list):
287-
for item in value:
288-
if not self._validate_min_max_value(item, msg, data_type, ranges):
289-
break
290-
else:
291-
valid = True
292-
elif isinstance(value, dict):
293-
for item in value.values():
294-
if not self._validate_min_max_value(item, msg, data_type, ranges):
295-
break
296-
else:
297-
valid = True
298-
else:
299-
log.error("Payload ungültig: Topic "+str(msg.topic)+", Payload " +
300-
str(value)+" sollte eine Kollektion vom Typ "+str(collection)+" sein.")
284+
if collection is not None and isinstance(value, collection):
285+
if isinstance(value, list):
286+
if ranges is not None:
287+
valid = all(self._validate_min_max_value(item, msg, data_type, ranges) for item in value)
288+
else:
289+
valid = all(isinstance(item, data_type) for item in value)
290+
elif isinstance(value, dict):
291+
if ranges is not None:
292+
valid = all(
293+
self._validate_min_max_value(item, msg, data_type, ranges) for item in value.values())
294+
else:
295+
valid = all(isinstance(item, data_type) for item in value.values())
296+
if not valid:
297+
log.error(f"Payload ungültig: Topic '{msg.topic}', Payload '{value}' "
298+
f"sollte eine Kollektion vom Typ {collection} sein "
299+
f"und nur Elemente vom Typ {data_type} enthalten.")
301300
return valid
302301
except Exception:
303302
log.exception(f"Fehler im setdata-Modul: Topic {msg.topic}, Value: {msg.payload}")
@@ -1010,6 +1009,8 @@ def process_system_topic(self, msg: mqtt.MQTTMessage):
10101009
self._validate_value(msg, str)
10111010
elif "openWB/set/system/mqtt/bridge/" in msg.topic:
10121011
self._validate_value(msg, "json")
1012+
elif "openWB/set/system/mqtt/valid_partner_ids" == msg.topic:
1013+
self._validate_value(msg, str, collection=list)
10131014
elif "configurable" in msg.topic:
10141015
self._validate_value(msg, None)
10151016
elif "device" in msg.topic:

packages/helpermodules/subdata.py

+5
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def on_connect(self, client: mqtt.Client, userdata, flags: dict, rc: int):
135135
# MQTT Bridge Topics vor "openWB/system/+" abonnieren, damit sie auch vor
136136
# "openWB/system/subdata_initialized" empfangen werden!
137137
("openWB/system/mqtt/bridge/+", 2),
138+
("openWB/system/mqtt/+", 2),
138139
# Nicht mit hash # abonnieren, damit nicht die Komponenten vor den Devices empfangen werden!
139140
("openWB/system/+", 2),
140141
("openWB/system/backup_cloud/#", 2),
@@ -763,6 +764,10 @@ def process_system_topic(self, client: mqtt.Client, var: dict, msg: mqtt.MQTTMes
763764
MessageType.SUCCESS if result.returncode == 0 else MessageType.ERROR)
764765
else:
765766
log.debug("skipping mqtt bridge message on startup")
767+
elif "mqtt" and "valid_partner_ids" in msg.topic:
768+
# duplicate topic for remote support service
769+
log.error(f"received valid partner ids: {decode_payload(msg.payload)}")
770+
Pub().pub("openWB-remote/valid_partner_ids", decode_payload(msg.payload))
766771
# will be moved to separate handler!
767772
elif "GetRemoteSupport" in msg.topic:
768773
log.warning("deprecated topic for remote support received!")

packages/helpermodules/update_config.py

+2
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ class UpdateConfig:
392392
"^openWB/system/lastlivevaluesJson$",
393393
"^openWB/system/messages/[0-9]+$",
394394
"^openWB/system/mqtt/bridge/[0-9]+$",
395+
"^openWB/system/mqtt/valid_partner_ids$",
395396
"^openWB/system/release_train$",
396397
"^openWB/system/time$",
397398
"^openWB/system/update_in_progress$",
@@ -474,6 +475,7 @@ class UpdateConfig:
474475
("openWB/system/debug_level", 30),
475476
("openWB/system/device/module_update_completed", True),
476477
("openWB/system/ip_address", "unknown"),
478+
("openWB/system/mqtt/valid_partner_ids", []),
477479
("openWB/system/release_train", "master"),
478480
)
479481
invalid_topic = (

runs/remoteSupport/remoteSupport.py

+101-30
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
#!/usr/bin/env python3
22
import logging
33
import re
4+
import json
45
from subprocess import Popen
56
from pathlib import Path
7+
from time import sleep
8+
from typing import Optional
69
import paho.mqtt.client as mqtt
710
import platform
811

12+
API_VERSION = "1"
913
BASE_PATH = Path(__file__).resolve().parents[2]
1014
RAMDISK_PATH = BASE_PATH / "ramdisk"
1115
RUNS_PATH = BASE_PATH / "runs"
1216
BASE_TOPIC = "openWB-remote/"
17+
API_TOPIC = BASE_TOPIC + "api_version"
18+
STATE_TOPIC = BASE_TOPIC + "connection_state"
1319
REMOTE_SUPPORT_TOPIC = BASE_TOPIC + "support"
1420
REMOTE_PARTNER_TOPIC = BASE_TOPIC + "partner"
21+
REMOTE_PARTNER_IDS_TOPIC = BASE_TOPIC + "valid_partner_ids"
1522
CLOUD_TOPIC = BASE_TOPIC + "cloud"
23+
1624
support_tunnel: Popen = None
1725
partner_tunnel: Popen = None
1826
cloud_tunnel: Popen = None
27+
valid_partner_ids: list[str] = []
1928
logging.basicConfig(
2029
filename=str(RAMDISK_PATH / "remote_support.log"),
2130
level=logging.DEBUG, format='%(asctime)s: %(message)s'
@@ -32,10 +41,43 @@ def get_serial():
3241
return "0000000000000000"
3342

3443

44+
def publish_as_json(client: mqtt.Client, topic: str, str_payload: str, qos: int = 0, retain: bool = False,
45+
properties: Optional[mqtt.Properties] = None) -> mqtt.MQTTMessageInfo:
46+
return client.publish(topic, json.dumps(str_payload), qos, retain, properties)
47+
48+
49+
def get_lt_executable() -> Optional[Path]:
50+
machine = platform.machine()
51+
bits, linkage = platform.architecture()
52+
lt_executable = f"lt-{machine}_{linkage}"
53+
54+
log.debug("System Info:")
55+
log.debug(f"Architecture: ({(bits, linkage)})")
56+
log.debug(f"Machine: {machine}")
57+
log.debug(f"Node: {platform.node()}")
58+
log.debug(f"Platform: {platform.platform()}")
59+
log.debug(f"System: {platform.system()}")
60+
log.debug(f"Release: {platform.release()}")
61+
log.debug(f"using binary: '{lt_executable}'")
62+
63+
lt_path = RUNS_PATH / lt_executable
64+
if not lt_path.is_file():
65+
log.error(f"file '{lt_executable}' does not exist!")
66+
return None
67+
return lt_path
68+
69+
3570
def on_connect(client: mqtt.Client, userdata, flags: dict, rc: int):
3671
"""connect to broker and subscribe to set topics"""
3772
log.info("Connected")
38-
client.subscribe(BASE_TOPIC + "#", 2)
73+
client.subscribe([
74+
(REMOTE_SUPPORT_TOPIC, 2),
75+
(CLOUD_TOPIC, 2),
76+
(REMOTE_PARTNER_TOPIC, 2),
77+
(REMOTE_PARTNER_IDS_TOPIC, 2)
78+
])
79+
publish_as_json(client, API_TOPIC, API_VERSION, qos=2, retain=True)
80+
publish_as_json(client, STATE_TOPIC, "online", qos=2, retain=True)
3981

4082

4183
def on_message(client: mqtt.Client, userdata, msg: mqtt.MQTTMessage):
@@ -56,6 +98,8 @@ def is_tunnel_closed(tunnel: Popen) -> bool:
5698
global support_tunnel
5799
global partner_tunnel
58100
global cloud_tunnel
101+
global valid_partner_ids
102+
clear_topic = False
59103
payload = msg.payload.decode("utf-8")
60104
if len(payload) > 0:
61105
log.debug("Topic: %s, Message: %s", msg.topic, payload)
@@ -81,6 +125,9 @@ def is_tunnel_closed(tunnel: Popen) -> bool:
81125
log.info(f"tunnel running with pid {support_tunnel.pid}")
82126
else:
83127
log.info("unknown message: " + payload)
128+
clear_topic = True
129+
elif msg.topic == REMOTE_PARTNER_IDS_TOPIC:
130+
valid_partner_ids = json.loads(payload)
84131
elif msg.topic == REMOTE_PARTNER_TOPIC:
85132
if payload == 'stop':
86133
if partner_tunnel is None:
@@ -90,22 +137,39 @@ def is_tunnel_closed(tunnel: Popen) -> bool:
90137
partner_tunnel.terminate()
91138
partner_tunnel.wait(timeout=3)
92139
partner_tunnel = None
93-
elif re.match(r'^([^;]+)(?:;([1-9][0-9]+)(?:;([a-zA-Z0-9]+))?)?$', payload):
140+
elif re.match(r'^([^;]+)(?:;((?:cnode)?[0-9]+)(?:;([\wäöüÄÖÜ-]+))?)?$', payload):
94141
if is_tunnel_closed(partner_tunnel):
95142
splitted = payload.split(";")
96143
if len(splitted) != 3:
97144
log.error("invalid number of settings received!")
98145
else:
99146
token = splitted[0]
100-
port = splitted[1]
101-
user = splitted[2]
102-
log.info("start partner support")
103-
partner_tunnel = Popen(["sshpass", "-p", token, "ssh", "-N", "-tt", "-o",
104-
"StrictHostKeyChecking=no", "-o", "ServerAliveInterval 60", "-R",
105-
f"{port}:localhost:80", f"{user}@partner.openwb.de"])
106-
log.info(f"tunnel running with pid {partner_tunnel.pid}")
147+
port_or_node = splitted[1]
148+
user = splitted[2] # not used in v0, partner-id in v1
149+
if port_or_node.isdecimal():
150+
# v0
151+
log.info("start partner support")
152+
partner_tunnel = Popen(["sshpass", "-p", token, "ssh", "-N", "-tt", "-o",
153+
"StrictHostKeyChecking=no", "-o", "ServerAliveInterval 60", "-R",
154+
f"{port_or_node}:localhost:80", f"{user}@partner.openwb.de"])
155+
log.info(f"tunnel running with pid {partner_tunnel.pid}")
156+
else:
157+
# v1
158+
if lt_executable is None:
159+
log.error("start partner tunnel requested but lt executable not found!")
160+
else:
161+
if user in valid_partner_ids:
162+
log.info("start partner support v1")
163+
if lt_executable is not None:
164+
partner_tunnel = Popen([f"{lt_executable}", "-h",
165+
"https://" + port_or_node + ".openwb.de/",
166+
"-p", "80", "-s", token])
167+
log.info(f"tunnel running with pid {partner_tunnel.pid}")
168+
else:
169+
log.error(f"invalid partner-id: {user}")
107170
else:
108171
log.info("unknown message: " + payload)
172+
clear_topic = True
109173
elif msg.topic == CLOUD_TOPIC:
110174
if payload == 'stop':
111175
if cloud_tunnel is None:
@@ -125,37 +189,44 @@ def is_tunnel_closed(tunnel: Popen) -> bool:
125189
cloud_node = splitted[1]
126190
user = splitted[2]
127191

128-
machine = platform.machine()
129-
bits, linkage = platform.architecture()
130-
lt_executable = f"lt-{machine}_{linkage}"
131-
132-
log.debug("System Info:")
133-
log.debug(f"Architecture: ({(bits, linkage)})")
134-
log.debug(f"Machine: {machine}")
135-
log.debug(f"Node: {platform.node()}")
136-
log.debug(f"Platform: {platform.platform()}")
137-
log.debug(f"System: {platform.system()}")
138-
log.debug(f"Release: {platform.release()}")
139-
log.debug(f"using binary: '{lt_executable}'")
140-
141-
log.info(f"start cloud tunnel '{token[:4]}...{token[-4:]}' on '{cloud_node}'")
142-
try:
143-
cloud_tunnel = Popen([f"{RUNS_PATH}/{lt_executable}", "-h",
192+
if lt_executable is None:
193+
log.error("start cloud tunnel requested but lt executable not found!")
194+
else:
195+
log.info(f"start cloud tunnel '{token[:4]}...{token[-4:]}' on '{cloud_node}'")
196+
cloud_tunnel = Popen([f"{lt_executable}", "-h",
144197
"https://" + cloud_node + ".openwb.de/", "-p", "80", "-s", token])
145198
log.info(f"cloud tunnel running with pid {cloud_tunnel.pid}")
146-
except FileNotFoundError:
147-
log.exception(f"executable '{lt_executable}' does not exist!")
148199
else:
149200
log.info("unknown message: " + payload)
201+
clear_topic = True
150202
# clear topic
151-
client.publish(msg.topic, "", qos=2, retain=True)
203+
if clear_topic and msg.retain:
204+
client.publish(msg.topic, "", qos=2, retain=True)
152205

153206

207+
lt_executable = get_lt_executable()
154208
mqtt_broker_host = "localhost"
155209
client = mqtt.Client("openWB-remote-" + get_serial())
156210
client.on_connect = on_connect
157211
client.on_message = on_message
212+
client.will_set(STATE_TOPIC, json.dumps("offline"), qos=2, retain=True)
158213

214+
log.debug("connecting to broker")
159215
client.connect(mqtt_broker_host, 1883)
160-
client.loop_forever()
161-
client.disconnect()
216+
log.debug("starting loop")
217+
client.loop_start()
218+
try:
219+
while True:
220+
sleep(1)
221+
except (Exception, KeyboardInterrupt) as e:
222+
log.debug(e)
223+
log.debug("terminated")
224+
finally:
225+
log.debug("publishing state 'offline'")
226+
publish_as_json(client, STATE_TOPIC, "offline", qos=2, retain=True)
227+
sleep(0.5)
228+
log.debug("stopping loop")
229+
client.loop_stop()
230+
client.disconnect()
231+
log.debug("disconnected")
232+
log.debug("exit")

runs/save_mqtt.php

+3
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ function cleanAndExit($message)
178178
<<<EOS
179179
topic openWB/system/time out 2 "" {$configuration->remote->prefix}
180180
topic openWB-remote/support both 2 "" {$configuration->remote->prefix}
181+
topic openWB-remote/api_version out 2 "" {$configuration->remote->prefix}
182+
topic openWB-remote/connection_state out 2 "" {$configuration->remote->prefix}
181183
182184
EOS
183185
);
@@ -199,6 +201,7 @@ function cleanAndExit($message)
199201
$configFile,
200202
<<<EOS
201203
topic openWB-remote/partner both 2 "" {$configuration->remote->prefix}
204+
topic openWB-remote/valid_partner_ids out 2 "" {$configuration->remote->prefix}
202205
203206
EOS
204207
);

0 commit comments

Comments
 (0)