8000 Aquacomputer D5 Next sensor reading support by aleksamagicka · Pull Request #482 · liquidctl/liquidctl · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Aquacomputer D5 Next sensor reading support #482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Aug 3, 2022

Conversation

aleksamagicka
Copy link
Member
@aleksamagicka aleksamagicka commented Jul 23, 2022

As promised in #438, here is the first PR for adding support for Aquacomputer devices to liquidctl. This PR adds a new driver called aquacomputer, and it currently supports reading sensor values of the D5 Next watercooling pump.

Aquacomputer devices share same layouts of sensor substructures/groups in their HID reports, so adding sensor reading support for other devices, such as Octo, Quadro, Farbwerk, Farbwerk 360 should be trivial. (Just need to specify the offsets in device specific data and write some cases for non-general sensors.) They are all supported by the partially mainlined driver that I maintain.

Some advanced features of the devices are still being investigated and reverse engineered, and additionally as far as I know nobody has looked at the RGB controls yet. The kernel driver supports sensor reading and directly controlling the PWM inputs, where available.

Marking this PR as draft for now, as I have to check more boxes down below. I've formatted the code using black (haven't used it before), so hopefully it looks as it should.

And a question for the future: the kernel driver support for devices varies between kernel versions. Is there a clean way to get the kernel version from liquidctl already?

Related: #439


Checklist:

  • Adhere to the development process
  • Conform to the style guide
  • Verify that the changes work as expected on real hardware
  • Add automated tests cases
  • Verify that all (other) automated tests (still) pass
  • Update the README and other applicable documentation pages
  • Update the liquidctl.8 Linux/Unix/Mac OS man page
  • Update or add applicable docs/*guide.md device guides
  • Submit relevant data, scripts or dissectors to https://github.com/liquidctl/collected-device-data

New CLI flag?

  • Adjust the completion scripts in extra/completions/

New device?

  • Regenerate extra/linux/71-liquidctl.rules (instructions in the file header)
  • Add entry to the README's supported device list with applicable notes (at least en)

New driver?

  • Document the protocol in docs/developer/protocol/

@jonasmalacofilho
Copy link
Member

Awesome, thanks for working on this!

Some advanced features of the devices are still being investigated and reverse engineered, and additionally as far as I know nobody has looked at the RGB controls yet.

Small note: in this case, I think it'd be more appropriate to raise NotSupportedByDriver.

And a question for the future: the kernel driver support for devices varies between kernel versions. Is there a clean way to get the kernel version from liquidctl already?

Not yet.

Also, while getting the kernel version should be relatively straightforward, that wouldn't tell us the full picture, as the user could be running an out-of-tree driver.

Do you know how we might get information about the module?


I haven't done a proper review yet, but at first glance the patch looks very looks good.

@aleksamagicka
Copy link
Member Author

Small note: in this case, I think it'd be more appropriate to raise NotSupportedByDriver.

Thanks. NotSupportedByDriver sound better, I wasn't sure exactly what exception to raise while I was writing it. I'll explore the code more.

Also, while getting the kernel version should be relatively straightforward, that wouldn't tell us the full picture, as the user could be running an out-of-tree driver.

Yes, that's a concern as well. I'm not yet that intimate with liquidctl, but if I'm not mistaken I saw some drivers accepting custom CLI flags. Maybe that could be used to override assumptions the code would make about the kernel version it detects. I'll have to explore and think about it more, because there's features piling up at the kernel driver repo that I have yet to send in. But purely for the sensor stuff for now, it should be a clear cut of what kernel supports what. I have a table in the readme to track that, aside from WIP branches for some other devices that I'm not going to send here just yet.

@jonasmalacofilho
Copy link
Member

I'm not yet that intimate with liquidctl, but if I'm not mistaken I saw some drivers accepting custom CLI flags. Maybe that could be used to override assumptions the code would make about the kernel version it detects.

I think that could get quite messy quite fast.

A think a combination of us implementing graceful degradation and the user having the option to use --direct-access might be enough, and better than a ton of CLI options.

(On a related note, for super hack level or only-enable-if-you-known-what-you're-doing options we also have --unsafe features and environment variables).

plus_5v_voltage = ("+5V voltage", self._hwmon.get_int("in2_input") * 1e-3, "V")
sensor_readings.append(plus_5v_voltage)

if self._hwmon.has_attribute("in3_input"):
Copy link
Member Author

Choose a reason for hiding this comment

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

Should a warning be produced if this is not available?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think so. That attribute is useful, and our other drivers emit warnings in similar situations:

_LOGGER.warning('some attributes cannot be read from %s kernel driver', self._hwmon.module)

I don't think the warning needs to be more specific than that, even though here it's just one attribute. In the future there might be more (or at least different) attributes missing, and I don't think most users need to care about the specifics (while this is an useful attribute, everything else is still quite functional without it).

That said, one downside of the non-specific warning is that users will need to check the documentation and/or try --direct-access just to see what they might be missing. But that's a general problem with all of our (hwmon-aware) drivers, so this probably isn't the best place to consider it.

Copy link
Member Author
@aleksamagicka aleksamagicka Jul 30, 2022

Choose a reason for hiding this comment

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

Added that, thanks.

README.md Outdated
@@ -125,6 +125,7 @@ The notes are sorted alphabetically, major (upper case) notes before minor
| AIO liquid cooler | [NZXT Kraken Z53, Z63, Z73](docs/kraken-x3-z3-guide.md) | USB & USB HID | <sup>_p_</sup> |
| DDR4 DRAM | [Corsair Vengeance RGB](docs/ddr4-guide.md) | SMBus | <sup>_Uax_</sup> |
| DDR4 DRAM | [Generic DDR4 temperature sensor](docs/ddr4-guide.md) | SMBus | <sup>_Uax_</sup> |
| Fan/LED controller | [Aquacomputer D5 Next](docs/aquacomputer-d5next-guide.md) | USB HID | <sup>_ehn_</sup> |
Copy link
Member Author

Choose a reason for hiding this comment

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

It's category is more of a "watercooling pump/fan controller", since it only controls it's own LEDs... Is the existing text OK or should I write it differently?

Copy link
Member

Choose a reason for hiding this comment

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

I think "Fan/pump controller" might be a reasonably appropriate and short name, and it can also replace the "Fan/AIO controller" category used for the NZXT H1 V2.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, marked both as that.

@aleksamagicka aleksamagicka marked this pull request as ready for review July 29, 2022 20:37
@jonasmalacofilho
Copy link
Member

Just saw that you marked the PR as ready for review, thanks! I'll try to do it this weekend.

Copy link
Member
@jonasmalacofilho jonasmalacofilho left a comment

Choose a reason for hiding this comment

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

Hi,

I really appreciate the attention to detail, the PR is super clean. I left a few notes, but they are very minor.

Regarding the remaining applicable items in the checklist, you can leave updating the man page to me, as for now that'll just be a placeholder, and I already need to add those to some other devices.

But can you also try to document the protocol (as much as currently implemented by the driver) in docs/developer/protocol/*?

sensor_readings = []

# Read temp sensor values
for idx, temp_sensor_offset in enumerate(self._device_info["temp_sensors"]):
Copy link
Member

Choose a reason for hiding this comment

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

This is probably just as easy to read as is, if not more, but a reminder that you could also do something like:

Suggested change
for idx, temp_sensor_offset in enumerate(self._device_info["temp_sensors"]):
for label, offset in zip(self._device_info["temp_sensors_label"], self._device_info["temp_sensors"]):

...here and in the similar loops bellow.

Copy link
Member Author
@aleksamagicka aleksamagicka Aug 3, 2022

Choose a reason for hiding this comment

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

Thanks, I've been aware of zip() before, but I never had an opportunity to use it (in Python or elsewhere). I converted that loop to use it, since it fits in nicely.

Copy link
Member
@jonasmalacofilho jonasmalacofilho Aug 3, 2022

Choose a reason for hiding this comment

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

Let me start by saying sorry for the noise!

I think it's (more) important to be consistent throughout the file. And while it's possible to use zip in the other loops too, it'll probably look a bit unwieldy when more than one label is needed per iteration. Honestly, even in this loop, I think I prefer the look of your original version, thanks to the shorter and more uniform lines.

I also thought that there could be a performance penalty in doing the extra indexed (and checked) access, but I can't actually see that in a quick microbenchmark, at least not with Python 3.10.[1]

So I might revert this back before merging the PR, unless you want to try using zip on the other loops.

Again, sorry!


[1] (EDIT) With small lists it makes no difference, but if the lists were larger enumerate+[idx] could have a moderate to significant (negative) impact:

$ SIZE=2                                                                                      
$ python -m timeit -s "values = list(range(0, $SIZE))" "for a, b in zip(values, values): pass"
2000000 loops, best of 5: 130 nsec per loop
$ python -m timeit -s "values = list(range(0, $SIZE))" "for idx, a in enumerate(values): b = values[idx]"
2000000 loops, best of 5: 125 nsec per loop

$ SIZE=10                                                                                                
$ python -m timeit -s "values = list(range(0, $SIZE))" "for a, b in zip(values, values): pass"           
1000000 loops, best of 5: 244 nsec per loop
$ python -m timeit -s "values = list(range(0, $SIZE))" "for idx, a in enumerate(values): b = values[idx]"
1000000 loops, best of 5: 317 nsec per loop

$ SIZE=100                                                                                               
$ python -m timeit -s "values = list(range(0, $SIZE))" "for a, b in zip(values, values): pass"           
200000 loops, best of 5: 1.49 usec per loop
$ python -m timeit -s "values = list(range(0, $SIZE))" "for idx, a in enumerate(values): b = values[idx]"
100000 loops, best of 5: 2.41 usec per loop

$ SIZE=1000
$ python -m timeit -s "values = list(range(0, $SIZE))" "for a, b in zip(values, values): pass"           
20000 loops, best of 5: 13.7 usec per loop
$ python -m timeit -s "values = list(range(0, $SIZE))" "for idx, a in enumerate(values): b = values[idx]"
10000 loops, best of 5: 28.9 usec per loop

Copy link
Member Author

Choose a reason for hiding this comment

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

No problem, I'll revert it now to make it easier.

Copy link
Member Author

Choose a reason for hiding this comment

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

Just saw the edit - off the top of my head there will be 8-10 max register offsets (for Octo), so we're good.

@aleksamagicka
Copy link
Member Author

Stuff you commented on should now be resolved, please take a look.

I'll add a protocol description later during the day, hopefully. I've written about it before, so I'll use it to speed up the process as it's good enough for the sensor report aspect. (The control/configuration report has been reverse engineered a bit more throughly, and that needs to be updated.)

@jonasmalacofilho
Copy link
Member

Stuff you commented on should now be resolved, please take a look.

Thank you!

I'll add a protocol description later during the day, hopefully. I've written about it before, so I'll use it to speed up the process as it's good enough for the sensor report aspect. (The control/configuration report has been reverse engineered a bit more throughly, and that needs to be updated.)

Yes, that's good enough, thanks!

@aleksamagicka
Copy link
Member Author

I've pushed the docs and added serial number retrieval to initialization. I've made the protocol docs general, because the devices are similar and I'll be extending it in future PRs once this one is merged.

@jonasmalacofilho jonasmalacofilho merged commit 42a5e7a into liquidctl:main Aug 3, 2022
@jonasmalacofilho
Copy link
Member

Merged, thank you!

I've made the protocol docs general, because the devices are similar and I'll be extending it in future PRs once this one is merged.

Yeah, that makes sense.

@aleksamagicka aleksamagicka deleted the aquacomputer-bringup branch August 3, 2022 20:43
@jonasmalacofilho
Copy link
Member

Err, I just noticed an issue.

You're overriding the serial_number property, and that will cause a HID read during probe if a --serial filter is used (and you have one of these devices).

Probing is not supposed to result in any reads or writes, and in the case of USB HIDs (and other USB devices), that properly should only return the USB iSerialNumber descriptor.


Related to it, there something else I noticed but had so far left for later:

Unless you end up (in the next devices) needing the firmware version and serial number outside of initialize, it's probably better to not parse them on every _read.

We also currently don't expose a general firmware version property for all devices. While it's fine to do have it (when, like here, it' seems stable and cheap to compute), it's also not necessary.

@aleksamagicka
Copy link
Member Author
aleksamagicka commented Aug 3, 2022

You're overriding the serial_number property, and that will cause a HID read during probe if a --serial filter is used (and you have one of these devices).

Sorry, that was accidental, I didn't realize there was an existing function with that same name. Is it OK to rename it so the true one is not overriden?

Unless you end up (in the next devices) needing the firmware version and serial number outside of initialize, it's probably better to not parse them on every _read.

Makes sense, I'll move parsing to their respective functions.

I'll send a PR tomorrow as it's getting late here.

@jonasmalacofilho
Copy link
Member

Sorry, that was accidental, I didn't realize there was an existing function with that same name.

Yeah, I missed it too.

Is it OK to rename it so the true one is not overriden?

In the case of the serial number I think it's probably better to not have it as public property.

IIRC we don't have it on any other drivers, and for those specifically interested in it, it would be a way to bypass calling initialize(), which might not be ideal in the long term.

(Besides initialize potentially becoming necessary at some point, we also explicitly document that the specific keys and values returned from it are not stable, and that's important because we can't guarantee that the firmware wont change these things).

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
0