I have a working PTF test written in Python, using the P4Runtime API, and a corresponding P4 program, such that there are times when the controller sends a PacketOut message to the switch (which it always received on a dedicated CPU port, not a regular switch port. In those tests the CPU port is numbered 510, but this is configurable on the simple_switch_grpc command line).
It also contains other test cases where the switch sends a message to the dedicated CPU port, and those become PacketIn messages to the controller.
It uses some Python functions in the directory testlib
that are common to some other PTF tests I have written, which are important to read and understand, too. You can focus on only the functions in testlib
that are actually called by the PTF test and ignore the rest, since not all of them are needed by this PTF test.