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

Fix common.iter_bytes #81

Merged
merged 10 commits into from
May 29, 2023
Merged

Conversation

dougthor42
Copy link
Contributor

Fixes #80.

See that Issue for more detail.

  • Adjust iter_bytes function signature to use data_bits instead of mask. Calculate the mask base on those number of bits.
  • Update docstring
  • Update tests

@codecov-commenter
Copy link

codecov-commenter commented Apr 15, 2023

Codecov Report

Merging #81 (7644d02) into main (dccb9ba) will increase coverage by 0.17%.
The diff coverage is 95.23%.

❗ 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     
Flag Coverage Δ
unittests 84.97% <95.23%> (+0.17%) ⬆️

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

Impacted Files Coverage Δ
pyvisa_sim/testsuite/test_serial.py 100.00% <ø> (ø)
pyvisa_sim/common.py 91.66% <92.85%> (-2.78%) ⬇️
pyvisa_sim/sessions/serial.py 68.75% <100.00%> (ø)
pyvisa_sim/testsuite/test_common.py 100.00% <100.00%> (ø)

@MatthieuDartiailh
Copy link
Member

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.

@dougthor42
Copy link
Contributor Author

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)

Input send_end=True send_end=False
N/A 7th bit set to 1 for last byte no matter what the original value was.
8th bit always removed b/c data_bits=7.
7th bit set to 0 for last byte no matter what the original value was.
8th bit always removed b/c data_bits=7.
0b1111_1111 0b0111_1111 0b0011_1111
0b0000_1111 0b0100_1111
0b0000_1111
0b1111_1100,
0b1011_1100,
0b1011_1100
0b0111_1100,
0b0011_1100,
0b0111_1100
0b0111_1100,
0b0011_1100,
0b0011_1100

Do I have that correct?

(The below is for my own future reference, emphasis and additions mine)

If it is set to VI_ASRL_END_LAST_BIT (pyvisa.constants.SerialTermination.last_bit), and VI_ATTR_SEND_END_EN (pyvisa.constants.ResourceAttribute.send_end_enabled which is the send_end arg) is set to VI_TRUE (True), the write will send all but the last character with the highest bit clear, then transmit the last character with the highest bit set. For example, if VI_ATTR_ASRL_DATA_BITS is set to 8, the write will clear the eighth bit for all but the last character, then transmit the last character with the eighth bit set. If VI_ATTR_SEND_END_EN is set to VI_FALSE, the write will send all the characters with the highest bit clear.

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).
Copy link
Contributor Author

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.

@MatthieuDartiailh
Copy link
Member

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:

  • None: serial termination does not depend on send end, so we simply apply the mask
  • False: serial termination depends on send end and send end enabled is False, we always clear (set to zero) the highest of the data bits.
  • True: serial termination depends on send end and send end is enabled, we clear the highest of the data bits for all bytes except the last for which it is set to 1.

Does that make sense ?

@dougthor42
Copy link
Contributor Author

Yeah I think so. Check out the new test suite and LMK.

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.

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 Show resolved Hide resolved
Comment on lines 61 to 34
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])
Copy link
Member

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.

Comment on lines +37 to +41
(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
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Comment on lines 54 to 56
(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"),
Copy link
Member

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.

Copy link
Contributor Author

@dougthor42 dougthor42 left a 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.

pyvisa_sim/common.py Show resolved Hide resolved
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.

One last inconsistency I noticed (but not sure how to test for it) otherwise LGTM

if mask is None:

def iter_bytes(
data: bytes, data_bits: Optional[int] = None, send_end: Optional[bool] = False
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 None would make for a more natural default for send_end.

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)
Copy link
Member

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.

Copy link
Contributor Author

@dougthor42 dougthor42 left a 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.

@MatthieuDartiailh
Copy link
Member

Thanks !

Could you make a similar PR againt PyVISA-py (I have no good solution to avoid the duplication) ?

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.

Bug in iter_bytes
3 participants