Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion luxtronik/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import logging

from luxtronik.common import get_host_lock
from luxtronik.common import LuxtronikSettings, get_host_lock # noqa: F401
from luxtronik.discover import discover # noqa: F401

from luxtronik.cfi import (
Expand Down
2 changes: 1 addition & 1 deletion luxtronik/cfi/calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Calculations(DataVectorConfig):

def get_firmware_version(self):
"""Get the firmware version as string."""
return "".join([super(Calculations, self).get(i).value for i in range(81, 91)])
return "".join([str(super(Calculations, self).get(i).value) for i in range(81, 91)])

def _get_firmware_version(self):
"""Get the firmware version as string like in previous versions."""
Expand Down
8 changes: 6 additions & 2 deletions luxtronik/cfi/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import struct
import time

from luxtronik.common import get_host_lock
from luxtronik.common import LuxtronikSettings, get_host_lock
from luxtronik.cfi.constants import (
LUXTRONIK_DEFAULT_PORT,
LUXTRONIK_PARAMETERS_WRITE,
Expand Down Expand Up @@ -162,6 +162,9 @@ def _write_and_read(self, parameters, data):
return self._read(data)

def _write(self, parameters):
if not isinstance(parameters, Parameters):
LOGGER.error(f"Only parameters are writable!")
return
for definition, field in parameters.items():
if field.write_pending:
field.write_pending = False
Expand Down Expand Up @@ -281,7 +284,8 @@ def _parse(self, data_vector, raw_data):
next_idx = definition.index + definition.count
if next_idx > raw_len:
# not enough registers
field.raw = None
if not LuxtronikSettings.preserve_last_read_value_on_fail:
field.raw = None
continue
# remove all used indices from the list of undefined indices
for index in range(definition.index, next_idx):
Expand Down
10 changes: 10 additions & 0 deletions luxtronik/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@

from threading import RLock

###############################################################################
# User adjust-able settings class
###############################################################################

class LuxtronikSettings:

# If False, overwrite existing values with None in case of a transmission error.
# Otherwise, leave the previous value unchanged.
preserve_last_read_value_on_fail = True

###############################################################################
# Multi-threading lock mechanism
###############################################################################
Expand Down
61 changes: 49 additions & 12 deletions luxtronik/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def __init__(self, names, writeable=False):
@classmethod
def to_heatpump(cls, value):
"""Converts value into heatpump units."""
if not isinstance(value, int):
return None
return value

@classmethod
Expand Down Expand Up @@ -93,7 +95,7 @@ def __repr__(self):
f"name: {self.name}, "
f"writeable: {self.writeable}, "
f"value: {self.value}, "
f"raw: {self._raw}, "
f"raw: {self.raw}, "
f"write_pending: {self.write_pending}, "
f"class: {self.datatype_class}, "
f"unit: {self.datatype_unit}"
Expand All @@ -115,7 +117,7 @@ def __eq__(self, other):
return False

return (
self.value == other.value
self._raw == other._raw
and self.datatype_class == other.datatype_class
and self.datatype_unit == other.datatype_unit
)
Expand All @@ -124,7 +126,7 @@ def __lt__(self, other):
"""Compares two datatype objects and returns which one contains the lower value"""

return (
self.value < other.value
self._raw < other._raw
and self.datatype_class == other.datatype_class
and self.datatype_unit == other.datatype_unit
)
Expand Down Expand Up @@ -177,7 +179,7 @@ def sanitize_option(cls, option):

@classmethod
def from_heatpump(cls, value):
if value is None:
if not isinstance(value, int):
return None
if value in cls.codes:
return cls.codes.get(value)
Expand Down Expand Up @@ -331,11 +333,16 @@ class Bool(Base):

@classmethod
def from_heatpump(cls, value):
if not isinstance(value, int):
return None
return bool(value)

@classmethod
def to_heatpump(cls, value):
return int(value)
try:
return int(bool(value))
except Exception:
return None


class Frequency(Base):
Expand All @@ -359,10 +366,14 @@ class IPv4Address(Base):

@classmethod
def from_heatpump(cls, value):
if not isinstance(value, int):
return None
return socket.inet_ntoa(struct.pack(">i", value))

@classmethod
def to_heatpump(cls, value):
if not isinstance(value, str):
return None
return struct.unpack(">i", socket.inet_aton(value))[0]


Expand All @@ -373,14 +384,16 @@ class Timestamp(Base):

@classmethod
def from_heatpump(cls, value):
if value is None:
if not isinstance(value, int):
return None
if value <= 0:
return datetime.datetime.fromtimestamp(0)
return datetime.datetime.fromtimestamp(value)

@classmethod
def to_heatpump(cls, value):
if not isinstance(value, (int, float, datetime.datetime)):
return None
return datetime.datetime.timestamp(value)


Expand Down Expand Up @@ -572,12 +585,14 @@ class Hours2(Base):

@classmethod
def from_heatpump(cls, value):
if value is None:
if not isinstance(value, int):
return None
return 1 + value / 2

@classmethod
def to_heatpump(cls, value):
if not isinstance(value, int):
return None
return round((value - 1) * 2)


Expand Down Expand Up @@ -607,13 +622,29 @@ class Count(Base):
datatype_class = "count"


class Version(Base):
"""Version datatype, converts from and to a Heatpump Version."""

datatype_class = "version"

concatenate_multiple_data_chunks = False

@classmethod
def from_heatpump(self, value):
if not isinstance(value, list):
return None
return "".join([chr(c) for c in value]).strip("\x00")


class Character(Base):
"""Character datatype, converts from and to a Character."""

datatype_class = "character"

@classmethod
def from_heatpump(cls, value):
if not isinstance(value, int):
return None
if value == 0:
return ""
return chr(value)
Expand All @@ -626,6 +657,8 @@ class MajorMinorVersion(Base):

@classmethod
def from_heatpump(cls, value):
if not isinstance(value, int):
return None
if value > 0:
major = value // 100
minor = value % 100
Expand Down Expand Up @@ -944,7 +977,7 @@ class TimeOfDay(Base):

@classmethod
def from_heatpump(cls, value):
if value is None:
if not isinstance(value, int):
return None
hours = value // 3600
minutes = (value // 60) % 60
Expand All @@ -954,6 +987,8 @@ def from_heatpump(cls, value):

@classmethod
def to_heatpump(cls, value):
if not isinstance(value, str):
return None
d = [int(v) for v in value.split(":")]

val = d[0] * 3600 + d[1] * 60
Expand All @@ -970,7 +1005,7 @@ class TimeOfDay2(Base):

@classmethod
def from_heatpump(cls, value):
if value is None:
if not isinstance(value, int):
return None

value_low = value & 0xFFFF
Expand All @@ -984,6 +1019,8 @@ def from_heatpump(cls, value):

@classmethod
def to_heatpump(cls, value):
if not isinstance(value, str):
return None
d = value.split("-")
low = [int(v) for v in d[0].split(":")]
high = [int(v) for v in d[1].split(":")]
Expand Down Expand Up @@ -1099,10 +1136,10 @@ class FullVersion(Base):

@classmethod
def from_heatpump(cls, value):
if isinstance(value, list) and len(value) >= 3:
if not isinstance(value, list) or len(value) <= 2:
return None
if len(value) >= 3:
return f"{value[0]}.{value[1]}.{value[2]}"
else:
return "0"


class Unknown(Base):
Expand Down
Loading
Loading