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

Allow for multiple builtin program including Port View #254

Closed
wants to merge 16 commits into from

Conversation

laurensvalk
Copy link
Member

@laurensvalk laurensvalk commented Jul 25, 2024

This is a PR against hostbuffer to make reviewing easier. These are separate features, but it builds on that one.


This adds an optional 32-bit little endian identifier payload to the START_USER_PROGRAM and START_BUILTIN_PROGRAM (formerly START_REPL).

The REPL becomes (remains) builtin program 0. Port View will be builtin program 1. These are defined at the application level (micropython.c) rather than at the pbio level. The main purpose for now is to enable Port View support. Future builtin programs can be gyro calibration routines (#252), etc.

User programs get an identifier payload as well, so we can properly start distinct user programs if we ever have them. Currently only index 0 is allowed for user programs.

@dlech : Should these preferably be new commands instead, or is it good as above? I don't mind changing it either way.


Can be tested with Pybricks Code by modifying createStartReplCommand follows:

export function createStartReplCommand(): Uint8Array {
    const msg = new Uint8Array(5);
    const view = new DataView(msg.buffer);
    view.setUint8(0, CommandType.StartRepl);
    const programID = 1; // HACK: Test to start port view. This should become an optional argument to this function.
    view.setUint32(1, programID, true); 
    return msg;
}

@laurensvalk
Copy link
Member Author

@afarago: This already includes a placeholder for port view in bricks/_common/modules/_builtin_port_view.py and has the AppData (HostBuffer) stuff, so it gets a bit closer to what we'd need for Port View. So in principle you can add your Python code there, and use the additional payload discussed above to start it almost instantly.

If it's OK with you, I'd like to give implementing _builtin_port_view.py a go as well, but I'll stick with your emitted format for now.

I would be happy to leave the Pybricks Code implementation side more to you and David, since that isn't my expertise. I'll post some visual suggestions in an issue, but we can start by combining our existing development and synchronize on a call. Thanks!

@afarago
Copy link
Contributor

afarago commented Jul 25, 2024

@afarago: This already includes a placeholder for port view in bricks/_common/modules/_builtin_port_view.py and has the AppData (HostBuffer) stuff, so it gets a bit closer to what we'd need for Port View. So in principle you can add your Python code there, and use the additional payload discussed above to start it almost instantly.

If it's OK with you, I'd like to give implementing _builtin_port_view.py a go as well, but I'll stick with your emitted format for now.

I would be happy to leave the Pybricks Code implementation side more to you and David, since that isn't my expertise. I'll post some visual suggestions in an issue, but we can start by combining our existing development and synchronize on a call. Thanks!

Sure, glad to see it happening.
I have recently experimented with the AppData, but feel free to implement an optimized version, this week I am anyhow unwell.
As I understand it will reside on the hub so I assume it should be as small as possible and maybe already in mpy?

https://gist.github.com/afarago/350a931992e05c423860f4b25ea5c6a2

@laurensvalk
Copy link
Member Author

laurensvalk commented Jul 25, 2024

As I understand it will reside on the hub so I assume it should be as small as possible and maybe already in mpy?

This PR ensures that the indicated file is built into the firmware as frozen bytecode. Size is a factor, though primarily on City/Technic Hub. Move Hub isn't going to have this feature at all.

The "on-brick" component is needed on Prime/Inventor Hub only, where we have some more space.

@coveralls
Copy link

coveralls commented Jul 25, 2024

Coverage Status

coverage: 55.961% (-0.06%) from 56.025%
when pulling df60572 on builtin-programs
into 53a60f9 on hostbuffer.

@laurensvalk
Copy link
Member Author

laurensvalk commented Jul 26, 2024

I've added a preliminary port view script. It generates output of the form discussed here.

It uses AppData to send telemetry to Pybricks Code and accepts mode info via AppData too.

Tested on Technic Hub and Prime Hub. Due to an limitation with the Technic Hub, messages are concatenated and split in chunks of at most 19 bytes. They can be put together in Pybricks Code and split at new lines to get the correct information.


If you just want to do a sanity check to make sure everything is working before you make any changes to Pybricks Code, just change the following before you build the firmware:

diff --git a/bricks/_common/micropython.c b/bricks/_common/micropython.c
index f43b804cf..785d44d83 100644
--- a/bricks/_common/micropython.c
+++ b/bricks/_common/micropython.c
@@ -302,12 +302,12 @@ typedef enum {
     /**
      * The MicroPython REPL.
      */
-    PYBRICKS_MICROPYTHON_BUILTIN_PROGRAM_ID_REPL = 0,
+    PYBRICKS_MICROPYTHON_BUILTIN_PROGRAM_ID_REPL = 1,
     /**
      * Program that detects attached devices, displays sensor values, and
      * relays sensor data to host if connected.
      */
-    PYBRICKS_MICROPYTHON_BUILTIN_PROGRAM_ID_PORT_VIEW = 1,
+    PYBRICKS_MICROPYTHON_BUILTIN_PROGRAM_ID_PORT_VIEW = 0,
     /**
      * The number of builtin programs.
      */
diff --git a/pybricks/tools/pb_type_app_data.c b/pybricks/tools/pb_type_app_data.c
index 89dcdffbc..b994156a3 100644
--- a/pybricks/tools/pb_type_app_data.c
+++ b/pybricks/tools/pb_type_app_data.c
@@ -115,7 +115,7 @@ STATIC mp_obj_t pb_type_app_data_make_new(const mp_obj_type_t *type, size_t n_ar
     app_data_instance->tx_context.done = NULL;
     app_data_instance->tx_context.connection = PBDRV_BLUETOOTH_CONNECTION_PYBRICKS;
     app_data_instance->tx_context.data = app_data_instance->tx_buffer;
-    app_data_instance->tx_buffer[0] = PBIO_PYBRICKS_EVENT_WRITE_APP_DATA;
+    app_data_instance->tx_buffer[0] = PBIO_PYBRICKS_EVENT_WRITE_STDOUT;
 
     return MP_OBJ_FROM_PTR(app_data_instance);
 }

This makes Port View builtin program 0, so you can just start it with the existing REPL >>> button for now. And it prints the hub data to stdout so you can see that it does in fact work before you begin processing it as app data.

@laurensvalk laurensvalk changed the title Builtin programs Allow for multiple builtin program including Port View Jul 26, 2024
@laurensvalk
Copy link
Member Author

This is working now, and could be used for Pybricks Code experiments 😃

@dlech
Copy link
Member

dlech commented Jul 26, 2024

This adds an optional 32-bit little endian identifier payload to the START_USER_PROGRAM and START_BUILTIN_PROGRAM (formerly START_REPL).

Yes, I think that is fine. We will also need to bump the protocol version and document the changes at https://github.com/pybricks/technical-info/blob/master/pybricks-ble-profile.md.

these are defined at the application level (micropython.c) rather than at the pbio level.

Since these are going over the air, they should be part of the protocol and not application-specific. With a 32-bit field, we will never run out of slots anyway.


Idea: if we make the index fields 1 byte, we could also send that info back in the periodic status update event so that Pyrbicks Code knows exactly which program is running, even when started by buttons on the hub.

I haven't thought too much about the Pybricks Code side of things yet but we may need something like this to make sure things work correctly.

@laurensvalk
Copy link
Member Author

Since these are going over the air, they should be part of the protocol and not application-specific. With a 32-bit field, we will never run out of slots anyway.

I suppose I was thinking of them similar to variable user programs, where the host just requests to start a particular index and get an error if it isn't available. And to avoid having any kind of MicroPython references in pbio.

But I can see that specifics could be useful and indeed with a 32 bit field we should not run out.

The thinking behind 4 bytes instead of 1 was to keep a few options open since we haven't settled on a UI or multi-file protocol yet. For example, the identifier might encode a 25-bit display icon for a particular program or builtin. Or maybe it could include a checksum, so that the program can only start if its contents correspond to its identifier.

Idea: if we make the index fields 1 byte, we could also send that info back in the periodic status update event so that Pybricks Code knows exactly which program is running, even when started by buttons on the hub.

I like it. So we would include that in pbio_pybricks_status_t?. Yes, that makes a good case for 1 byte.

@afarago
Copy link
Contributor

afarago commented Jul 27, 2024

some

Awesome progress, I see that there is so much going on it is better to wait for things to settle before moving forward with the code on both sides.

As a first remark with _builtin_port_view.py:
Due to the infinite loop within the update_*_sensor subroutines the mode update is not taken into account.
Would suggest a separate function instead of a parameter:

def get_mode(port):
    return app_data.get_values()[ports.index(port)]

...

def update_color_and_distance_sensor(port, type_id):
   ...
    while True:
        mode = get_mode(port)

@laurensvalk
Copy link
Member Author

Thank you, I'll fix that.

Feel free to experiment, but we can definitely try to schedule a chat between all of us to catch up.

I've not done much exploring on the Pybricks Code side, so I'll be curious to see how that might work.

@dlech
Copy link
Member

dlech commented Jul 27, 2024

So we would include that in pbio_pybricks_status_t?

No. We would add a new "program ID" parameter to pbio_pybricks_event_status_report(). (side note, it could be nice to add a battery percent to this while we are touching it)

Also, we should probably rename PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING to PBIO_PYBRICKS_STATUS_PROGRAM_RUNNING because it really means user or builtin program running (and changing that meaning would break things).

Then we likely also need a new flag in pbio_pybricks_status_t to differentiate between user and builtin programs running if we just add one "program ID" parameter to pbio_pybricks_event_status_report().


Another option could be to just keep calling everything a "user program" and have, e.g. the msb of the program ID byte be a flag indicating if it is a user "slot" or a builtin program. In this case, we could deprecate START_REPL and just add the extra arg to START_USER_PROGRAM.

@laurensvalk
Copy link
Member Author

Another option could be to just keep calling everything a "user program" and have, e.g. the msb of the program ID byte be a flag indicating if it is a user "slot" or a builtin program. In this case, we could deprecate START_REPL and just add the extra arg to START_USER_PROGRAM.

👍

Then we can keep the START_REPL command working as is for backwards compatibility, which will just be cast to builtin program 0.

@laurensvalk
Copy link
Member Author

Another option could be to just keep calling everything a "user program" and have, e.g. the msb of the program ID byte be a flag indicating if it is a user "slot" or a builtin program.

Right. By this terminology, builtin programs are "builtin user programs".

@dlech
Copy link
Member

dlech commented Aug 23, 2024

Idea: if we make the index fields 1 byte, we could also send that info back in the periodic status update event so that Pyrbicks Code knows exactly which program is running, even when started by buttons on the hub.

In addition to knowing which program is running, we will also want to know what programs are available (e.g. so that the Pybricks UI can be smart about only enabling buttons if a feature is actually available). I just remembered that we already have pbio_pybricks_feature_flags_t for this. It already has a flag for the REPL. So we should add a new flag for the port view program to this.

@laurensvalk
Copy link
Member Author

This PR currently addresses that by generalizing the meaning of that flag to supporting builtin programs or not.

But flags per program would indeed give a bite more refined control. At the moment, it returns an error if the selected builtin is not available.

@dlech
Copy link
Member

dlech commented Aug 23, 2024

But flags per program would indeed give a bite more refined control. At the moment, it returns an error if the selected builtin is not available.

Yeah, it would be best if we could know before actually trying to run the program.

This adds the PB_ namespace to FOR_EACH_IDX. This avoid conflicts with
other libraries (e.g. zephyr) that may use FOR_EACH_IDX.
gc_collect() can take a long time to run (milliseconds) which means
that it isn't possible to get smaller loop times than that.

This was here in the first place to help prevent fragmentation, but
there are alternative solutions, like setting gc.threshold() or manually
running gc.collect() in an application that can handle the delay.
This fixes deprecation warnings in the GitHub UI when running actions.
This fixes deprecation warnings when running the actions.
pbio_int_math_bind() already ensures that time >= 0, so we don't need
to clamp it again later.
dlech and others added 11 commits August 24, 2024 15:22
Recently, we changed to have uncrustify installed using Poetry, so we
no longer need to install it manually.

Also recommend installing the Ubuntu ARM GCC package.
Since removing the gc_collect() from the run_task() loop, when setting
loop_time_ms to 0, it was possible that the contiki events were never
getting handled, causing random crashes due to watchdog timeout or
UART device failure.

This adds MICROPY_VM_HOOK_LOOP in the loop to ensure that background
events are always being handled even when running in a tight loop.

Fixes: 5a0c7d5 ("pybricks/tools/pb_module_tools: remove gc_collect() from run loop")
This wasn't having the desired effect, and would cause loop round trips to take 10 ms for every await wait(1). Passing the argument is still allowed for backwards compatibility.
This brings the waiting process for the main program to start to the
main.c loop, which makes it easier to follow.

This also generalized the commands to start builtin and user programs
with an optional identifier. For now, this will be used to start either the
REPL or Port View. In the future, it can be used to start programs on
different slots.

This also adds a hook for verifying the program at the application level
so we can verify MicroPython-specific validity and avoid hardcoding such
checks at the pbio level.

pbio/sys/config: Consolidate app config.

The separate placement of these settings was a bit contrived. We can have it
at the pbsys config level since the definitions exist at the pbio protocol level anyway.

This lets us use them as defined feature guards.
The City/Technic Hub currently do not support messages longer than 19 bytes.
This makes it clearer that things such as "user program running" also
applies to builtin programs, which are also user programs.
This will let us include it in the hub status for
monitoring by Pybricks Code.

Use it for both downloaded and builtin user
programs.

Also restore the original START_REPL and START_PROGRAMS, and instead
introduce a new command for starting both builtin and downloaded user
programs. The previous modifications had not been released yet.
@laurensvalk
Copy link
Member Author

This was accidentally closed due to the opposing branch being removed. I'll re-submit when the above suggestions are incorporated.

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.

5 participants