-
Notifications
You must be signed in to change notification settings - Fork 39
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
Fix common.iter_bytes
#81
Conversation
Codecov Report
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. @@ Coverage Diff @@
## main #81 +/- ##
==========================================
+ Coverage 84.80% 84.97% +0.17%
==========================================
Files 18 18
Lines 1033 1058 +25
Branches 152 160 +8
==========================================
+ Hits 876 899 +23
- Misses 120 121 +1
- Partials 37 38 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
|
I have a slightly different understanding of send_end. The way I read the specs if we have 7 data bits and send_end is True, then the bit 7 should be true only for the last bytes. In your case it seems you encode send_end in an extra bit compared to the number of allowed data bits. I checked on my phone (little time this days) so I may have missed something. |
Hmm... So you're interpreting "transmit the last character with the eighth bit set" as "8th bit == 1", not "8th bit == whatever value it would have been for that character" Like this? (7 data bits)
Do I have that correct? (The below is for my own future reference, emphasis and additions mine)
|
pyvisa_sim/common.py
Outdated
data : The data to clip as a byte string. | ||
data_bits : How many bits per byte should be sent. Clip to this many bits. | ||
For example: data_bits=5: 0xff (0b1111_1111) --> 0x1f (0b0001_1111) | ||
send_end : If True, send the final byte unclipped (with all 8 bits). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-(with all 8 bits)
+(with all `data_bits` bits)
Pending clarification of #81 (comment) - might be different.
Rereading https://www.ni.com/docs/en-US/bundle/ni-visa/page/ni-visa/vi_attr_asrl_end_out.html, I realize I missed one case which calls for taking bool | None as argument:
Does that make sense ? |
Yeah I think so. Check out the new test suite and LMK. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks almost right but I have one small concern for the non terminal bytes when send_end is not None
.
pyvisa_sim/common.py
Outdated
mask = _create_bitmask(data_bits) | ||
|
||
# Send everything but the last byte with the mask applied. | ||
for d in data[:-1]: | ||
yield bytes([d & ~mask]) | ||
yield bytes([d & mask]) | ||
|
||
last_byte = data[-1] | ||
|
||
if send_end: | ||
yield bytes([data[-1] | ~mask]) | ||
# Send the last byte adjusted by `send_end` | ||
if send_end is None: | ||
# only apply the mask | ||
yield bytes([last_byte & mask]) | ||
elif bool(send_end) is True: | ||
# apply the mask and set highest of data_bits to 1 | ||
highest_bit = 1 << (data_bits - 1) | ||
yield bytes([(last_byte & mask) | highest_bit]) | ||
elif bool(send_end) is False: | ||
# apply the mask and set highest of data_bits to 0 | ||
# This is effectively the same has reducing the mask by 1 bit. | ||
new_mask = _create_bitmask(data_bits - 1) | ||
yield bytes([last_byte & new_mask]) | ||
else: | ||
yield bytes([data[-1] & ~mask]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If send_end
is not None
the highest bit should always be cleared since it has a special meaning. The easiest way to do it would make sense to adjust the value of data_bits
.
(b"\x04", 2, False, b"\x00"), # 0b0000_0100 --> 0b0000_0000 | ||
(b"\x04", 3, False, b"\x00"), # 0b0000_0100 --> 0b0000_0000 | ||
(b"\x05", 3, False, b"\x01"), # 0b0000_0101 --> 0b0000_0001 | ||
(b"\xff", 7, False, b"\x3f"), # 0b1111_1111 --> 0b0011_1111 | ||
(b"\xff", 8, False, b"\x7f"), # 0b1111_1111 --> 0b0111_1111 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those cases need to cover more than a single byte.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See L54-L59 - those cases test multiple bytes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and they do expose the issue about clearing the highest bit I mentioned in another comment.
pyvisa_sim/testsuite/test_common.py
Outdated
(b"\x6d\x5c\x25\x25", 4, None, b"\r\x0c\x05\x05"), | ||
(b"\x6d\x5c\x25\x25", 4, False, b"\r\x0c\x05\x05"), | ||
(b"\x6d\x5c\x25\x25", 4, True, b"\r\x0c\x05\x0d"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that the case for send_end = None
match the other two tells me that the highest bit is not cleared as I would expect for a non-None send_end
value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Holy crap, I have no idea why that was so hard for me to grok. But I think (I hope 🤣) I've got it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One last inconsistency I noticed (but not sure how to test for it) otherwise LGTM
pyvisa_sim/common.py
Outdated
if mask is None: | ||
|
||
def iter_bytes( | ||
data: bytes, data_bits: Optional[int] = None, send_end: Optional[bool] = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think None
would make for a more natural default for send_end.
pyvisa_sim/sessions/serial.py
Outdated
data_bits, _ = self.get_attribute( | ||
constants.ResourceAttribute.asrl_data_bits | ||
) | ||
val = b"".join(common.iter_bytes(data, data_bits, send_end)) | ||
self.device.write(val) | ||
else: | ||
self.device.write(data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that this is not affected by asrl_data_bits looks awfully wrong to me, we should go through iter_bytes with a send_end of None.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay. Both comments addressed.
Thanks ! Could you make a similar PR againt PyVISA-py (I have no good solution to avoid the duplication) ? |
Fixes #80.
See that Issue for more detail.
iter_bytes
function signature to usedata_bits
instead ofmask
. Calculate the mask base on those number of bits.