Skip to content
Merged
26 changes: 17 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,24 @@ The following example reads in data from the heat pump:
from luxtronik import Luxtronik

l = Luxtronik('192.168.1.23', 8889)
calculations, parameters, visibilities = l.read()

t_forerun = l.calculations.get("ID_WEB_Temperatur_TVL")
t_forerun = calculations.get("ID_WEB_Temperatur_TVL")

# alternatively get also works with numerical ID values

t_forerun = l.calculations.get(10)
t_forerun = calculations.get(10)

print(t_forerun) # this returns the temperature value of the forerun, 22.7 for example
print(t_forerun.unit) # gives you the unit of the value if known, °C for example

# l.calculations holds measurement values
# calculations holds measurement values
# check https://github.com/Bouni/luxtronik/blob/master/luxtronik/calculations.py for values you might need

# l.parameters holds parameter values
# parameters holds parameter values
# check https://github.com/Bouni/luxtronik/blob/master/luxtronik/parameters.py for values you might need

# l.visibilitys holds visibility values, the function of visibilities is not clear at this point
# visibilitys holds visibility values, the function of visibilities is not clear at this point
# check https://github.com/Bouni/luxtronik/blob/master/luxtronik/visibilities.py for values you might need
```

Expand All @@ -85,21 +86,28 @@ modifying them) and to get a better understanding about parameters (e.g. by
looking for differences when comparing the output after doing some changes
locally, etc.).

Alternatively, you can use the `dump-changes.py` script to output only changed values:

```python
PYTHONPATH=. ./scripts/dump-changes.py 192.168.1.5
```

### WRITING VALUES TO HEAT PUMP

The following example writes data to the heat pump:

```python
from luxtronik import Luxtronik
from luxtronik import Luxtronik, Parameters

l = Luxtronik('192.168.1.23', 8889)

heating_mode = l.parameters.set("ID_Ba_Hz_akt", "Party")
l.write()
parameters = Parameters()
heating_mode = parameters.set("ID_Ba_Hz_akt", "Party")
l.write(parameters)

# If you're not sure what values to write, you can get all available options:

print(l.parameters.get("ID_Ba_Hz_akt").options) # returns a list of possible values to write, ['Automatic', 'Second heatsource', 'Party', 'Holidays', 'Off'] for example
print(parameters.get("ID_Ba_Hz_akt").options) # returns a list of possible values to write, ['Automatic', 'Second heatsource', 'Party', 'Holidays', 'Off'] for example
```

**NOTE:** Writing values to the heat pump is particulary dangerous as this is
Expand Down
94 changes: 51 additions & 43 deletions luxtronik/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@ def __init__(self, host, port=8889, safe=True):
self._lock = threading.Lock()
self._host = host
self._port = port
self._safe = safe
self._socket = None
self.calculations = Calculations()
self.parameters = Parameters(safe=safe)
self.visibilities = Visibilities()
self.read()

def __del__(self):
Expand All @@ -66,13 +64,13 @@ def __del__(self):

def read(self):
"""Read data from heatpump."""
self._read_after_write(write=False)
return self._read_after_write(parameters=None)

def write(self):
def write(self, parameters):
"""Write parameter to heatpump."""
self._read_after_write(write=True)
return self._read_after_write(parameters=parameters)

def _read_after_write(self, write=False):
def _read_after_write(self, parameters):
"""
Read and/or write value from and/or to heatpump.
This method is essentially a wrapper for the _read() and _write()
Expand All @@ -84,8 +82,9 @@ def _read_after_write(self, write=False):
prior to reading back in all data from the heat pump. If write is
false, no data will be written, but all available data will be read
from the heat pump.
:param bool write Indicates whether parameters should be written to heatpump
prior to reading in all available data from heatpump
:param Parameters() parameters Parameter dictionary to be written
to the heatpump before reading all available data
from the heatpump. At 'None' it is read only.
"""

with self._lock:
Expand All @@ -97,84 +96,93 @@ def _read_after_write(self, write=False):
LOGGER.info(
"Connected to Luxtronik heatpump %s:%s", self._host, self._port
)
if write:
self._write()
return
self._read()
if parameters is not None:
return self._write(parameters)
return self._read()

def _read(self):
self._read_parameters()
self._read_calculations()
self._read_visibilities()
parameters = self._read_parameters()
calculations = self._read_calculations()
visibilities = self._read_visibilities()
return calculations, parameters, visibilities

def _write(self):
for index, value in self.parameters.queue.items():
def _write(self, parameters):
for index, value in parameters.queue.items():
if not isinstance(index, int) or not isinstance(value, int):
LOGGER.warning("Parameter id '%s' or value '%s' invalid!", index, value)
LOGGER.warning(
"%s: Parameter id '%s' or value '%s' invalid!",
self._host,
index,
value,
)
continue
LOGGER.info("Parameter '%d' set to '%s'", index, value)
LOGGER.info("%s: Parameter '%d' set to '%s'", self._host, index, value)
data = struct.pack(">iii", 3002, index, value)
LOGGER.debug("Data %s", data)
LOGGER.debug("%s: Data %s", self._host, data)
self._socket.sendall(data)
cmd = struct.unpack(">i", self._socket.recv(4))[0]
LOGGER.debug("Command %s", cmd)
LOGGER.debug("%s: Command %s", self._host, cmd)
val = struct.unpack(">i", self._socket.recv(4))[0]
LOGGER.debug("Value %s", val)
LOGGER.debug("%s: Value %s", self._host, val)
# Flush queue after writing all values
self.parameters.queue = {}
parameters.queue = {}
# Give the heatpump a short time to handle the value changes/calculations:
time.sleep(WAIT_TIME_AFTER_PARAMETER_WRITE)
# Read the new values based on our parameter changes:
self._read_parameters()
self._read_calculations()
self._read_visibilities()
return self._read()

def _read_parameters(self):
data = []
self._socket.sendall(struct.pack(">ii", 3003, 0))
cmd = struct.unpack(">i", self._socket.recv(4))[0]
LOGGER.debug("Command %s", cmd)
LOGGER.debug("%s: Command %s", self._host, cmd)
length = struct.unpack(">i", self._socket.recv(4))[0]
LOGGER.debug("Length %s", length)
LOGGER.debug("%s: Length %s", self._host, length)
for _ in range(0, length):
try:
data.append(struct.unpack(">i", self._socket.recv(4))[0])
except struct.error as err:
# not logging this as error as it would be logged on every read cycle
LOGGER.debug(err)
LOGGER.info("Read %d parameters", length)
self.parameters.parse(data)
LOGGER.debug("%s: %s", self._host, err)
LOGGER.info("%s: Read %d parameters", self._host, length)
parameters = Parameters(safe=self._safe)
parameters.parse(data)
return parameters

def _read_calculations(self):
data = []
self._socket.sendall(struct.pack(">ii", 3004, 0))
cmd = struct.unpack(">i", self._socket.recv(4))[0]
LOGGER.debug("Command %s", cmd)
LOGGER.debug("%s: Command %s", self._host, cmd)
stat = struct.unpack(">i", self._socket.recv(4))[0]
LOGGER.debug("Stat %s", stat)
LOGGER.debug("%s: Stat %s", self._host, stat)
length = struct.unpack(">i", self._socket.recv(4))[0]
LOGGER.debug("Length %s", length)
LOGGER.debug("%s: Length %s", self._host, length)
for _ in range(0, length):
try:
data.append(struct.unpack(">i", self._socket.recv(4))[0])
except struct.error as err:
# not logging this as error as it would be logged on every read cycle
LOGGER.debug(err)
LOGGER.info("Read %d calculations", length)
self.calculations.parse(data)
LOGGER.debug("%s: %s", self._host, err)
LOGGER.info("%s: Read %d calculations", self._host, length)
calculations = Calculations()
calculations.parse(data)
return calculations

def _read_visibilities(self):
data = []
self._socket.sendall(struct.pack(">ii", 3005, 0))
cmd = struct.unpack(">i", self._socket.recv(4))[0]
LOGGER.debug("Command %s", cmd)
LOGGER.debug("%s: Command %s", self._host, cmd)
length = struct.unpack(">i", self._socket.recv(4))[0]
LOGGER.debug("Length %s", length)
LOGGER.debug("%s: Length %s", self._host, length)
for _ in range(0, length):
try:
data.append(struct.unpack(">b", self._socket.recv(1))[0])
except struct.error as err:
# not logging this as error as it would be logged on every read cycle
LOGGER.debug(err)
LOGGER.info("Read %d visibilities", length)
self.visibilities.parse(data)
LOGGER.debug("%s: %s", self._host, err)
LOGGER.info("%s: Read %d visibilities", self._host, length)
visibilities = Visibilities()
visibilities.parse(data)
return visibilities
Loading