Skip to content

Conversation

@etmmvdp
Copy link
Contributor

@etmmvdp etmmvdp commented Jul 24, 2025

  • make more pythonic
  • use f-strings
  • use magic values for json names only once
  • add time delay device

Summary by Sourcery

Modernize the Quatt Domoticz plugin by refactoring for Pythonic style, consolidating helper functions, bumping version, and adding a time delay device.

New Features:

  • Add a 'Time Delay' device to measure latency between system and CIC timestamps

Enhancements:

  • Use f-strings for all string formatting
  • Adopt walrus operators and dict.get() to streamline JSON data access
  • Rename internal helpers with underscore prefixes and reduce magic value duplication

* make more pythonic
* use f-strings
* use magic values for json names only once
* add time delay device
@sourcery-ai
Copy link

sourcery-ai bot commented Jul 24, 2025

Reviewer's Guide

The PR refactors plugin.py to adopt Pythonic idioms—replacing string concatenation and .format calls with f-strings, leveraging walrus operators and dict.get for concise JSON and parameter handling—privatizes helper functions, centralizes magic values, bumps the plugin version, and adds a new time-delay device to measure latency.

Sequence diagram for Time Delay device update on new data

sequenceDiagram
    participant Domoticz
    participant QuattPlugin
    participant Devices
    participant time
    Domoticz->>QuattPlugin: onMessage(Connection, Data)
    QuattPlugin->>QuattPlugin: _process_response(data)
    QuattPlugin->>time: time.time_ns()
    QuattPlugin->>Devices: _update_device(unit=35, sValue=delay, nValue=0)
    Devices-->>QuattPlugin: Update device 35 with delay
Loading

Class diagram for refactored QuattPlugin and helper functions

classDiagram
    class QuattPlugin {
        httpConn
        sendAfterConnect
        discovered_data
        __init__()
        onStart()
        onConnect(Connection, Status, Description)
        onStop()
        onHeartbeat()
        onMessage(Connection, Data)
        onDisconnect(Connection)
    }
    class _dump_config_to_log {
    }
    class _create_basic_devices {
    }
    class _create_dynamic_devices {
    }
    class _process_response {
    }
    class _update_device {
    }
    QuattPlugin -- _dump_config_to_log : uses
    QuattPlugin -- _create_basic_devices : uses
    QuattPlugin -- _create_dynamic_devices : uses
    QuattPlugin -- _process_response : uses
    QuattPlugin -- _update_device : uses
Loading

File-Level Changes

Change Details Files
Modernized logging and messaging with f-strings
  • Replaced all Domoticz.Debug and Domoticz.Log concatenations and .format calls with f-strings
  • Updated onConnect, onDisconnect, onMessage, DumpConfigToLog, and updateDevice logging output
plugin.py
Simplified parameter and JSON handling using walrus operators and dict.get
  • Assigned mode6 via walrus and default from Parameters.get
  • Replaced manual key checks (e.g., "if 'hp1' in data") with walrus assignments and data.get()
  • Streamlined nested JSON value retrievals in _process_response
plugin.py
Privatized helper functions and updated their invocations
  • Renamed DumpConfigToLog, createBasicDevices, createDynamicDevices, processResponse, and updateDevice to use leading underscores
  • Updated all internal calls to use the new private helper names
plugin.py
Added time-delay device and latency computation
  • Imported time module
  • Added a basic device unit (35) for 'Time Delay' with milliseconds option
  • Computed delay between system and CIC timestamps in onMessage and updated the new device
plugin.py
Bumped plugin version from 0.0.9 to 0.0.10
  • Updated plugin header version attribute
plugin.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @etmmvdp - I've reviewed your changes - here's some feedback:

  • The _process_response function is quite long and repetitive—consider defining a mapping of JSON keys to device units and update parameters, then looping over it to reduce boilerplate and improve maintainability.
  • Many of the helper functions are defined at the module level with leading underscores; you might encapsulate them as private methods on QuattPlugin or move them to a separate utility module to avoid polluting the global namespace.
  • You’re using time.time_ns() for the delay calculation, which requires Python 3.7+—verify that the Domoticz environment supports this or provide a fallback like int(time.time() * 1000) for compatibility.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `_process_response` function is quite long and repetitive—consider defining a mapping of JSON keys to device units and update parameters, then looping over it to reduce boilerplate and improve maintainability.
- Many of the helper functions are defined at the module level with leading underscores; you might encapsulate them as private methods on `QuattPlugin` or move them to a separate utility module to avoid polluting the global namespace.
- You’re using `time.time_ns()` for the delay calculation, which requires Python 3.7+—verify that the Domoticz environment supports this or provide a fallback like `int(time.time() * 1000)` for compatibility.

## Individual Comments

### Comment 1
<location> `plugin.py:318` </location>
<code_context>
+                _update_device(self, 25, round(value, 1), 1)
+
+        # Time Delay
+        if (cic_time := data.get("time")) is not None:
+            if (cic_ts := cic_time.get("ts")) is not None:
+                cic_ts_millis = cic_ts
+                system_ts_millis = time.time_ns() // 1_000_000
+                delay = system_ts_millis - cic_ts_millis
+                _update_device(self, 35, delay, 0)
+
     except Exception as e:
</code_context>

<issue_to_address>
The calculation of delay assumes synchronized clocks and may be misleading.

Document the assumption of synchronized clocks or add a mechanism to handle clock drift or offsets to ensure accurate delay reporting.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        # Time Delay
+        if (cic_time := data.get("time")) is not None:
+            if (cic_ts := cic_time.get("ts")) is not None:
+                cic_ts_millis = cic_ts
+                system_ts_millis = time.time_ns() // 1_000_000
+                delay = system_ts_millis - cic_ts_millis
+                _update_device(self, 35, delay, 0)
=======
        # Time Delay
        # Assumes system clock and device clock are synchronized.
        # If clocks are not synchronized, delay calculation may be inaccurate.
        # TODO: Implement clock drift/offset correction if device and system clocks can differ.
        if (cic_time := data.get("time")) is not None:
            if (cic_ts := cic_time.get("ts")) is not None:
                cic_ts_millis = cic_ts
                system_ts_millis = time.time_ns() // 1_000_000
                delay = system_ts_millis - cic_ts_millis
                _update_device(self, 35, delay, 0)
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +317 to +323
# Time Delay
if (cic_time := data.get("time")) is not None:
if (cic_ts := cic_time.get("ts")) is not None:
cic_ts_millis = cic_ts
system_ts_millis = time.time_ns() // 1_000_000
delay = system_ts_millis - cic_ts_millis
_update_device(self, 35, delay, 0)
Copy link

Choose a reason for hiding this comment

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

suggestion: The calculation of delay assumes synchronized clocks and may be misleading.

Document the assumption of synchronized clocks or add a mechanism to handle clock drift or offsets to ensure accurate delay reporting.

Suggested change
# Time Delay
if (cic_time := data.get("time")) is not None:
if (cic_ts := cic_time.get("ts")) is not None:
cic_ts_millis = cic_ts
system_ts_millis = time.time_ns() // 1_000_000
delay = system_ts_millis - cic_ts_millis
_update_device(self, 35, delay, 0)
# Time Delay
# Assumes system clock and device clock are synchronized.
# If clocks are not synchronized, delay calculation may be inaccurate.
# TODO: Implement clock drift/offset correction if device and system clocks can differ.
if (cic_time := data.get("time")) is not None:
if (cic_ts := cic_time.get("ts")) is not None:
cic_ts_millis = cic_ts
system_ts_millis = time.time_ns() // 1_000_000
delay = system_ts_millis - cic_ts_millis
_update_device(self, 35, delay, 0)

Comment on lines +126 to +131
Domoticz.Debug(f"Device: {x} - {Devices[x]}")
Domoticz.Debug(f"Device ID: '{Devices[x].ID}'")
Domoticz.Debug(f"Device Name: '{Devices[x].Name}'")
Domoticz.Debug(f"Device nValue: {Devices[x].nValue}")
Domoticz.Debug(f"Device sValue: '{Devices[x].sValue}'")
Domoticz.Debug(f"Device LastLevel: {Devices[x].LastLevel}")
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Extract code out into function (extract-method)

Domoticz.Device(Name=name, Unit=unit, TypeName=type_name, Options=options[0] if options else {}, Image=options[1] if len(options) > 1 else 0).Create()

def createDynamicDevices(self, data):
def _create_dynamic_devices(self, data):
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Low code quality found in _create_dynamic_devices - 19% (low-code-quality)


ExplanationThe quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

  • Reduce the function length by extracting pieces of functionality out into
    their own functions. This is the most important thing you can do - ideally a
    function should be less than 10 lines.
  • Reduce nesting, perhaps by introducing guard clauses to return early.
  • Ensure that variables are tightly scoped, so that code using related concepts
    sits together within the function rather than being scattered.

Domoticz.Device(Name=name, Unit=unit, TypeName=type_name, Options=options[0] if options else {}, Image=options[1] if len(options) > 1 else 0).Create()

def processResponse(self, data):
def _process_response(self, data):
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:


Explanation
The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

  • Reduce the function length by extracting pieces of functionality out into
    their own functions. This is the most important thing you can do - ideally a
    function should be less than 10 lines.
  • Reduce nesting, perhaps by introducing guard clauses to return early.
  • Ensure that variables are tightly scoped, so that code using related concepts
    sits together within the function rather than being scattered.

@etmmvdp
Copy link
Contributor Author

etmmvdp commented Jul 24, 2025

It seems to be a big change but that's mostly using new python features without changing functionality.
Functionality wise I added the time delay device to measure the time difference between CIC and the Domoticz host.

Let me know if you like it or not.

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.

1 participant