Return to Home Page

This page talks about the PicoToPi and PiToPico messages.

Inter-device Communications Framework

An IBus Packet looks like this: Packet format

The Source and Destination device IDs have been documented on the internet before: WilhelmDocs

I noticed I could borrow the following device IDs

/* Borrow the two MINI D-BUS addresses. */
#define PICO_VALUE 0x31
#define PI_VALUE 0x81

and then put any arbitrary binary message within the data field, and have my own private pico<->pi communications protocol without having to add extra serial or i2c lines on the board! Better still, I could re-use all the existing parsing logic and infra to make it work with minimal changes.

Protobuf

The natural choice for a “small binary format” is protobuf. On the Pico, I used NanoPb with NanoPb_cpp. On the Pi, I used the Google Protobuf implementation for Kotlin/JVM.

Schema

Both pieces of software know of both PicoToPi.proto and PiToPico.proto message formats.

message PiToPico {
  //TODO Someday this could be a union proto type, but not this release.
  enum MessageType {
    EmptyMessage = 0;
    HeartbeatRequest = 1;
    HeartbeatResponse = 2;

    ConfigStatusRequest = 3; //Instruct the pico to tell us what it's config object is
    ConfigPush = 4; //Tell the pico what it's new config object should be

    PicoVideoRequestUpstream = 5; //Ask the pico to show upstream (for Back to BMW function)
    PicoVideoRequestPico = 6; //Ask the pico to show the debug menu
    PicoVideoRequestRpi = 7; //Ask the pico to show the RPi.
    PicoVideoRequestRVC = 8;

    //For test purposes to toggle the power switch for the RPI power supply.
    PicoPowerRequestOn = 9;
    PicoPowerRequestOff = 10;

    //Allow us to simulate ignition events without a GM or LCM or ignition switch
    SimulatedIgnitionPosition0 = 11;
    SimulatedIgnitionPosition1 = 12;
    SimulatedIgnitionPosition2 = 13;
    SimulatedIgnitionPosition3 = 14;
  }

  MessageType messageType = 1;

  ConfigProto newConfig = 2;
}

and

message PicoToPi {
  enum MessageType {
    EmptyMessage = 0;
    HeartbeatRequest = 1;
    HeartbeatResponse = 2;

    //For the logger on the pico to shuttle to Rpi.
    LogStatement = 3;

    //Dump the config object
    ConfigStatusResponse = 4;

    PiSoftPowerRestartX = 5; //Ask the RPI to restart the X server
    PiSoftPowerRestartPi = 6; //Ask the RPI to legit restart
    PiSoftPowerShutdown = 7;
  }

  MessageType messageType = 1;

  oneof body {
    ca.stefanm.e39.proto.ConfigProto configMessage = 2;
    string loggerStatement = 3;
  }
}

Message Categorization

Each direction of message (PiToPico, PicoToPi) contains a MessageType enum that allows the parser logic to switch on the enum value and perform a different action. Reading through some of the enum values, you can see that some messages (ConfigStatusRequest/ConfigStatusResponse for example) make up a two-way request/response protocol.

Heart-beat request/response

There are two types of request/response flows. They can be triggered via the PicoComms test screen in e39-rpi. The observers and writers for each of these are meant to be the barebones test that the stack works.

A tester app/device can spy and inject messages on the bus as either participant.

Pico initiated

sequenceDiagram participant Pico participant Pi Pico->>Pi: PicoToPi.HeartbeatRequest Pi->>Pico: PiToPico.HeartbeatResponse

Pi initiated

sequenceDiagram participant Pico participant Pi Pi->>Pico: PiToPico.HeartbeatRequest Pico->>Pi: PicoToPi.HeartbeatResponse

One-way peripheral messages (PiToPico)

These messages are for sending a message to the Pico to make it do something with its peripherals for test, or business logic purposes.

Video Switch

These messages

    PicoVideoRequestUpstream = 5; //Ask the pico to show upstream (for Back to BMW function)
    PicoVideoRequestPico = 6; //Ask the pico to show the debug menu
    PicoVideoRequestRpi = 7; //Ask the pico to show the RPi.
    PicoVideoRequestRVC = 8;

dispatch a call through Observer to the VideoSwitch

Simulated Ignition (PiToPico)

These messages

    //Allow us to simulate ignition events without a GM or LCM or ignition switch
    SimulatedIgnitionPosition0 = 11;
    SimulatedIgnitionPosition1 = 12;
    SimulatedIgnitionPosition2 = 13;
    SimulatedIgnitionPosition3 = 14;

dispatch a call to Observer

One-way power request (PicoToPi)

These messages

    PiSoftPowerRestartX = 5; //Ask the RPI to restart the X server
    PiSoftPowerRestartPi = 6; //Ask the RPI to legit restart
    PiSoftPowerShutdown = 7;

end up in an observer on the Pi.

Configuration Request/Response

message PicoToPi {
  enum MessageType {
    ...
    //Dump the config object
    ConfigStatusResponse = 4;
    ...

and

message PiToPico {
  enum MessageType {
    ...
    ConfigStatusRequest = 3; //Instruct the pico to tell us what it's config object is
    ConfigPush = 4; //Tell the pico what it's new config object should be

make up the cycle to bring the Configuration object back and forth between the Pi and the Pico.

The Configuration object contains some parameters stored in the Pico’s flash memory for feature flags to use on bootup.

Logging

The coolest part about the whole setup is that Log messages from the Pico can get sent to the Pi.

message PicoToPi {
  enum MessageType {
    ...
    LogStatement = 3;
    ...
  }

  MessageType messageType = 1;

  oneof body {
    ca.stefanm.e39.proto.ConfigProto configMessage = 2;
    string loggerStatement = 3;
  }
}

This is detailed here: Inter-device Logger

Test tools

The HMI has a Pico Comms debug window. This window lets you:

Comms Debug Window 1

Comms Debug Window 2

Comms Debug Window 3

Return to Top