Skip to content

Commit 7480d8e

Browse files
committed
standalone: rework configuration to support api v3
1 parent 9c45ffe commit 7480d8e

File tree

3 files changed

+190
-54
lines changed

3 files changed

+190
-54
lines changed

src/enapter/mqtt/config.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def from_env(
4848
port=int(env[prefix + "PORT"]),
4949
user=env.get(prefix + "USER", default=None),
5050
password=env.get(prefix + "PASSWORD", default=None),
51-
tls=TLSConfig.from_env(env, namespace=namespace),
51+
tls_config=TLSConfig.from_env(env, namespace=namespace),
5252
)
5353

5454
def __init__(
@@ -57,13 +57,17 @@ def __init__(
5757
port: int,
5858
user: Optional[str] = None,
5959
password: Optional[str] = None,
60-
tls: Optional[TLSConfig] = None,
60+
tls_config: Optional[TLSConfig] = None,
6161
) -> None:
6262
self.host = host
6363
self.port = port
6464
self.user = user
6565
self.password = password
66-
self.tls = tls
66+
self.tls_config = tls_config
67+
68+
@property
69+
def tls(self) -> TLSConfig | None:
70+
return self.tls_config
6771

6872
def __repr__(self) -> str:
6973
return "mqtt.Config(host=%r, port=%r, tls=%r)" % (

src/enapter/standalone/app.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,24 @@ def __init__(
3131

3232
async def _run(self) -> None:
3333
async with asyncio.TaskGroup() as tg:
34-
mqtt_client = mqtt.Client(task_group=tg, config=self._config.mqtt)
34+
mqtt_client = mqtt.Client(
35+
task_group=tg, config=self._config.communication.mqtt
36+
)
3537
_ = DeviceDriver(
3638
task_group=tg,
3739
device_channel=mqtt.api.DeviceChannel(
3840
client=mqtt_client,
39-
hardware_id=self._config.hardware_id,
40-
channel_id=self._config.channel_id,
41+
hardware_id=self._config.communication.hardware_id,
42+
channel_id=self._config.communication.channel_id,
4143
),
4244
device=self._device,
4345
)
44-
if self._config.start_ucm:
46+
if self._config.communication.ucm_needed:
4547
_ = DeviceDriver(
4648
task_group=tg,
4749
device_channel=mqtt.api.DeviceChannel(
4850
client=mqtt_client,
49-
hardware_id=self._config.hardware_id,
51+
hardware_id=self._config.communication.hardware_id,
5052
channel_id="ucm",
5153
),
5254
device=UCM(),

src/enapter/standalone/config.py

Lines changed: 176 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,203 @@
11
import base64
2+
import dataclasses
3+
import enum
24
import json
35
import os
4-
from typing import MutableMapping
6+
from typing import Any, Dict, MutableMapping, Self
57

68
from enapter import mqtt
79

810

11+
@dataclasses.dataclass
912
class Config:
1013

14+
communication_config: "CommunicationConfig"
15+
16+
@property
17+
def communication(self) -> "CommunicationConfig":
18+
return self.communication_config
19+
1120
@classmethod
1221
def from_env(
1322
cls, env: MutableMapping[str, str] = os.environ, namespace: str = "ENAPTER_"
14-
) -> "Config":
15-
prefix = namespace + "STANDALONE_"
23+
) -> Self:
24+
communication_config = CommunicationConfig.from_env(env, namespace=namespace)
25+
return cls(communication_config=communication_config)
1626

17-
try:
18-
blob = env[prefix + "BLOB"]
19-
except KeyError:
20-
pass
21-
else:
22-
config = cls.from_blob(blob)
23-
try:
24-
config.channel_id = env[prefix + "CHANNEL_ID"]
25-
except KeyError:
26-
pass
27-
return config
2827

29-
hardware_id = env[prefix + "HARDWARE_ID"]
30-
channel_id = env[prefix + "CHANNEL_ID"]
28+
@dataclasses.dataclass
29+
class CommunicationConfig:
3130

32-
mqtt_config = mqtt.Config.from_env(env, namespace=namespace)
31+
mqtt_config: mqtt.Config
32+
hardware_id: str
33+
channel_id: str
34+
ucm_needed: bool
3335

34-
start_ucm = env.get(prefix + "START_UCM", "1") != "0"
36+
@property
37+
def mqtt(self) -> mqtt.Config:
38+
return self.mqtt_config
3539

36-
return cls(
37-
hardware_id=hardware_id,
38-
channel_id=channel_id,
39-
mqtt=mqtt_config,
40-
start_ucm=start_ucm,
41-
)
40+
@classmethod
41+
def from_env(
42+
cls, env: MutableMapping[str, str] = os.environ, namespace: str = "ENAPTER_"
43+
) -> Self:
44+
prefix = namespace + "STANDALONE_COMMUNICATION_"
45+
blob = env[prefix + "CONFIG"]
46+
return cls.from_blob(blob)
4247

4348
@classmethod
44-
def from_blob(cls, blob: str) -> "Config":
45-
payload = json.loads(base64.b64decode(blob))
49+
def from_blob(cls, blob: str) -> Self:
50+
dto = json.loads(base64.b64decode(blob))
51+
if "ucm_id" in dto:
52+
config_v1 = CommunicationConfigV1.from_dto(dto)
53+
return cls.from_config_v1(config_v1)
54+
else:
55+
config_v3 = CommunicationConfigV3.from_dto(dto)
56+
return cls.from_config_v3(config_v3)
4657

58+
@classmethod
59+
def from_config_v1(cls, config: "CommunicationConfigV1") -> Self:
4760
mqtt_config = mqtt.Config(
48-
host=payload["mqtt_host"],
49-
port=int(payload["mqtt_port"]),
50-
tls=mqtt.TLSConfig(
51-
ca_cert=payload["mqtt_ca"],
52-
cert=payload["mqtt_cert"],
53-
secret_key=payload["mqtt_private_key"],
61+
host=config.mqtt_host,
62+
port=config.mqtt_port,
63+
tls_config=mqtt.TLSConfig(
64+
secret_key=config.mqtt_private_key,
65+
cert=config.mqtt_cert,
66+
ca_cert=config.mqtt_ca,
5467
),
5568
)
69+
return cls(
70+
mqtt_config=mqtt_config,
71+
hardware_id=config.ucm_id,
72+
channel_id=config.channel_id,
73+
ucm_needed=True,
74+
)
75+
76+
@classmethod
77+
def from_config_v3(cls, config: "CommunicationConfigV3") -> Self:
78+
mqtt_config: mqtt.Config | None = None
79+
match config.mqtt_protocol:
80+
case CommunicationConfigV3.MQTTProtocol.MQTT:
81+
assert isinstance(
82+
config.mqtt_credentials, CommunicationConfigV3.MQTTCredentials
83+
)
84+
mqtt_config = mqtt.Config(
85+
host=config.mqtt_host,
86+
port=config.mqtt_port,
87+
user=config.mqtt_credentials.username,
88+
password=config.mqtt_credentials.password,
89+
)
90+
case CommunicationConfigV3.MQTTProtocol.MQTTS:
91+
assert isinstance(
92+
config.mqtt_credentials, CommunicationConfigV3.MQTTSCredentials
93+
)
94+
mqtt_config = mqtt.Config(
95+
host=config.mqtt_host,
96+
port=config.mqtt_port,
97+
tls_config=mqtt.TLSConfig(
98+
secret_key=config.mqtt_credentials.private_key,
99+
cert=config.mqtt_credentials.certificate,
100+
ca_cert=config.mqtt_credentials.ca_chain,
101+
),
102+
)
103+
case _:
104+
raise NotImplementedError(config.mqtt_protocol)
105+
assert mqtt_config is not None
106+
return cls(
107+
mqtt_config=mqtt_config,
108+
hardware_id=config.hardware_id,
109+
channel_id=config.channel_id,
110+
ucm_needed=False,
111+
)
112+
56113

114+
@dataclasses.dataclass
115+
class CommunicationConfigV1:
116+
117+
mqtt_host: str
118+
mqtt_port: int
119+
mqtt_ca: str
120+
mqtt_cert: str
121+
mqtt_private_key: str
122+
ucm_id: str
123+
channel_id: str
124+
125+
@classmethod
126+
def from_dto(cls, dto: Dict[str, Any]) -> Self:
57127
return cls(
58-
hardware_id=payload["ucm_id"],
59-
channel_id=payload["channel_id"],
60-
mqtt=mqtt_config,
128+
mqtt_host=dto["mqtt_host"],
129+
mqtt_port=int(dto["mqtt_port"]),
130+
mqtt_ca=dto["mqtt_ca"],
131+
mqtt_cert=dto["mqtt_cert"],
132+
mqtt_private_key=dto["mqtt_private_key"],
133+
ucm_id=dto["ucm_id"],
134+
channel_id=dto["channel_id"],
61135
)
62136

63-
def __init__(
64-
self,
65-
hardware_id: str,
66-
channel_id: str,
67-
mqtt: mqtt.Config,
68-
start_ucm: bool = True,
69-
) -> None:
70-
self.hardware_id = hardware_id
71-
self.channel_id = channel_id
72-
self.mqtt = mqtt
73-
self.start_ucm = start_ucm
137+
138+
@dataclasses.dataclass
139+
class CommunicationConfigV3:
140+
141+
class MQTTProtocol(enum.Enum):
142+
143+
MQTT = "MQTT"
144+
MQTTS = "MQTTS"
145+
146+
@dataclasses.dataclass
147+
class MQTTCredentials:
148+
149+
username: str
150+
password: str
151+
152+
@classmethod
153+
def from_dto(cls, dto: Dict[str, Any]) -> Self:
154+
return cls(username=dto["username"], password=dto["password"])
155+
156+
@dataclasses.dataclass
157+
class MQTTSCredentials:
158+
159+
private_key: str
160+
certificate: str
161+
ca_chain: str
162+
163+
@classmethod
164+
def from_dto(cls, dto: Dict[str, Any]) -> Self:
165+
return cls(
166+
private_key=dto["private_key"],
167+
certificate=dto["certificate"],
168+
ca_chain=dto["ca_chain"],
169+
)
170+
171+
mqtt_host: str
172+
mqtt_port: int
173+
mqtt_protocol: MQTTProtocol
174+
mqtt_credentials: MQTTCredentials | MQTTSCredentials
175+
hardware_id: str
176+
channel_id: str
177+
178+
@classmethod
179+
def from_dto(cls, dto: Dict[str, Any]) -> Self:
180+
mqtt_protocol = cls.MQTTProtocol(dto["mqtt_protocol"])
181+
mqtt_credentials: (
182+
CommunicationConfigV3.MQTTCredentials
183+
| CommunicationConfigV3.MQTTSCredentials
184+
| None
185+
) = None
186+
match mqtt_protocol:
187+
case cls.MQTTProtocol.MQTT:
188+
mqtt_credentials = cls.MQTTCredentials.from_dto(dto["mqtt_credentials"])
189+
case cls.MQTTProtocol.MQTTS:
190+
mqtt_credentials = cls.MQTTSCredentials.from_dto(
191+
dto["mqtt_credentials"]
192+
)
193+
case _:
194+
raise NotImplementedError(mqtt_protocol)
195+
assert mqtt_credentials is not None
196+
return cls(
197+
mqtt_host=dto["mqtt_host"],
198+
mqtt_port=int(dto["mqtt_port"]),
199+
mqtt_credentials=mqtt_credentials,
200+
mqtt_protocol=mqtt_protocol,
201+
hardware_id=dto["hardware_id"],
202+
channel_id=dto["channel_id"],
203+
)

0 commit comments

Comments
 (0)