Skip to content

Conversation

@trlemon
Copy link
Contributor

@trlemon trlemon commented Sep 29, 2025

Summary of Changes

  • Add blocking function to DynaCool PPMS driver that waits for temperature state to be stable before allowing other functions to be called when setting the temperature.

@codecov
Copy link

codecov bot commented Sep 30, 2025

Codecov Report

❌ Patch coverage is 16.66667% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.88%. Comparing base (d337823) to head (67b78a9).

Files with missing lines Patch % Lines
...ent_drivers/QuantumDesign/DynaCoolPPMS/DynaCool.py 16.66% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7534      +/-   ##
==========================================
- Coverage   59.89%   59.88%   -0.01%     
==========================================
  Files         352      352              
  Lines       31806    31812       +6     
==========================================
+ Hits        19051    19052       +1     
- Misses      12755    12760       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

def _block_on_temperature_ramp(self) -> None:
"""Block all instrument interactions until temperature stabilizes."""
while self.temperature_state() != "stable":
sleep(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

let's make this sleep time configurable via an attrbiute on the class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added _blocking_t_sleep attribute.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is better, however it's a private attribute, so as if it's not supposed to be touched by the users, which is not the case, i think it's totally fair that users can adjust that sleep time. So with that logic, i suggest to make it a memory/in-memory parameter (get_cmd=None, set_cmd=None), similar to how it's done with this parameter

self.ramping_state_check_interval = Parameter(
that controls sleep interval for a while loop that monitors magnetic field ramp.

values[self.temp_params.index(param)] = value

self.write(f"TEMP {values[0]}, {values[1]}, {values[2]}")
self._block_on_temperature_ramp()
Copy link
Contributor

Choose a reason for hiding this comment

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

i'd suggest to leave the current T parameter's implementation as-is in order not to break any existing code in terms of the fact that now setting the temperature blocks; and instead implement a new parameter that includes the "block on ramp". What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I created a new parameter to address this but also added a default bool to _temp_setter to control blocking functionality without breaking current implementation of the original temperature setting parameters.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not a big fan of having two parameters that control the temperature. Which one is the source of truth? I would have made a manual boolean parameter (block_during_temperature_ramp) and then make that one control the behaviour of the existing temperature parameter

Copy link
Contributor

Choose a reason for hiding this comment

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

My guess is that we're trying to follow the pattern that was established in lakeshore temperature controller driver https://microsoft.github.io/Qcodes/examples/driver_examples/Qcodes%20example%20with%20Lakeshore%20336%20or%20372%20-%20Bluefors%20T%20control.html#Sweeping/blocking-paramameter where there's setpoint parameter that's not blocking, and a blocking_t parameter that does the same as setpoint but also blocks until it's reached.

I'd be in favor of keeping consistency across the drivers (even if it's likely opinionated), so having both setpoint and blocking_t parameters is ok as long as their caches are in sync, i left a separate comment about it.

@SherwanMicrosoft
Copy link

Testing Results - Bug Found

Environment:

  • PPMS: Quantum Design DynaCool
  • QCodes version: 0.55.0.dev194 (this PR branch)
  • Testing date: December 19, 2025

Issue: blocking_t() causes PPMS server crash

Steps to Reproduce:

  1. Fresh PPMS connection - reads work normally (ppms.temperature() ✓)
  2. Execute: ppms.blocking_t(300)
  3. Result: VI_ERROR_IO on the TEMP write command
  4. Subsequent reads fail with VI_ERROR_CONN_LOST
  5. PPMS server connection is lost

Error Details:
VisaIOError: VI_ERROR_IO (-1073807298): Could not perform operation because of I/O error
at DynaCool.py:434 in write(): self._error_code = int(self.visa_handle.read())

Then:
VisaIOError: VI_ERROR_CONN_LOST (-1073807194): The connection for the given session has been lost

Observations:

  • ppms.temperature_setpoint() (non-blocking) works fine
  • ppms.blocking_t() crashes the connection
  • blocking_t parameter exists and is configured correctly
  • Issue occurs immediately when TEMP command is sent with blocking enabled

Root Cause Analysis:
The failure occurs at line 434 when trying to read the error code response after sending the TEMP command. The PPMS receives the command but fails to respond, then the connection is lost.

@trlemon
Copy link
Contributor Author

trlemon commented Jan 2, 2026

@SherwanMicrosoft My last commit resolves this issue. Thank you for the detailed report!

@trlemon trlemon marked this pull request as ready for review January 6, 2026 23:30
@trlemon trlemon requested a review from a team as a code owner January 6, 2026 23:30
Copy link
Contributor

@astafan8 astafan8 left a comment

Choose a reason for hiding this comment

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

a few small comments, but otherwise ready for merge!


self.blocking_t: Parameter = self.add_parameter(
"blocking_t",
label="Block instrument while ramping temp",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
label="Block instrument while ramping temp",
label="Block instrument while ramping temperature",

)
"""Parameter temperature_setpoint"""

self.blocking_t: Parameter = self.add_parameter(
Copy link
Contributor

Choose a reason for hiding this comment

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

def _block_on_temperature_ramp(self) -> None:
"""Block all instrument interactions until temperature stabilizes."""
while self.temperature_state() != "stable":
sleep(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is better, however it's a private attribute, so as if it's not supposed to be touched by the users, which is not the case, i think it's totally fair that users can adjust that sleep time. So with that logic, i suggest to make it a memory/in-memory parameter (get_cmd=None, set_cmd=None), similar to how it's done with this parameter

self.ramping_state_check_interval = Parameter(
that controls sleep interval for a while loop that monitors magnetic field ramp.

values[self.temp_params.index(param)] = value

self.write(f"TEMP {values[0]}, {values[1]}, {values[2]}")
self._block_on_temperature_ramp()
Copy link
Contributor

Choose a reason for hiding this comment

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

My guess is that we're trying to follow the pattern that was established in lakeshore temperature controller driver https://microsoft.github.io/Qcodes/examples/driver_examples/Qcodes%20example%20with%20Lakeshore%20336%20or%20372%20-%20Bluefors%20T%20control.html#Sweeping/blocking-paramameter where there's setpoint parameter that's not blocking, and a blocking_t parameter that does the same as setpoint but also blocks until it's reached.

I'd be in favor of keeping consistency across the drivers (even if it's likely opinionated), so having both setpoint and blocking_t parameters is ok as long as their caches are in sync, i left a separate comment about it.

label="Block instrument while ramping temp",
unit="K",
vals=vals.Numbers(1.6, 400),
set_cmd=partial(
Copy link
Contributor

Choose a reason for hiding this comment

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

let's make this parameter not gettable (explicitly :) )

Suggested change
set_cmd=partial(
get_cmd=False,
set_cmd=partial(

if block_while_ramping:
while self.temperature_state() != "stable":
sleep(self._blocking_t_sleep)

Copy link
Contributor

Choose a reason for hiding this comment

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

let's make sure that the caches of the two temperature parameters are in sync when either of them gets set. Note that private method _set_from_raw_value is used since we operate inside this method with raw values (not values, which could have offset/scale), so for correctness reasons we need to use the private method, which is OK to use inside qcodes and inside driver code, just not in user facing measurement code.

Suggested change
self.setpoint.cache._set_from_raw_value(values[0])
self.blocking_t.cache._set_from_raw_value(values[0])

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.

4 participants