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

Refactor src/uartdev for async and code size #169

Merged
merged 13 commits into from
May 26, 2023
Merged

Refactor src/uartdev for async and code size #169

merged 13 commits into from
May 26, 2023

Conversation

laurensvalk
Copy link
Member

The current iodev and uartdev code will only ever be used for LEGO uart devices, so we can reduce the level of abstraction and complexity considerably. More generic devices would be handled by a port interface like proposed in #159.

The main reason for doing is to prepare for handling sensors in an async way compatible with #166. Now, we can make a single synchronous call to set the mode/data (and raise a relevant error as needed), and await data to be ready in the same way.

This also reduces the code size by about 570 bytes despite adding new functionality like buffered lpf2 writes, which should further reduce the (otherwise needed) size to implement async for sensors.

The final commit with buffered data writes is perhaps the more controversial one, as it gets a bit complicated. We could consider doing without that one and check if it's needed when we get to the async part.

@coveralls
Copy link

coveralls commented May 19, 2023

Coverage Status

Coverage: 52.889% (+0.7%) from 52.167% when pulling 9198918 on iodev into 6b759d7 on master.

Copy link
Member

@dlech dlech left a comment

Choose a reason for hiding this comment

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

Looks good.

It would be nice if we could also extend the uartdev tests to get more coverage of the new code too.

@laurensvalk
Copy link
Member Author

laurensvalk commented May 21, 2023

Todos:


  • [x]

It seems like we should be checking for this in the contiki process instead of here, so that we don't have to keep calling pbio_iodev_is_ready() to keep things moving along.

Originally posted by @dlech in #169 (comment)


  • [x]

This needs an explanation about the alignment rules of data. Is the returned pointer guaranteed to be 4-byte aligned so we can cast directly to uint32_t* (and mention this only works on little endian systems)? Or is alignment not guaranteed and data should be accessed using pbio_get_uint32_le() and friends. In the general case of multi-mode with mixed data types, pbio_get_uint32_le() probably needs to be used in any case.

Originally posted by @dlech in #169 (comment)


  • [x]

Should we raise an error here if mode is out of range?

Originally posted by @dlech in #169 (comment)

Done by getting format first, which asserts number of modes.


  • [x]
void pb_pup_device_get_data(pbio_iodev_t *iodev, uint8_t mode, void **data);

If we make this void** then we don't need to cast every time we call this.

Originally posted by @dlech in #169 (comment)

Had this initially, but that still requires casting to void**.


  • [ ]

Isn't this going to cause undefined behavior for non-UART motors now since pbio_iodev_set_mode() is really now pbio_uartdev_set_mode() (due to CONTAINER_OF)?

Originally posted by @dlech in #169 (comment)

laurensvalk added a commit that referenced this pull request May 21, 2023
@laurensvalk
Copy link
Member Author

There was a note on naming in one of the review comments.

Since uartdev iodev and ioport in their current forms are only for LEGO devices, maybe we could consider:

  • ludev: LEGO UART Device (now uartdev)
  • lpdev: LEGO Passive Device (now ioport_lpf2).

We could drop the iodev abstraction that tries to combine the two. This could perhaps save some more space since it drops mode info of passive devices that is mostly unused.

Names like iodev and ioport may be more suited for generic ports interfaces like in #159.

We could defer this until we actually get to #159 though.

@laurensvalk
Copy link
Member Author

  • Assert ID in get/set calls.

One limitation of the previous version of the code and carried over now, is that we only assert the device ID when instantiating the user device class.

In theory you could instantiate the class, unplug the sensor, plug in something else, and then do a read/write.

Now that everything is consolidated to just a few generic get/set calls, it may be a bit cheaper than before to assert not only the mode but also the ID.

@laurensvalk
Copy link
Member Author

To make it safer, instead of

pbio_error_t pbio_iodev_get_data(pbio_iodev_t *iodev, uint8_t mode, void **data)

We could do:

pbio_error_t pbio_iodev_get_data(pbio_iodev_t *iodev, uint8_t mode, void *data)

And let it explicitly unpack the data based on its type in the mode info.

laurensvalk added a commit that referenced this pull request May 22, 2023
@laurensvalk
Copy link
Member Author

laurensvalk commented May 22, 2023

It is currently probably in a shape where I can start completing the (separate) async PR.

Would you be interested in taking this pull request further if you think that is better?

These were no-ops since pbdrv_uart_write_cancel was still
"//TODO" on all platforms except on Move Hub.

Considering that LEGO UART sensors work fine without it, we
can make the higher level code easier and smaller by by not
calling these no-ops on cancel.
Only set_data is used.
The current iodev and uartdev code will only ever be used for LEGO uart
devices, so we can reduce the level of abstraction and complexity
considerably.

Combined with the previous and next commits, this reduces code size by
about 580 bytes and helps prepare for async functionality later on,
which is the main reason for this refactoring.

The goal it to keep src/uartdev.c completely in charge of uart devices,
instead of relying on user modules correctly calling operations like
set_mode_end.
This further simplifies handling waits/awaits when writing data,
so fewer hacks are needed in the high-level modules, saving code size.
This is more stable and will be cheaper in the async case, since this
does not require both a mode switch and a data setter.
See also dd322df for portions that were previously removed.
This lets pbio/uartdev handle setting data on completing the mode change.

When we get to async later on, this means the async handlers don't need class-specific data buffers or states to remember what phase of mode/data setting they are in.
This way it completes even if the higher level modules never check the ready state.
@dlech
Copy link
Member

dlech commented May 22, 2023

  • lpdev: LEGO Passive Device (now ioport_lpf2).

ioport_lpf2 is mainly the device connection manager for the I/O ports, so should not be renamed to lpdev. But we should be able to drop the iodev layer and just say "if detected device id on the ioport is ... then poke ioport directly else raise ENODEV" for passive devices kind of like we already do for motors.

dlech added 3 commits May 22, 2023 17:44
Returning void* from pb_pup_device_get_data() instead of passing **data
as arg save a bit on code size. Using void* instead of uint8_t*
eliminates the need for explicit casts.

Also always use array access of the returned value for consistency.
Some functions were already moved the from the iodev module to the
uartdev module. This moves the declarations to the correct header and
fixes the namespace prefix in the function names.
This saves some CONTAINER_OF calls by passing the uartdev port_data
pointer directly to private methods instead of passing the iodev
pointer.
The virtualhub doesn't use uartdev currently so was failing when trying
to initialize motors. Code is disable on virtualhub until we can make
a more general solution.
@laurensvalk
Copy link
Member Author

laurensvalk commented May 23, 2023

  • lpdev: LEGO Passive Device (now ioport_lpf2).

ioport_lpf2 is mainly the device connection manager for the I/O ports, so should not be renamed to lpdev. But we should be able to drop the iodev layer and just say "if detected device id on the ioport is ... then poke ioport directly else raise ENODEV" for passive devices kind of like we already do for motors.

Sounds good. I've been working on exactly this, let's see what we can save here...

EDIT: Pushed some drafts to https://github.com/pybricks/pybricks-micropython/tree/iodev-explore This saves a further 200 bytes and goes some way to address several old FIXMEs about not using the port to access things.

I'm not merging that into this PR yet as it may not be needed for now and isn't fully finished (it would be nice to clean up how motor IDs work).

@laurensvalk laurensvalk merged commit 28c95d3 into master May 26, 2023
laurensvalk added a commit that referenced this pull request May 26, 2023
@laurensvalk
Copy link
Member Author

Thanks for the review and additional improvements!

Since several other PRs hinge on this, I've gone ahead and merged it. We can continue to polish in a follow up PR as needed.

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