Skip to content
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

Fixed bug in USBTMC code which caused issues when packets were longer than max usb packet size #449

Merged
merged 7 commits into from
Oct 1, 2024

Conversation

hcoffin-vecatom
Copy link
Contributor

@hcoffin-vecatom hcoffin-vecatom commented Sep 10, 2024

Following sec. 3.3 of "Universal Serial Bus Test and Measurement Class Specification (USBTMC)" Rev 1.0, updated the read function of USBTMC in protocols/usbtmc.py . Read now reads from usb until a "short packet" has been read (see specification), and only expects a header on the first packet received. In usb.py, added separate read function to USBInstrSession which is nearly identical to USBSessions.read, but has "lambda current: False" instead of "lambda current: True", to allow for usbtmc device to return multiple packets before a termination character and to remove trailing alignment bytes which might be sent by device.

  • Closes # (insert issue number if relevant)
  • Executed black . && isort -c . && flake8 with no errors
  • The change is fully covered by automated unit tests
  • Documented in docs/ as appropriate
  • Added an entry to the CHANGES file

hcoffin-vecatom and others added 3 commits September 10, 2024 11:31
…BTMC class to correctly handle messages longer than the usb packet size. Created USBIntstrSession read to handle case where an incomplete message is read (i.e., before a term character is reached)
Copy link

codecov bot commented Sep 10, 2024

Codecov Report

Attention: Patch coverage is 0% with 6 lines in your changes missing coverage. Please review.

Project coverage is 24.47%. Comparing base (eed3dca) to head (3cf5528).
Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
pyvisa_py/protocols/usbtmc.py 0.00% 4 Missing ⚠️
pyvisa_py/sessions.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #449      +/-   ##
==========================================
- Coverage   24.48%   24.47%   -0.01%     
==========================================
  Files          23       23              
  Lines        3488     3485       -3     
  Branches      485      480       -5     
==========================================
- Hits          854      853       -1     
- Misses       2617     2626       +9     
+ Partials       17        6      -11     
Flag Coverage Δ
unittests 24.47% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@arr-ee
Copy link
Contributor

arr-ee commented Sep 10, 2024

Hi — thank you for the PR!

Would you mind providing some context regarding what prompted you to make the change? Any specific instrument/command pattern that were having issues and are fixed by this? This information might help with revisiting existing USBTMC-related issues.

Thanks!

@hcoffin-vecatom
Copy link
Contributor Author

hcoffin-vecatom commented Sep 10, 2024

Hi — thank you for the PR!

Would you mind providing some context regarding what prompted you to make the change? Any specific instrument/command pattern that were having issues and are fixed by this? This information might help with revisiting existing USBTMC-related issues.

Thanks!

I am working with a Siglent SDG1062X function generator. I tried querying the waveform parameters using 'C1:BSWV?' (see https://siglentna.com/USA_website_2014/Documents/Program_Material/SDG_ProgrammingGuide_PG_E03B.pdf section 3.4), but I was getting issues where I would only get a partial response back (first 52 bytes, which is one usb packet without the 12 byte BulkInMessage header), and future queries would return garbage (the remaining packets partially misinterpreted as part of the BulkInMessage header information). The code in this PR fixes the issue for me, but I haven't been able to test it on other devices, so I'm not sure if part of this is a quirk of this particular device.

@arr-ee
Copy link
Contributor

arr-ee commented Sep 10, 2024

Amazing, thank you so much!

This PR might be related to a bunch of existing issues around USBTMC connectivity with Siglent and Rigol instruments (that are known to be... quirky): off the top of the issues list, #414, #436, #318, and #311 seem like they might be caused by the same problem.

Once again, thank you for taking the time to dig into the issue and open a PR.

@hcoffin-vecatom
Copy link
Contributor Author

Amazing, thank you so much!

This PR might be related to a bunch of existing issues around USBTMC connectivity with Siglent and Rigol instruments (that are known to be... quirky): off the top of the issues list, #414, #436, #318, and #311 seem like they might be caused by the same problem.

Once again, thank you for taking the time to dig into the issue and open a PR.

Happy to help! Took a look at the issues you mentioned. #414 and #436 look like the same issue I was having. #318 could be, but has a slightly different symptom than what I was seeing; in the USBTMC spec they say that the "short message" can be 0 bytes, which sounds kind of like the symptom they describe. #311 definitely won't be fixed by this PR since I didn't change any of the write code. I didn't look into the spec for bulkout at all so I'm not sure if it has the same type of issue.

Copy link
Member

@MatthieuDartiailh MatthieuDartiailh left a comment

Choose a reason for hiding this comment

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

Thanks your contribution ! USBTMC is complicated and all fix improving support are more than welcome.

I left a comment that I think is worth addressing here. Can you have a look @hcoffin-vecatom ?

In addition to that if you can add a releasenotes entry it would be great.

pyvisa_py/usb.py Outdated
return self._read(
_usb_reader,
count,
lambda current: False, # USBTMC can return partial message (i.e.,
Copy link
Member

Choose a reason for hiding this comment

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

Here the end indicator checker does not care about the termination character, only about the fact that the underlying hardware signaled it is done transmitting a message. For GPIB, this is done by checking the state of hardware line, for USB it is always trivially true since by construction a BulkIn message is complete when received. Using this kind of notification is more efficient than checking for term chars.

The _read implementation takes care of ensuring we read till a term char if we were requested to do so. Actually it is at this layer that support for suppress_end is implemented so I would appreciate if while removing this implementation, you could also get rid of the error forbidding the use of suppress_end.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the comment. I didn't fully understand what _read was meant to do. I think I have fixed the issue and also allowed suppress_end_en to be True in my lastest commit.

@hcoffin-vecatom
Copy link
Contributor Author

Thanks your contribution ! USBTMC is complicated and all fix improving support are more than welcome.

I left a comment that I think is worth addressing here. Can you have a look @hcoffin-vecatom ?

In addition to that if you can add a releasenotes entry it would be great.

I think I have made the relevant changes. I'm not sure if I did the releasenotes correctly as I haven't done that before, so feel free to change it if desired.

@hcoffin-vecatom
Copy link
Contributor Author

Also here is the code I used to test it in case someone else needs it:

import pyvisa

res = pyvisa.ResourceManager('@py').open_resource('USB0::62700::4355::SDG1XDDC801438::0::INSTR')
res.set_visa_attribute(pyvisa.constants.VI_ATTR_SUPPRESS_END_EN, True)
res.read_termination = '\n'
res.write_termination = '\n'

with res:
    res.write('C1:BSWV?')
    print(res.read_raw())

With 'res.set_visa_attribute(pyvisa.constants.VI_ATTR_SUPPRESS_END_EN, True)' it outputs

b'C1:BSWV WVTP,SINE,FRQ,123HZ,PERI,0.00813008S,AMP,0.05V,AMPVRMS,0.017675Vrms,AMPDBM,0dBm,MAX_OUTPUT_AMP,20V,OFST,-0.2V,HLEV,-0.175V,LLEV,-0.225V,PHSE,78\n'

With 'res.set_visa_attribute(pyvisa.constants.VI_ATTR_SUPPRESS_END_EN, False)' it outputs

b'C1:BSWV WVTP,SINE,FRQ,123HZ,PERI,0.00813008S,AMP,0.05V,AMPVRMS,0.017675Vrms,AMPDBM,0dBm,MAX_OUTPUT_AMP,20V,OFST,-0.2V,HLEV,-0.175V,LLEV,-0.225V,PHSE,78\n\x00\x00\x00\x00\x00\x00\x00\x00'

@MatthieuDartiailh
Copy link
Member

Wow thanks for doing the fixes so fast !!!

For the suppress_end option, can you run your test with NI or Keysight VISA to see if the output is the same ?

@hcoffin-vecatom
Copy link
Contributor Author

Wow thanks for doing the fixes so fast !!!

For the suppress_end option, can you run your test with NI or Keysight VISA to see if the output is the same ?

Unfortunately, I have not been unable to correctly install either of those libraries on my computer (running Linux Mint w/ Ubuntu 21.2). I tried installing NI-VISA before looking into changing the code in pyvisa-py and was unable to get it to even see any device let alone query them.

I just tried installing Keysight's version, but I'm mostly getting errors (to be fair to Keysight, they say my version of Linux isn't supported during the installation).
Every once in a while, if keep unplugging and replugging the device with suppress_end set to False, I get

b'C1:BSWV WVTP,SINE,FRQ,1000HZ,PERI,0.001S,AMP,4V,AMPV'
Segmentation fault (core dumped)

which (besides the seg fault) is what I got with pyvisa-py backend before any of the updates in this pull request. Trying to run the test script again without replugging the device results in.

pyvisa.errors.VisaIOError: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.

If I run it with suppress_end set to True, I always get

pyvisa.errors.VisaIOError: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.

So I would not trust that suppress_end is working correctly unless someone else can verify.

@MatthieuDartiailh
Copy link
Member

@arr-ee would be able to test the suppress_end behavior ?

@arr-ee
Copy link
Contributor

arr-ee commented Sep 18, 2024

I should be able to in the next couple of days, will update soon

@arr-ee
Copy link
Contributor

arr-ee commented Sep 20, 2024

Testing with my SDG2122X:

Test code:

import sys
import pyvisa

pyvisa.log_to_screen()

rm = pyvisa.ResourceManager('@py')
i = rm.open_resource('USB0::62700::60984::SDG2XCA4162836::0::INSTR')
i.set_visa_attribute(pyvisa.constants.VI_ATTR_SUPPRESS_END_EN, sys.argv[1] == '1')

print(f"IDN: {i.query('*IDN?')}")
i.write('C1:BSWV?')
print(f"C1:BSWV?: {i.read_raw()}")

pyvisa 1.14.1, pyvisa-py 0.7.2:

Repeated executions of the script lead to different results, with previous responses showing up in place of up to date ones, e.g.:

$ python test.py 0
...
USB0::62700::60984::SDG2XCA4162836::0::INSTR - reading 20480 bytes (last status <StatusCode.success_max_count_read: 1073676294>)
/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_base/lib/python3.10/site-packages/pyvisa_py/protocols/usbtmc.py:116: UserWarning: Unexpected MsgID format. Consider updating the device's firmware. See https://github.com/pyvisa/pyvisa-py/issues/20Expected message id was 2, got 77.
  warnings.warn(
IDN: C1:BSWV WVTP,SINE,FRQ,55000000HZ,PERI,1.81818e-08S,AMP,1
2024-09-20 16:22:28,308 - pyvisa - DEBUG - USB0::62700::60984::SDG2XCA4162836::0::INSTR - reading 20480 bytes (last status <StatusCode.success_max_count_read: 1073676294>)
/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_base/lib/python3.10/site-packages/pyvisa_py/protocols/usbtmc.py:116: UserWarning: Unexpected MsgID format. Consider updating the device's firmware. See https://github.com/pyvisa/pyvisa-py/issues/20Expected message id was 2, got 46.
  warnings.warn(
C1:BSWV?: b'Siglent Technologies,SDG2122X,SDG2XCA4162836,2.01.01.35R'
2024-09-20 16:22:28,309 - pyvisa - DEBUG - Closing ResourceManager (session: 4947628)

pyvisa.constants.VI_ATTR_SUPPRESS_END_EN is not supported by the released version.

pyvisa 1.14.1, pyvisa-py 0.7.3.dev26+gc632e68:

Same code hangs on the first read, not reacting to Ctrl-C. When process is killed, attempts to write to the device timeout, and even physical controls do not react to anything (I suspected it to be "remote" mode, but hard-reboot and running the script using the released version does not lead to UI lock-up). Adding read/write termination does not change the behaviour.

I also tried running the same code with R&S VISA, and both through pyvisa and through the bundled tester utility I've been getting exception while reading: VI_ERROR_IO (-1073807298): Could not perform operation because of I/O error. when reading C1:BSWV? response.

Attached are packet captures for released code (before), new code running after released (after), new code running after instrument reboot (after_clean), and rsvisa (rsvisa)

pyvisapy_449_captures.zip

@arr-ee
Copy link
Contributor

arr-ee commented Sep 20, 2024

However, I do see progress when running a slightly changed test code against SDS2104X Plus:

Code change:

print(f"IDN: {i.query('*IDN?')}")
i.write(':WAVEFORM:SOURCE C1')
i.write(':WAVEFORM:DATA?')
print(f":WAVEFORM:DATA?: {i.read_raw()}")

Released code gets the same issues with old results, but that also leads to

...
  File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_base/lib/python3.10/site-packages/pyvisa_py/protocols/usbtmc.py", line 481, in read
    response = BulkInMessage.from_bytes(resp)
  File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_base/lib/python3.10/site-packages/pyvisa_py/protocols/usbtmc.py", line 114, in from_bytes
    msgid, btag, btaginverse = struct.unpack_from("BBBx", data)
struct.error: unpack_from requires a buffer of at least 4 bytes for unpacking 4 bytes at offset 0 (actual buffer size is 0)

errors. R&S VISA just throws I/O errors on raw read.

Updated code (after several tried to get rid of old replies in the instrument buffer) works every time. suppress_end also appears to be working since I do sometimes get implausibly short results when it is enabled because response can include \n characters.

@MatthieuDartiailh
Copy link
Member

I am a bit worried by the very hard freeze you report in you first test but the rest seems like clear win.
At this point you know more than me about the USBTMC backend @arr-ee so I let you decide what is the best course of action here.

@arr-ee
Copy link
Contributor

arr-ee commented Sep 20, 2024

According to a quick glance at the packet capture, version from the PR attempts to read repeatedly (?), and responses from the device get more and more jumbled until it hangs. I have not looked into USB packet internals much before, so it might take me a minute.

@hcoffin-vecatom let me know if there is any info you might need in the meantime, I have aforementioned Siglent devices, some supposedly better behaving ones (R&S/HPAK), and some potentially broken-in-different-ways (hi GW Instek and possibly Tenma) that I'll try to test against.

Another thing I can eventually try is updating the firmware on the SDG — it's several minor versions behind, although it's not old old; and testing it against a windows box with Siglent's drivers to get a "reference implementation" (assuming it works).

@hcoffin-vecatom
Copy link
Contributor Author

Took me a bit to parse those log files, but it looks like your SDG2122X has some even weirder behavior than my SDG1062X.
When I do '*IDN?', I get the following packets

b'\x02\x02\xfd\x004\x00\x00\x00\x00\x00\x00\x00Siglent Technologies,SDG1062X,SDG1XDDC801438,1.01.01'
b'.33R'
b'\x02\x03\xfc\x00\x06\x00\x00\x00\x01\x00\x00\x008\n\x00\x00\x00\x00\x00\x00'

Notice the x01 on the last packet. That represents that this usbtmc message (which could be multiple usb packets) should be the last (the eom logic in usbtmc.py).

When you do the same query on your device (looking at the after_clean capture you sent), you never receive an eom (in picture marked below, I have underlined where that should be in red). The device sends a new line character (underlined in green), which I guess is meant to tell the computer to stop reading more packets, but that is not how usbtmc is specified. It then proceeds to just keep copy and pasting its id over and over.
image

To be honest, I have no idea how to handle that in a reasonable way. I guess you could set a relatively short timeout and just get rid of all the extra copies it sends, but that seems quite hacky. Only other option I can see is to parse out termination characters in the USBTMC.read function, which again seems wrong.

@arr-ee
Copy link
Contributor

arr-ee commented Sep 21, 2024

Fascinating.

@hcoffin-vecatom thank you for looking into the captures right away.

I’ll try to run more tests in the next couple of days to hopefully get a better understanding of what’s happening, but ultimately I think this PR should be OK to merge since my instrument’s behaviour appears to be broken-broken.

If no one minds I suggest postponing merge until early next week just in case any of further tests point to an explanation/workaround.

@hcoffin-vecatom
Copy link
Contributor Author

Fascinating.

@hcoffin-vecatom thank you for looking into the captures right away.

I’ll try to run more tests in the next couple of days to hopefully get a better understanding of what’s happening, but ultimately I think this PR should be OK to merge since my instrument’s behaviour appears to be broken-broken.

If no one minds I suggest postponing merge until early next week just in case any of further tests point to an explanation/workaround.

Thought of one more small thing to try over the weekend. I'm guessing you have already tried this, but if you haven't, can you try setting the termination character to '\n' before reading/writing to SDG2122X. It looks like, at least for writing to the device, you are terminating with '\r\n' in that log. The SDG programming manual specifies '\n' as the termination, so I'm not sure what to expect if it isn't.
image

@arr-ee
Copy link
Contributor

arr-ee commented Sep 23, 2024

Yes, I have noticed that you were changing termination settings, and tried that as well with no change in behaviour IIRC. I will try it again to make sure I have not missed a combination of factors that actually works.

@arr-ee
Copy link
Contributor

arr-ee commented Sep 30, 2024

Thank you for your patience, took me a minute.

I only got to run linux-based tests since windows machine is out of reach for this instrument's USB right now, but tl;dr PR is fine, Siglent just being Siglent. Firmware update from 2.01.01.35R (2019 vintage) to latest fixed hanging reads.

Details:

  1. Changing read/write termination does not change the behaviour, the first read still hangs:
    <powering the DUT off during hang produces the following>
    Traceback (most recent call last):
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa_py/protocols/usbtmc.py", line 480, in read
        resp = raw_read(recv_chunk + header_size + max_padding)
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa_py/protocols/usbtmc.py", line 295, in read
        data = self.usb_recv_ep.read(size, self.timeout).tobytes()
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/usb/core.py", line 423, in read
        return self.device.read(self, size_or_buffer, timeout)
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/usb/core.py", line 1029, in read
        ret = fn(
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/usb/backend/libusb1.py", line 846, in bulk_read
        return self.__read(self.lib.libusb_bulk_transfer,
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/usb/backend/libusb1.py", line 954, in __read
        _check(retval)
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/usb/backend/libusb1.py", line 604, in _check
        raise USBError(_strerror(ret), ret, _libusb_errno[ret])
    usb.core.USBError: [Errno 5] Input/Output Error
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/test.py", line 33, in <module>
        logger.info(f"IDN: {i.query('*IDN?')}")
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa/resources/messagebased.py", line 647, in query
        return self.read()
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa/resources/messagebased.py", line 485, in read
        message = self._read_raw().decode(enco)
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa/resources/messagebased.py", line 441, in _read_raw
        chunk, status = self.visalib.read(self.session, size)
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa_py/highlevel.py", line 513, in read
        data, status_code = self.sessions[session].read(count)
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa_py/usb.py", line 163, in read
        return self._read(
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa_py/sessions.py", line 792, in _read
        current = reader()
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa_py/usb.py", line 152, in _usb_reader
        return self.interface.read(count)
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa_py/protocols/usbtmc.py", line 492, in read
        self._abort_bulk_in(self._btag)
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/pyvisa_py/protocols/usbtmc.py", line 388, in _abort_bulk_in
        data = self.usb_dev.ctrl_transfer(
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/usb/core.py", line 1082, in ctrl_transfer
        ret = self._ctx.backend.ctrl_transfer(
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/usb/backend/libusb1.py", line 893, in ctrl_transfer
        ret = _check(self.lib.libusb_control_transfer(
      File "/home/lab/projects/pyvisa-repro/449-usbtmc/.venv_fixed/lib/python3.10/site-packages/usb/backend/libusb1.py", line 604, in _check
        raise USBError(_strerror(ret), ret, _libusb_errno[ret])
    usb.core.USBError: [Errno 5] Input/Output Error
    
  2. kernel usbtmc driver (6.8.0-45-generic, Ubuntu 24.04.1 LTS) behaves similarly to R&S VISA: incomplete first read, nothing on second read:
    $ echo "*IDN?" > /dev/usbtmc0 && cat /dev/usbtmc0
    Siglent Technologies,SDG2122X,SDG2XCA4162836,2.01.01<no newline, not full fw version>
    dmesg:
    [1148895.281910] usbtmc 1-1.4.1:1.0: Device sent too small first packet: 8 < 12
    $ echo "C1:BSWV?" > /dev/usbtmc0 && cat /dev/usbtmc0
    <no output>
    dmesg:
    [1148947.020224] usbtmc 1-1.4.1:1.0: Device sent reply with wrong bTag: 3 != 5
    <(also seen Device sent reply with wrong bTag: 5 != 8)>
    
    Note that 6.8.0 does not include Fix to use reported MaxPacketSize in buffers. dpenkler/linux-usbtmc#9; however, that logic has been added to pyvisa-py (Fix to use endpoints MaxPacketSize instead of hardcoded RECV_CHUNK size #417), so it might be worth it to test the experimental kernel driver to see if it exhibits issues similar to what's being fixed here.
  3. Finally, SUPPRESS_END_EN:
    1. Disabled: identical results for default read_termination=None and read_termination='\n': bunch of zero bytes after \n.
    2. Enabled:
      • read_termination=None: all reads timeout
      • read_termination='\n': response string ends with \n.
        This appears to be the desired behaviour I think?

Apologies for not getting to the bottom of this, but at least it appears to be safe(r) to merge now.

@MatthieuDartiailh
Copy link
Member

I updated the README and will merge as soon as the CIs are green.

@MatthieuDartiailh
Copy link
Member

Thanks @hcoffin-vecatom for the patch and @arr-ee for testing !!!

@MatthieuDartiailh MatthieuDartiailh merged commit 40e1e4b into pyvisa:main Oct 1, 2024
16 of 18 checks passed
@arr-ee
Copy link
Contributor

arr-ee commented Oct 1, 2024

Huge shout to @hcoffin-vecatom for the fix and explanations during testing!

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.

3 participants