9 Commits

Author SHA1 Message Date
926a71e6a8 converters
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-11-12 12:19:00 +01:00
20a064dc1f converters
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-11-12 12:17:49 +01:00
8417454f5b converters 2024-11-12 12:06:45 +01:00
ee0efb6c19 converters
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-11-12 12:01:13 +01:00
dbdd24822e fix
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-11-07 22:15:20 +01:00
78a68f9009 fix
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-11-07 22:10:39 +01:00
fbb9aa6665 fix
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-11-07 22:05:13 +01:00
51995fc489 status text output
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-11-07 20:02:03 +01:00
e6b4733a60 overwrite window added
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-11-07 19:22:41 +01:00
3 changed files with 79 additions and 36 deletions

View File

@ -10,6 +10,7 @@ data:
MQTT_BOX_TOPIC_PREFIXES: | MQTT_BOX_TOPIC_PREFIXES: |
{ {
"high_temp": "heating/config/high_temp/", "high_temp": "heating/config/high_temp/",
"overwrite_window": "heating/overwrite_window/",
"cmd": "heating/command/" "cmd": "heating/command/"
} }
MQTT_CENTRAL_TOPICS: | MQTT_CENTRAL_TOPICS: |
@ -19,6 +20,7 @@ data:
"status": "heating/system/status" "status": "heating/system/status"
} }
MQTT_STATUS_TOPIC: "heating/status" MQTT_STATUS_TOPIC: "heating/status"
MQTT_CONTEXT_TOPIC_PREFIX: "heating/context/"
OFF_TEMPERATURE: "5.0" OFF_TEMPERATURE: "5.0"
LOW_TEMPERATURE: "15.0" LOW_TEMPERATURE: "15.0"
DEFAULT_HIGH_TEMPERATURE: "21.0" DEFAULT_HIGH_TEMPERATURE: "21.0"
@ -28,63 +30,71 @@ data:
"patty": { "patty": {
"label": "patty", "label": "patty",
"windows": [ "windows": [
{ "topic": "homegear/instance1/plain/18/1/STATE", "label": "Garten rechts" }, { "topic": "homegear/instance1/plain/18/1/STATE", "label": "Garten rechts", "converter": "max" },
{ "topic": "homegear/instance1/plain/22/1/STATE", "label": "Garten links" } { "topic": "homegear/instance1/plain/22/1/STATE", "label": "Garten links", "converter": "max" }
], ],
"output_topic": "heating/homegear/instance1/set/39/1/SET_TEMPERATURE" "output_topic": "heating/homegear/instance1/set/39/1/SET_TEMPERATURE",
"output_converter": "max"
}, },
"kueche": { "kueche": {
"label": "kueche", "label": "kueche",
"windows": [ "windows": [
{ "topic": "homegear/instance1/plain/37/1/STATE", "label": "Garten Fenster" }, { "topic": "homegear/instance1/plain/37/1/STATE", "label": "Garten Fenster", "converter": "max" },
{ "topic": "homegear/instance1/plain/36/1/STATE", "label": "Garten Tuer" }, { "topic": "homegear/instance1/plain/36/1/STATE", "label": "Garten Tuer", "converter": "max" },
{ "topic": "homegear/instance1/plain/38/1/STATE", "label": "Strasse rechts" }, { "topic": "homegear/instance1/plain/38/1/STATE", "label": "Strasse rechts", "converter": "max" },
{ "topic": "homegear/instance1/plain/13/1/STATE", "label": "Strasse links" } { "topic": "homegear/instance1/plain/13/1/STATE", "label": "Strasse links", "converter": "max" }
], ],
"output_topic": "heating/homegear/instance1/set/40/1/SET_TEMPERATURE" "output_topic": "heating/homegear/instance1/set/40/1/SET_TEMPERATURE",
"output_converter": "max"
}, },
"bad_oben": { "bad_oben": {
"label": "bad_oben", "label": "bad_oben",
"windows": [ "windows": [
], ],
"output_topic": "heating/homegear/instance1/set/41/1/SET_TEMPERATURE" "output_topic": "heating/homegear/instance1/set/41/1/SET_TEMPERATURE",
"output_converter": "max"
}, },
"schlafzimmer": { "schlafzimmer": {
"label": "schlafzimmer", "label": "schlafzimmer",
"windows": [ "windows": [
{ "topic": "homegear/instance1/plain/52/1/STATE", "label": "Strasse" } { "topic": "homegear/instance1/plain/52/1/STATE", "label": "Strasse", "converter": "max" }
], ],
"output_topic": "heating/homegear/instance1/set/42/1/SET_TEMPERATURE" "output_topic": "heating/homegear/instance1/set/42/1/SET_TEMPERATURE",
"output_converter": "max"
}, },
"wolfgang": { "wolfgang": {
"label": "wolfgang", "label": "wolfgang",
"windows": [ "windows": [
{ "topic": "homegear/instance1/plain/24/1/STATE", "label": "Garten" } { "topic": "homegear/instance1/plain/24/1/STATE", "label": "Garten", "converter": "max" }
], ],
"output_topic": "heating/homegear/instance1/set/43/1/SET_TEMPERATURE" "output_topic": "heating/homegear/instance1/set/43/1/SET_TEMPERATURE",
"output_converter": "max"
}, },
"esszimmer": { "esszimmer": {
"label": "esszimmer", "label": "esszimmer",
"windows": [ "windows": [
{ "topic": "homegear/instance1/plain/26/1/STATE", "label": "Strasse rechts" }, { "topic": "homegear/instance1/plain/26/1/STATE", "label": "Strasse rechts", "converter": "max" },
{ "topic": "homegear/instance1/plain/27/1/STATE", "label": "Strasse links" } { "topic": "homegear/instance1/plain/27/1/STATE", "label": "Strasse links", "converter": "max" }
], ],
"output_topic": "heating/homegear/instance1/set/45/1/SET_TEMPERATURE" "output_topic": "heating/homegear/instance1/set/45/1/SET_TEMPERATURE",
"output_converter": "max"
}, },
"wohnzimmer": { "wohnzimmer": {
"label": "wohnzimmer", "label": "wohnzimmer",
"windows": [ "windows": [
{ "topic": "homegear/instance1/plain/28/1/STATE", "label": "Garten rechts" }, { "topic": "homegear/instance1/plain/28/1/STATE", "label": "Garten rechts", "converter": "max" },
{ "topic": "homegear/instance1/plain/29/1/STATE", "label": "Garten links" } { "topic": "homegear/instance1/plain/29/1/STATE", "label": "Garten links", "converter": "max" }
], ],
"output_topic": "heating/homegear/instance1/set/46/1/SET_TEMPERATURE" "output_topic": "heating/homegear/instance1/set/46/1/SET_TEMPERATURE",
"output_converter": "max"
}, },
"bad_unten": { "bad_unten": {
"label": "bad_unten", "label": "bad_unten",
"windows": [ "windows": [
{ "topic": "homegear/instance1/plain/44/1/STATE", "label": "Strasse" } { "topic": "homegear/instance1/plain/44/1/STATE", "label": "Strasse", "converter": "max" }
], ],
"output_topic": "heating/homegear/instance1/set/48/1/SET_TEMPERATURE" "output_topic": "heating/homegear/instance1/set/48/1/SET_TEMPERATURE",
"output_converter": "max"
} }
} }

View File

@ -19,6 +19,7 @@ LOW_TEMPERATURE = os.getenv("LOW_TEMPERATURE", "15.0")
DEFAULT_HIGH_TEMPERATURE = os.getenv("DEFAULT_HIGH_TEMPERATURE", "21.0") DEFAULT_HIGH_TEMPERATURE = os.getenv("DEFAULT_HIGH_TEMPERATURE", "21.0")
MAINTENANCE_TEMPERATURE = os.getenv("MAINTENANCE_TEMPERATURE", "30.0") MAINTENANCE_TEMPERATURE = os.getenv("MAINTENANCE_TEMPERATURE", "30.0")
STATUS_TOPIC = os.getenv("MQTT_STATUS_TOPIC") STATUS_TOPIC = os.getenv("MQTT_STATUS_TOPIC")
CONTEXT_TOPIC_PREFIX = os.getenv("MQTT_CONTEXT_TOPIC_PREFIX")
# Check if required environment variables are set # Check if required environment variables are set
missing_vars = [] missing_vars = []
@ -32,6 +33,8 @@ if not CENTRAL_TOPICS_CONFIG:
missing_vars.append('MQTT_CENTRAL_TOPICS') missing_vars.append('MQTT_CENTRAL_TOPICS')
if not STATUS_TOPIC: if not STATUS_TOPIC:
missing_vars.append('MQTT_STATUS_TOPIC') missing_vars.append('MQTT_STATUS_TOPIC')
if not CONTEXT_TOPIC_PREFIX:
missing_vars.append('MQTT_CONTEXT_TOPIC_PREFIX')
if missing_vars: if missing_vars:
logger.error(f"Error: The following environment variables are not set: {', '.join(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['default_high_temperature'] = DEFAULT_HIGH_TEMPERATURE
context['maintenance_temperature'] = MAINTENANCE_TEMPERATURE context['maintenance_temperature'] = MAINTENANCE_TEMPERATURE
context['status_topic'] = STATUS_TOPIC context['status_topic'] = STATUS_TOPIC
context['context_topic_prefix'] = CONTEXT_TOPIC_PREFIX
# Load box configurations from JSON # Load box configurations from JSON
try: try:
@ -66,7 +70,7 @@ try:
box_topic_prefixes = json.loads(BOX_TOPIC_PREFIXES_CONFIG) box_topic_prefixes = json.loads(BOX_TOPIC_PREFIXES_CONFIG)
# Validation: Check if the required keys are present # 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() missing_keys = required_keys - box_topic_prefixes.keys()
if missing_keys: if missing_keys:

View File

@ -1,6 +1,17 @@
from loguru import logger from loguru import logger
import json
CONVERTERS = {
"target_temperature_output": {
"max": lambda x: x["output_temperature"],
},
"window_contact_input": {
"max": lambda x: 'closed' if (x.lower() in ('false', 'close', 'closed')) else 'open'
}
}
# context # context
# boxes: structure of boxes # boxes: structure of boxes
# client: MQTT client # client: MQTT client
@ -20,6 +31,7 @@ def prepare_context(box_name, context):
local_context['general_off'] = False local_context['general_off'] = False
local_context['maintenance_mode'] = False local_context['maintenance_mode'] = False
local_context['overwrite_window'] = False
local_context['window_state'] = {} local_context['window_state'] = {}
for w in context['boxes'][box_name]['windows']: for w in context['boxes'][box_name]['windows']:
@ -45,6 +57,8 @@ def process_message(box_name, topic_key, payload, context):
result = process_high_temp(box_name, context, local_context, payload) result = process_high_temp(box_name, context, local_context, payload)
case [ primary_key ] if primary_key == 'cmd': case [ primary_key ] if primary_key == 'cmd':
result = process_cmd(box_name, context, local_context, payload) 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': case [ primary_key ] if primary_key == 'general_off':
result = process_general_off(box_name, context, local_context, payload) result = process_general_off(box_name, context, local_context, payload)
case [ primary_key ] if primary_key == 'maintenance_mode': case [ primary_key ] if primary_key == 'maintenance_mode':
@ -55,10 +69,13 @@ def process_message(box_name, topic_key, payload, context):
raise Error(f"Unexcepted topic_key: {topic_key}, {payload}") raise Error(f"Unexcepted topic_key: {topic_key}, {payload}")
if result: if result:
(result_message, status) = result result_message = CONVERTERS["target_temperature_output"][box["output_converter"]](local_context)
publish_topic = box["output_topic"] if not status else context['status_topic'] publish_topic = box["output_topic"]
context['client'].publish(publish_topic, result_message) context['client'].publish(publish_topic, result_message)
logger.info(f"[{box_name}] Result published on '{publish_topic}': {status} {result_message}") logger.info(f"[{box_name}] Result published on '{publish_topic}': {result_message}")
context_topic = f"{context['context_topic_prefix']}{box['label']}"
context['client'].publish(context_topic, json.dumps(local_context))
logger.info(f"[{box_name}] Local context after: {local_context}") logger.info(f"[{box_name}] Local context after: {local_context}")
except Exception as e: except Exception as e:
@ -74,10 +91,11 @@ def _calculate_output_temperature(local_context):
local_context['output_temperature'] = local_context['off_temperature'] local_context['output_temperature'] = local_context['off_temperature']
return return
# an open window shuts off the heating # an open window shuts off the heating
for w in local_context['window_state'].values(): if not local_context['overwrite_window']:
if w == 'open': for w in local_context['window_state'].values():
local_context['output_temperature'] = local_context['off_temperature'] if w == 'open':
return local_context['output_temperature'] = local_context['off_temperature']
return
# finally evaluate the mode # finally evaluate the mode
if local_context['mode'] == 'off': if local_context['mode'] == 'off':
local_context['output_temperature'] = local_context['off_temperature'] local_context['output_temperature'] = local_context['off_temperature']
@ -93,17 +111,17 @@ def _calculate_output_temperature(local_context):
return return
def process_status(box_name, context, local_context, payload): def process_status(box_name, context, local_context, payload):
return (f"{local_context}", True) return False
def process_general_off(box_name, context, local_context, payload): def process_general_off(box_name, context, local_context, payload):
local_context['general_off'] = (payload.lower() in ('true')) local_context['general_off'] = (payload.lower() in ('true'))
_calculate_output_temperature(local_context) _calculate_output_temperature(local_context)
return (local_context['output_temperature'], False) return True
def process_maintenance_mode(box_name, context, local_context, payload): def process_maintenance_mode(box_name, context, local_context, payload):
local_context['maintenance_mode'] = (payload.lower() in ('true')) local_context['maintenance_mode'] = (payload.lower() in ('true'))
_calculate_output_temperature(local_context) _calculate_output_temperature(local_context)
return (local_context['output_temperature'], False) return True
def process_cmd(box_name, context, local_context, payload): def process_cmd(box_name, context, local_context, payload):
if payload.lower() in ('high', 'low', 'off'): if payload.lower() in ('high', 'low', 'off'):
@ -111,15 +129,26 @@ def process_cmd(box_name, context, local_context, payload):
_calculate_output_temperature(local_context) _calculate_output_temperature(local_context)
else: else:
logger.error(f"Invalid cmd for {box_name} received: {payload}") logger.error(f"Invalid cmd for {box_name} received: {payload}")
return (local_context['output_temperature'], False) return True
def process_overwrite_window(box_name, context, local_context, payload):
local_context['overwrite_window'] = (payload.lower() in ('true'))
_calculate_output_temperature(local_context)
return True
def process_high_temp(box_name, context, local_context, payload): def process_high_temp(box_name, context, local_context, payload):
local_context['high_temperature'] = payload local_context['high_temperature'] = payload
_calculate_output_temperature(local_context) _calculate_output_temperature(local_context)
return (local_context['output_temperature'], False) return True
def process_window(box_name, context, local_context, sub_key, payload): def process_window(box_name, context, local_context, sub_key, payload):
local_context['window_state'][sub_key] = 'closed' if (payload.lower() in ('false', 'close', 'closed')) else 'open' # default converter
converter = lambda x:x
for sk in box["windows"]:
if sk["label"] == sub_key:
converter = CONVERTERS["window_contact_input"][sk["converter"]]
break
local_context['window_state'][sub_key] = converter(payload)
_calculate_output_temperature(local_context) _calculate_output_temperature(local_context)
return (local_context['output_temperature'], False) return True