Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
78a68f9009
|
|||
fbb9aa6665
|
|||
51995fc489
|
|||
e6b4733a60
|
|||
62ce6f1b9c
|
|||
adcc5a86f8
|
|||
39adf907b1
|
|||
7abec12620
|
|||
046430d1d1
|
@ -10,6 +10,7 @@ data:
|
||||
MQTT_BOX_TOPIC_PREFIXES: |
|
||||
{
|
||||
"high_temp": "heating/config/high_temp/",
|
||||
"overwrite_window": "heating/overwrite_window/",
|
||||
"cmd": "heating/command/"
|
||||
}
|
||||
MQTT_CENTRAL_TOPICS: |
|
||||
@ -19,28 +20,73 @@ data:
|
||||
"status": "heating/system/status"
|
||||
}
|
||||
MQTT_STATUS_TOPIC: "heating/status"
|
||||
MQTT_STATUSTEXT_TOPIC_PREFIX: "heating/statustext/"
|
||||
OFF_TEMPERATURE: "5.0"
|
||||
LOW_TEMPERATURE: "15.0"
|
||||
DEFAULT_HIGH_TEMPERATURE: "21.0"
|
||||
MAINTENANCE_TEMPERATURE: "30.0"
|
||||
BOXES: |
|
||||
{
|
||||
"box1": {
|
||||
"label": "living_room",
|
||||
"patty": {
|
||||
"label": "patty",
|
||||
"windows": [
|
||||
{ "topic": "window/living_room/street_side", "label": "street_side" },
|
||||
{ "topic": "window/living_room/garden_side", "label": "garden_side" }
|
||||
{ "topic": "homegear/instance1/plain/18/1/STATE", "label": "Garten rechts" },
|
||||
{ "topic": "homegear/instance1/plain/22/1/STATE", "label": "Garten links" }
|
||||
],
|
||||
"output_topic": "output/living_room"
|
||||
"output_topic": "heating/homegear/instance1/set/39/1/SET_TEMPERATURE"
|
||||
},
|
||||
"box2": {
|
||||
"label": "kitchen",
|
||||
"kueche": {
|
||||
"label": "kueche",
|
||||
"windows": [
|
||||
{ "topic": "window/kitchen/street_side", "label": "street_side" },
|
||||
{ "topic": "window/kitchen/garden_side", "label": "garden_side" },
|
||||
{ "topic": "window/kitchen/garden_door", "label": "garden_door" }
|
||||
{ "topic": "homegear/instance1/plain/37/1/STATE", "label": "Garten Fenster" },
|
||||
{ "topic": "homegear/instance1/plain/36/1/STATE", "label": "Garten Tuer" },
|
||||
{ "topic": "homegear/instance1/plain/38/1/STATE", "label": "Strasse rechts" },
|
||||
{ "topic": "homegear/instance1/plain/13/1/STATE", "label": "Strasse links" }
|
||||
],
|
||||
"output_topic": "output/kitchen"
|
||||
"output_topic": "heating/homegear/instance1/set/40/1/SET_TEMPERATURE"
|
||||
},
|
||||
"bad_oben": {
|
||||
"label": "bad_oben",
|
||||
"windows": [
|
||||
],
|
||||
"output_topic": "heating/homegear/instance1/set/41/1/SET_TEMPERATURE"
|
||||
},
|
||||
"schlafzimmer": {
|
||||
"label": "schlafzimmer",
|
||||
"windows": [
|
||||
{ "topic": "homegear/instance1/plain/52/1/STATE", "label": "Strasse" }
|
||||
],
|
||||
"output_topic": "heating/homegear/instance1/set/42/1/SET_TEMPERATURE"
|
||||
},
|
||||
"wolfgang": {
|
||||
"label": "wolfgang",
|
||||
"windows": [
|
||||
{ "topic": "homegear/instance1/plain/24/1/STATE", "label": "Garten" }
|
||||
],
|
||||
"output_topic": "heating/homegear/instance1/set/43/1/SET_TEMPERATURE"
|
||||
},
|
||||
"esszimmer": {
|
||||
"label": "esszimmer",
|
||||
"windows": [
|
||||
{ "topic": "homegear/instance1/plain/26/1/STATE", "label": "Strasse rechts" },
|
||||
{ "topic": "homegear/instance1/plain/27/1/STATE", "label": "Strasse links" }
|
||||
],
|
||||
"output_topic": "heating/homegear/instance1/set/45/1/SET_TEMPERATURE"
|
||||
},
|
||||
"wohnzimmer": {
|
||||
"label": "wohnzimmer",
|
||||
"windows": [
|
||||
{ "topic": "homegear/instance1/plain/28/1/STATE", "label": "Garten rechts" },
|
||||
{ "topic": "homegear/instance1/plain/29/1/STATE", "label": "Garten links" }
|
||||
],
|
||||
"output_topic": "heating/homegear/instance1/set/46/1/SET_TEMPERATURE"
|
||||
},
|
||||
"bad_unten": {
|
||||
"label": "bad_unten",
|
||||
"windows": [
|
||||
{ "topic": "homegear/instance1/plain/44/1/STATE", "label": "Strasse" }
|
||||
],
|
||||
"output_topic": "heating/homegear/instance1/set/48/1/SET_TEMPERATURE"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ kind: Deployment
|
||||
metadata:
|
||||
name: heating-controller
|
||||
annotations:
|
||||
secret.reloader.stakater.com/reload: "heating-controller-config"
|
||||
configmap.reloader.stakater.com/reload: "heating-controller-config"
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
|
18
src/main.py
18
src/main.py
@ -19,6 +19,7 @@ LOW_TEMPERATURE = os.getenv("LOW_TEMPERATURE", "15.0")
|
||||
DEFAULT_HIGH_TEMPERATURE = os.getenv("DEFAULT_HIGH_TEMPERATURE", "21.0")
|
||||
MAINTENANCE_TEMPERATURE = os.getenv("MAINTENANCE_TEMPERATURE", "30.0")
|
||||
STATUS_TOPIC = os.getenv("MQTT_STATUS_TOPIC")
|
||||
STATUSTEXT_TOPIC_PREFIX = os.getenv("MQTT_STATUSTEXT_TOPIC_PREFIX")
|
||||
|
||||
# Check if required environment variables are set
|
||||
missing_vars = []
|
||||
@ -32,6 +33,8 @@ if not CENTRAL_TOPICS_CONFIG:
|
||||
missing_vars.append('MQTT_CENTRAL_TOPICS')
|
||||
if not STATUS_TOPIC:
|
||||
missing_vars.append('MQTT_STATUS_TOPIC')
|
||||
if not STATUSTEXT_TOPIC_PREFIX:
|
||||
missing_vars.append('MQTT_STATUSTEXT_TOPIC_PREFIX')
|
||||
|
||||
if missing_vars:
|
||||
logger.error(f"Error: The following environment variables are not set: {', '.join(missing_vars)}")
|
||||
@ -46,6 +49,7 @@ context['low_temperature'] = LOW_TEMPERATURE
|
||||
context['default_high_temperature'] = DEFAULT_HIGH_TEMPERATURE
|
||||
context['maintenance_temperature'] = MAINTENANCE_TEMPERATURE
|
||||
context['status_topic'] = STATUS_TOPIC
|
||||
context['statustext_topic_prefix'] = STATUSTEXT_TOPIC_PREFIX
|
||||
|
||||
# Load box configurations from JSON
|
||||
try:
|
||||
@ -66,7 +70,7 @@ try:
|
||||
box_topic_prefixes = json.loads(BOX_TOPIC_PREFIXES_CONFIG)
|
||||
|
||||
# Validation: Check if the required keys are present
|
||||
required_keys = {'high_temp', 'cmd'}
|
||||
required_keys = {'high_temp', 'cmd', 'overwrite_window'}
|
||||
missing_keys = required_keys - box_topic_prefixes.keys()
|
||||
|
||||
if missing_keys:
|
||||
@ -91,8 +95,8 @@ CLIENT_ID = f"{CLIENT_PREFIX}_{uuid.uuid4()}"
|
||||
topic_mapping = {}
|
||||
|
||||
# Callback function for successful connection to the broker
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
def on_connect(client, userdata, flags, reason_code, properties):
|
||||
if reason_code == 0:
|
||||
logger.info("Connected to the broker")
|
||||
|
||||
# Subscribe to dynamically generated topics for each box and create mappings
|
||||
@ -125,7 +129,7 @@ def on_connect(client, userdata, flags, rc):
|
||||
topic_mapping[central_topic] = ("__central__", central_key)
|
||||
logger.info(f"Subscribed to central topic '{central_topic}' (Key: '{central_key}')")
|
||||
else:
|
||||
logger.error(f"Connection error with code {rc}")
|
||||
logger.error(f"Connection error with code {reason_code}")
|
||||
|
||||
# Callback function for received messages
|
||||
def on_message(client, userdata, msg):
|
||||
@ -149,8 +153,8 @@ def on_message(client, userdata, msg):
|
||||
logger.error(f"Error processing message from '{msg.topic}': {e}")
|
||||
|
||||
# Callback function for disconnection
|
||||
def on_disconnect(client, userdata, rc):
|
||||
if rc != 0:
|
||||
def on_disconnect(client, userdata, flags, reason_code, properties):
|
||||
if reason_code != 0:
|
||||
logger.warning("Unexpected disconnection, attempting to reconnect...")
|
||||
else:
|
||||
logger.info("Disconnected from the broker.")
|
||||
@ -161,7 +165,7 @@ def handle_exit_signal(signum, frame):
|
||||
client.disconnect() # Disconnects from the broker and stops loop_forever()
|
||||
|
||||
# Initialize the MQTT client and configure callbacks
|
||||
client = mqtt.Client(client_id=CLIENT_ID)
|
||||
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID, )
|
||||
|
||||
context['client'] = client
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
from loguru import logger
|
||||
import json
|
||||
|
||||
|
||||
# context
|
||||
@ -20,6 +21,7 @@ def prepare_context(box_name, context):
|
||||
|
||||
local_context['general_off'] = False
|
||||
local_context['maintenance_mode'] = False
|
||||
local_context['overwrite_window'] = False
|
||||
|
||||
local_context['window_state'] = {}
|
||||
for w in context['boxes'][box_name]['windows']:
|
||||
@ -35,8 +37,8 @@ def process_message(box_name, topic_key, payload, context):
|
||||
try:
|
||||
box = context['boxes'][box_name]
|
||||
local_context = box['context']
|
||||
logger.info(f"{local_context=}")
|
||||
logger.info(f"[{box_name}, {box['label']}] Processing message for '{topic_key}': {payload}")
|
||||
logger.info(f"[{box_name}] Local context before: {local_context}")
|
||||
logger.info(f"[{box_name}] Processing message for '{topic_key}': {payload}")
|
||||
|
||||
match topic_key.split('/'):
|
||||
case [ primary_key, sub_key ] if primary_key == 'window':
|
||||
@ -45,6 +47,8 @@ def process_message(box_name, topic_key, payload, context):
|
||||
result = process_high_temp(box_name, context, local_context, payload)
|
||||
case [ primary_key ] if primary_key == 'cmd':
|
||||
result = process_cmd(box_name, context, local_context, payload)
|
||||
case [ primary_key ] if primary_key == 'overwrite_window':
|
||||
result = process_overwrite_window(box_name, context, local_context, payload)
|
||||
case [ primary_key ] if primary_key == 'general_off':
|
||||
result = process_general_off(box_name, context, local_context, payload)
|
||||
case [ primary_key ] if primary_key == 'maintenance_mode':
|
||||
@ -59,6 +63,11 @@ def process_message(box_name, topic_key, payload, context):
|
||||
publish_topic = box["output_topic"] if not status else context['status_topic']
|
||||
context['client'].publish(publish_topic, result_message)
|
||||
logger.info(f"[{box_name}] Result published on '{publish_topic}': {status} {result_message}")
|
||||
|
||||
statustext_topic = f"{context['statustext_topic_prefix']}{box['label']}"
|
||||
context['client'].publish(statustext_topic, json.dumps(local_context))
|
||||
|
||||
logger.info(f"[{box_name}] Local context after: {local_context}")
|
||||
except Exception as e:
|
||||
logger.error(f"[{box_name}] Error processing '{topic_key}': {e}")
|
||||
|
||||
@ -72,10 +81,11 @@ def _calculate_output_temperature(local_context):
|
||||
local_context['output_temperature'] = local_context['off_temperature']
|
||||
return
|
||||
# an open window shuts off the heating
|
||||
for w in local_context['window_state'].values():
|
||||
if w == 'open':
|
||||
local_context['output_temperature'] = local_context['off_temperature']
|
||||
return
|
||||
if not local_context['overwrite_window']:
|
||||
for w in local_context['window_state'].values():
|
||||
if w == 'open':
|
||||
local_context['output_temperature'] = local_context['off_temperature']
|
||||
return
|
||||
# finally evaluate the mode
|
||||
if local_context['mode'] == 'off':
|
||||
local_context['output_temperature'] = local_context['off_temperature']
|
||||
@ -111,13 +121,18 @@ def process_cmd(box_name, context, local_context, payload):
|
||||
logger.error(f"Invalid cmd for {box_name} received: {payload}")
|
||||
return (local_context['output_temperature'], False)
|
||||
|
||||
def process_overwrite_window(box_name, context, local_context, payload):
|
||||
local_context['overwrite_window'] = (payload.lower() in ('true'))
|
||||
_calculate_output_temperature(local_context)
|
||||
return (local_context['output_temperature'], False)
|
||||
|
||||
def process_high_temp(box_name, context, local_context, payload):
|
||||
local_context['high_temperature'] = payload
|
||||
_calculate_output_temperature(local_context)
|
||||
return (local_context['output_temperature'], False)
|
||||
|
||||
def process_window(box_name, context, local_context, sub_key, payload):
|
||||
local_context['window_state'][sub_key] = 'closed' if (payload.lower() in ('true', 'close', 'closed')) else 'open'
|
||||
local_context['window_state'][sub_key] = 'closed' if (payload.lower() in ('false', 'close', 'closed')) else 'open'
|
||||
_calculate_output_temperature(local_context)
|
||||
return (local_context['output_temperature'], False)
|
||||
|
||||
|
Reference in New Issue
Block a user