Skip to content

Conversation

@jwatzk
Copy link

@jwatzk jwatzk commented Jan 7, 2026

The NetzOÖ Smartmeter Siemens TD-3511 does not provide a 'saldo' for grid export/import so is has to be calculated.

Messages in throttling.py floods the container logs and causes a constant increase of container memory.
Only print the messages when container starts with '--loglevel debug'.

Summary by CodeRabbit

  • New Features

    • Configurable power-calculation mode: toggle calculation and specify input/output measurement IDs via config.
  • Chores

    • Updated sample configuration with new power-related keys.
    • Improved logging for throttling and error conditions.
    • Modernized container build/startup and added an explicit dependency list.
    • Added a new ignore pattern for container build context.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

Warning

Rate limit exceeded

@jwatzk has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 34 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between a8d33f9 and bdc73b5.

📒 Files selected for processing (1)
  • Dockerfile

Walkthrough

Added POWER_CALCULATE, POWER_INPUT_UUID, and POWER_OUTPUT_UUID config keys and threaded them from the config loader into VZLogger. VZLogger gained flags/UUIDs to either read a single meter or compute power as input minus output. Throttling prints switched to logger.debug; Docker and dependency handling simplified.

Changes

Cohort / File(s) Summary
Configuration examples
config/config.ini.example
Added POWER_CALCULATE, POWER_INPUT_UUID, POWER_OUTPUT_UUID keys to GENERAL and VZLOGGER sections
Config loader
config/config_loader.py
create_vzlogger_powermeter now reads POWER_CALCULATE, POWER_INPUT_UUID, POWER_OUTPUT_UUID and passes them to VZLogger
VZLogger powermeter
powermeter/vzlogger.py
VZLogger.__init__ signature expanded to include power_calculate, power_input_uuid, power_output_uuid; get_json(path) added; get_powermeter_watts branches to fetch single UUID or compute input − output difference based on power_calculate
Throttling / logging
powermeter/throttling.py
Replaced print calls with logger.debug and added logger import
Docker build / deps
.dockerignore, Dockerfile, requirements.txt
.dockerignore adds ha_addon; Dockerfile removes curl/Pipenv, consolidates ENV, uses requirements.txt, updates HEALTHCHECK and CMD; new pinned requirements.txt added

Sequence Diagram(s)

sequenceDiagram
    participant ConfigLoader
    participant VZLogger
    participant RemoteAPI

    ConfigLoader->>ConfigLoader: Read POWER_CALCULATE, POWER_INPUT_UUID, POWER_OUTPUT_UUID
    ConfigLoader->>VZLogger: __init__(ip, port, uuid, power_calculate, input_uuid, output_uuid)
    VZLogger->>VZLogger: Store parameters

    alt power_calculate == true
        VZLogger->>RemoteAPI: GET /json/<POWER_INPUT_UUID>
        RemoteAPI-->>VZLogger: input power JSON
        VZLogger->>RemoteAPI: GET /json/<POWER_OUTPUT_UUID>
        RemoteAPI-->>VZLogger: output power JSON
        VZLogger->>VZLogger: compute input − output -> watts
    else power_calculate == false
        VZLogger->>RemoteAPI: GET /json/<uuid>
        RemoteAPI-->>VZLogger: power JSON -> watts
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title mentions 'Enhancement for vzlogger meter' and 'change logging in throttling.py', which aligns with the main changes: adding power calculation features to vzlogger and replacing print statements with logger.debug calls in throttling.py.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @powermeter/vzlogger.py:
- Around line 6-20: In the vzlogger.__init__ constructor, add validation that if
power_calculate is True then power_input_uuid and power_output_uuid are provided
and non-empty (e.g., check they are not None and bool(str.strip()) is True); if
either is missing/empty raise a ValueError with a clear message identifying the
missing UUID(s). This prevents later malformed HTTP requests when power
calculation is enabled.
- Around line 28-35: The current parsing in the method that uses
self.get_json(...) (checking self.power_calculate and returning values using
["data"][0]["tuples"][0][1]) can raise KeyError/IndexError/TypeError if the API
shape changes; wrap the parsing of responses from get_json(self.uuid),
get_json(self.power_input_uuid) and get_json(self.power_output_uuid) in a
try/except that catches KeyError, IndexError and TypeError, validate that the
top-level "data" key exists and is a non-empty list and that "tuples" exists and
has at least one item before indexing, and on failure raise or log a clear error
(e.g., include which UUID and the raw response) or return a safe sentinel (None
or 0) so callers get an explicit, descriptive error instead of an obscure
exception.
🧹 Nitpick comments (2)
powermeter/throttling.py (1)

3-3: Unused import: logging module not referenced.

The logging module is imported but never used directly. Only logger from config.logger is used.

♻️ Remove unused import
-import logging
powermeter/vzlogger.py (1)

31-32: Remove redundant variable initialization.

Lines 31-32 initialize power_in and power_out to 0, but these values are immediately overwritten on lines 33-34. The initialization serves no purpose.

♻️ Remove redundant initialization
     else:
-        power_in = 0
-        power_out = 0
         power_in = int(self.get_json(self.power_input_uuid)["data"][0]["tuples"][0][1])
         power_out = int(self.get_json(self.power_output_uuid)["data"][0]["tuples"][0][1])
         return [power_in - power_out]
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2f6a96 and 2abed04.

📒 Files selected for processing (4)
  • config.ini.example
  • config/config_loader.py
  • powermeter/throttling.py
  • powermeter/vzlogger.py
🧰 Additional context used
🧬 Code graph analysis (1)
powermeter/throttling.py (1)
powermeter/base.py (1)
  • Powermeter (2-8)
🪛 Ruff (0.14.10)
powermeter/throttling.py

74-74: Consider moving this statement to an else block

(TRY300)

🔇 Additional comments (3)
powermeter/throttling.py (1)

55-82: Excellent improvement to use debug-level logging.

Replacing print() statements with logger.debug() aligns with the PR objective to prevent log flooding and control message output via log levels. This will help prevent container memory growth from excessive logging.

config.ini.example (1)

92-94: LGTM! Configuration keys properly documented.

The new configuration options for power calculation are clearly defined and align with the code changes in vzlogger.py and config_loader.py.

config/config_loader.py (1)

243-245: Configuration reading looks good, but depends on validation in VZLogger.

The new configuration parameters are correctly read and passed to the VZLogger constructor. The fallback values are appropriate. However, ensure that the validation suggested in powermeter/vzlogger.py is implemented to catch cases where POWER_CALCULATE is True but the UUID parameters are empty or missing.

Comment on lines +6 to +20
def __init__(
self,
ip: str,
port: str,
uuid: str,
power_calculate: bool,
power_input_uuid: str,
power_output_uuid: str,
):
self.ip = ip
self.port = port
self.uuid = uuid
self.power_calculate = power_calculate
self.power_input_uuid = power_input_uuid
self.power_output_uuid = power_output_uuid
Copy link

@coderabbitai coderabbitai bot Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate required UUIDs when power calculation is enabled.

When power_calculate is True, the power_input_uuid and power_output_uuid parameters are required for proper operation. However, there's no validation to ensure these values are non-empty. If empty strings are passed (which can happen from config defaults), the code will attempt HTTP requests to malformed URLs, resulting in confusing runtime errors.

🛡️ Add validation in constructor
 def __init__(
         self, 
         ip: str, 
         port: str, 
         uuid: str,
         power_calculate: bool,
         power_input_uuid: str,
         power_output_uuid: str,
     ):
     self.ip = ip
     self.port = port
     self.uuid = uuid
     self.power_calculate = power_calculate
     self.power_input_uuid = power_input_uuid
     self.power_output_uuid = power_output_uuid
+    
+    # Validate required parameters when power calculation is enabled
+    if self.power_calculate:
+        if not self.power_input_uuid or not self.power_output_uuid:
+            raise ValueError(
+                "POWER_INPUT_UUID and POWER_OUTPUT_UUID must be provided when POWER_CALCULATE is True"
+            )
+    elif not self.uuid:
+        raise ValueError("UUID must be provided when POWER_CALCULATE is False")
+    
     self.session = requests.Session()

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @powermeter/vzlogger.py around lines 6 - 20, In the vzlogger.__init__
constructor, add validation that if power_calculate is True then
power_input_uuid and power_output_uuid are provided and non-empty (e.g., check
they are not None and bool(str.strip()) is True); if either is missing/empty
raise a ValueError with a clear message identifying the missing UUID(s). This
prevents later malformed HTTP requests when power calculation is enabled.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with this issue. Would be good to add validation for a clear error message when users provide an invalid config.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomquist, I'll plan the validation changes for the VZLogger constructor to ensure proper configuration validation.

[plan_code_changes]

Task: Add validation to VZLogger constructor

  • Add validation logic after parameter assignment in __init__
  • Check that required UUIDs are non-empty based on power_calculate mode
  • Raise ValueError with clear messages for invalid configurations

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @Dockerfile:
- Line 8: Restore the non-root user setup by re-enabling the user creation and
switching to it: uncomment or re-add the RUN useradd -m -u 1003 b2500 line (and
any related chown commands) and add a USER b2500 directive so the container does
not run as root; ensure any application directories and files referenced in the
Dockerfile are chown'd to that UID (or adjust file ownership steps already
present) and if the service needs privileged ports (<1024) either move to
unprivileged ports or grant capabilities explicitly rather than running as root.
- Line 30: Remove the leading indentation before the Dockerfile instruction so
the CMD instruction is left-aligned; change the line containing CMD ["python",
"main.py", "--loglevel", "info"] to have no leading spaces or tabs so the
Dockerfile parser/linter recognizes the instruction correctly.
- Around line 24-27: The HEALTHCHECK using a heredoc plus backslashes is invalid
because Dockerfile line continuations collapse the heredoc; replace it with a
single-line command (e.g., change the HEALTHCHECK to use python -c) so the probe
runs reliably: remove the multiline heredoc and backslashes around "python -
<<'EOF' ..." and use a one-liner like HEALTHCHECK CMD python -c "import
urllib.request; urllib.request.urlopen('http://127.0.0.1:52500/health',
timeout=3)" || exit 1 so the check actually executes and returns proper exit
codes.

In @requirements.txt:
- Around line 1-9: Update the pinned urllib3 version in requirements.txt from
"urllib3==2.2.2" to "urllib3==2.6.3" to address the listed HIGH-severity CVEs;
ensure the change coexists with the existing "requests==2.32.3" (requests allows
urllib3>=1.21.1,<3) and then regenerate lock/assets or run dependency install
and tests to verify no compatibility breakage.
🧹 Nitpick comments (1)
Dockerfile (1)

11-14: Consider collapsing pip installs into one layer.
Fewer layers + simpler caching behavior.

Proposed refactor
 COPY requirements.txt .
-RUN pip install --no-cache-dir --upgrade pip
-RUN pip install --no-cache-dir -r requirements.txt
+RUN pip install --no-cache-dir --upgrade pip \
+ && pip install --no-cache-dir -r requirements.txt
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2abed04 and bebe1b0.

📒 Files selected for processing (3)
  • .dockerignore
  • Dockerfile
  • requirements.txt
🧰 Additional context used
🪛 OSV Scanner (2.3.1)
requirements.txt

[HIGH] 9-9: urllib3 2.2.2: urllib3 streaming API improperly handles highly compressed data

(GHSA-2xpw-w6gg-jr37)


[HIGH] 9-9: urllib3 2.2.2: Decompression-bomb safeguards bypassed when following HTTP redirects (streaming API)

(GHSA-38jv-5279-wg99)


[HIGH] 9-9: urllib3 2.2.2: urllib3 does not control redirects in browsers and Node.js

(GHSA-48p4-8xcf-vxj5)


[HIGH] 9-9: urllib3 2.2.2: urllib3 allows an unbounded number of links in the decompression chain

(GHSA-gm62-xv2j-4w53)


[HIGH] 9-9: urllib3 2.2.2: urllib3 redirects are not disabled when retries are disabled on PoolManager instantiation

(GHSA-pq67-6m6q-mj2v)

🔇 Additional comments (1)
.dockerignore (1)

1-1: The ignore pattern ha_addon is already correct for your repository structure.

The ha_addon directory exists only at the root level, so the simple pattern excludes it properly. The optional refactors (**/ha_addon or ha_addon/) are unnecessary—they would work but add no practical value since the directory doesn't appear nested elsewhere in the repository.

Dockerfile Outdated
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
#RUN useradd -m -u 1003 b2500
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t run the container as root (security regression).
This container exposes network ports; dropping privileges materially reduces impact of a breakout/RCE.

Proposed fix (restore non-root user)
-#RUN useradd -m -u 1003 b2500
+RUN useradd -m -u 1003 b2500

 COPY . /app/
-#RUN chown -R b2500:b2500 /app
-#USER b2500
+RUN chown -R b2500:b2500 /app
+USER b2500

Also applies to: 17-18

🤖 Prompt for AI Agents
In @Dockerfile at line 8, Restore the non-root user setup by re-enabling the
user creation and switching to it: uncomment or re-add the RUN useradd -m -u
1003 b2500 line (and any related chown commands) and add a USER b2500 directive
so the container does not run as root; ensure any application directories and
files referenced in the Dockerfile are chown'd to that UID (or adjust file
ownership steps already present) and if the service needs privileged ports
(<1024) either move to unprivileged ports or grant capabilities explicitly
rather than running as root.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
powermeter/vzlogger.py (2)

6-21: LGTM! Constructor follows established pattern.

The new parameters for power calculation are consistent with the approach used in iobroker.py.

Consider validating that power_input_uuid and power_output_uuid are provided when power_calculate is True to fail fast with a clear error message rather than at runtime during the HTTP call.


27-45: Good addition of error handling for response parsing.

The branching logic for power_calculate correctly mirrors the pattern in iobroker.py, and the error handling for parsing failures (KeyError, IndexError, ValueError) is a valuable improvement for debugging malformed responses.

Note: Network errors from requests (e.g., ConnectionError, Timeout) will propagate uncaught, which is consistent with other powermeter implementations in this codebase. If you want uniform error handling, you could catch requests.RequestException as well, but this is optional given the existing patterns.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bebe1b0 and ee2d589.

📒 Files selected for processing (1)
  • powermeter/vzlogger.py
🧰 Additional context used
🧬 Code graph analysis (1)
powermeter/vzlogger.py (4)
powermeter/amisreader.py (2)
  • get_json (10-12)
  • get_powermeter_watts (14-16)
powermeter/iobroker.py (2)
  • get_json (23-25)
  • get_powermeter_watts (27-44)
powermeter/tasmota.py (1)
  • get_json (29-31)
powermeter/base.py (1)
  • get_powermeter_watts (7-8)
🪛 Ruff (0.14.10)
powermeter/vzlogger.py

34-34: Avoid specifying long messages outside the exception class

(TRY003)


45-45: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (1)
powermeter/vzlogger.py (1)

23-25: LGTM!

The get_json method now accepts a path parameter, which is appropriate for supporting multiple UUID endpoints. The URL construction with the explicit / before path works correctly with UUID-based paths.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @Dockerfile:
- Around line 19-22: The HEALTHCHECK heredoc with backslash-escaped newlines is
collapsing the shell heredoc and causing immediate failures; replace the
heredoc-style block under the HEALTHCHECK directive with a single-line command
(e.g., use python -c "import urllib.request;
urllib.request.urlopen('http://127.0.0.1:52500/health', timeout=3)" or an
equivalent curl/wget one-liner) so the command runs correctly in Docker without
relying on escaped newlines; update the HEALTHCHECK CMD entry in the Dockerfile
accordingly.
🧹 Nitpick comments (1)
Dockerfile (1)

8-10: Prefer python -m pip and collapse the two RUNs into one layer. This avoids any pip/python mismatch edge cases and trims image layers.

Proposed change
 COPY requirements.txt .
-RUN pip install --no-cache-dir --upgrade pip
-RUN pip install --no-cache-dir -r requirements.txt
+RUN python -m pip install --no-cache-dir --upgrade pip \
+ && python -m pip install --no-cache-dir -r requirements.txt
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27bd892 and a8d33f9.

📒 Files selected for processing (2)
  • Dockerfile
  • requirements.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • requirements.txt
🔇 Additional comments (2)
Dockerfile (2)

3-4: ENV consolidation looks good. Keeps runtime behavior consistent (PYTHONDONTWRITEBYTECODE, PYTHONUNBUFFERED).


25-25: CMD update is fine and aligns with the “debug-only throttling logs” objective.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep the comments? I would prefer to continue using the Pipfile.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would prefer if we keep using the Pipfile. I might decide to switch to requirements.txt at some point but this seems unrelated to the change.

Comment on lines +6 to +20
def __init__(
self,
ip: str,
port: str,
uuid: str,
power_calculate: bool,
power_input_uuid: str,
power_output_uuid: str,
):
self.ip = ip
self.port = port
self.uuid = uuid
self.power_calculate = power_calculate
self.power_input_uuid = power_input_uuid
self.power_output_uuid = power_output_uuid
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with this issue. Would be good to add validation for a clear error message when users provide an invalid config.

Comment on lines +92 to +94
#POWER_CALCULATE = True
#POWER_INPUT_UUID = grid-import-uuid
#POWER_OUTPUT_UUID = grid-export-uuid
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment above those lines to document what this does. And also please update the README file accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants