Erster Stand der Hintergrundprozesse, die auf der Synology unter /volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins Web-Verzeichnis. Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot), zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat. Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py, gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte. Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein pip, sie muessen neben den Skripten liegen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
152 lines
3.9 KiB
Python
152 lines
3.9 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Generator
|
|
|
|
|
|
class StreamReader:
|
|
"""
|
|
Generator-based stream reader.
|
|
|
|
This class doesn't support concurrent calls to :meth:`read_line`,
|
|
:meth:`read_exact`, or :meth:`read_to_eof`. Make sure calls are
|
|
serialized.
|
|
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
self.buffer = bytearray()
|
|
self.eof = False
|
|
|
|
def read_line(self, m: int) -> Generator[None, None, bytes]:
|
|
"""
|
|
Read a LF-terminated line from the stream.
|
|
|
|
This is a generator-based coroutine.
|
|
|
|
The return value includes the LF character.
|
|
|
|
Args:
|
|
m: maximum number bytes to read; this is a security limit.
|
|
|
|
Raises:
|
|
EOFError: if the stream ends without a LF.
|
|
RuntimeError: if the stream ends in more than ``m`` bytes.
|
|
|
|
"""
|
|
n = 0 # number of bytes to read
|
|
p = 0 # number of bytes without a newline
|
|
while True:
|
|
n = self.buffer.find(b"\n", p) + 1
|
|
if n > 0:
|
|
break
|
|
p = len(self.buffer)
|
|
if p > m:
|
|
raise RuntimeError(f"read {p} bytes, expected no more than {m} bytes")
|
|
if self.eof:
|
|
raise EOFError(f"stream ends after {p} bytes, before end of line")
|
|
yield
|
|
if n > m:
|
|
raise RuntimeError(f"read {n} bytes, expected no more than {m} bytes")
|
|
r = self.buffer[:n]
|
|
del self.buffer[:n]
|
|
return r
|
|
|
|
def read_exact(self, n: int) -> Generator[None, None, bytes]:
|
|
"""
|
|
Read a given number of bytes from the stream.
|
|
|
|
This is a generator-based coroutine.
|
|
|
|
Args:
|
|
n: how many bytes to read.
|
|
|
|
Raises:
|
|
EOFError: if the stream ends in less than ``n`` bytes.
|
|
|
|
"""
|
|
assert n >= 0
|
|
while len(self.buffer) < n:
|
|
if self.eof:
|
|
p = len(self.buffer)
|
|
raise EOFError(f"stream ends after {p} bytes, expected {n} bytes")
|
|
yield
|
|
r = self.buffer[:n]
|
|
del self.buffer[:n]
|
|
return r
|
|
|
|
def read_to_eof(self, m: int) -> Generator[None, None, bytes]:
|
|
"""
|
|
Read all bytes from the stream.
|
|
|
|
This is a generator-based coroutine.
|
|
|
|
Args:
|
|
m: maximum number bytes to read; this is a security limit.
|
|
|
|
Raises:
|
|
RuntimeError: if the stream ends in more than ``m`` bytes.
|
|
|
|
"""
|
|
while not self.eof:
|
|
p = len(self.buffer)
|
|
if p > m:
|
|
raise RuntimeError(f"read {p} bytes, expected no more than {m} bytes")
|
|
yield
|
|
r = self.buffer[:]
|
|
del self.buffer[:]
|
|
return r
|
|
|
|
def at_eof(self) -> Generator[None, None, bool]:
|
|
"""
|
|
Tell whether the stream has ended and all data was read.
|
|
|
|
This is a generator-based coroutine.
|
|
|
|
"""
|
|
while True:
|
|
if self.buffer:
|
|
return False
|
|
if self.eof:
|
|
return True
|
|
# When all data was read but the stream hasn't ended, we can't
|
|
# tell if until either feed_data() or feed_eof() is called.
|
|
yield
|
|
|
|
def feed_data(self, data: bytes) -> None:
|
|
"""
|
|
Write data to the stream.
|
|
|
|
:meth:`feed_data` cannot be called after :meth:`feed_eof`.
|
|
|
|
Args:
|
|
data: data to write.
|
|
|
|
Raises:
|
|
EOFError: if the stream has ended.
|
|
|
|
"""
|
|
if self.eof:
|
|
raise EOFError("stream ended")
|
|
self.buffer += data
|
|
|
|
def feed_eof(self) -> None:
|
|
"""
|
|
End the stream.
|
|
|
|
:meth:`feed_eof` cannot be called more than once.
|
|
|
|
Raises:
|
|
EOFError: if the stream has ended.
|
|
|
|
"""
|
|
if self.eof:
|
|
raise EOFError("stream ended")
|
|
self.eof = True
|
|
|
|
def discard(self) -> None:
|
|
"""
|
|
Discard all buffered data, but don't end the stream.
|
|
|
|
"""
|
|
del self.buffer[:]
|