UNO Q: Overview of Bidirectional Data Transfer between MCU and MPU via Router Bridge

This post aims to describe the sequence of events that occur when a user sends these data items: 0x1234 , 1.5 , Forum from MCU to MPU using Router Bridge.

Readers are welcome to provide comments and suggestions for improving both the technical content and the presentation of this post.

A: MCU ------------> MPU
1. At the MCU side, the user executes the following code to send the tag and the data items:

Bridge.call("get_data", 0x1234, 1.57, "Forum"); //get_data is called tag

2. Data Send Mechanism by MCU
(1) MCU and MPU uses UART port communication link called Router Bridge for data exchange.

(2) The four arguments of Bridge.call("get_data", 0x1234, 1.57, "Forum") are taken care of by the MsgPack.h Library to prepare the following 24-byte Message Frame.

1-byte : Message Marker
9-byte : for string get_data (1-byte String Marke1 + 8 byte for 8 characters)
3-byte : for integer 0x1234 (1-byte Int Marker + 2-byte for 2-byte data)
5-byte : for float 1.57 (1-byte float Marker + 4-byte for IEEE-754 fromatted 32-bit)
6-byte : for string Forum (1-byte String Marker + 5-byte for 5-charcaters)

0x94 : Message Marker

0xA8 : String Marker (0xA0)+Number of characters in string (0x08): get_data
0x67 0x65 0x74 0x5F 0x64 0x61 0x74 0x61 : ASCII code for: g e t _ d a t a

0xCD : Integer Marker (0xCD)
0x12 0x34 : High byte and Low byte of 0x1234

0xCA : Float Marker
0x3F 0xC8 0xF5 0xC3 : 32-bit IEEE-754 formatted data for 1.57

0xA5 : String Marker (0xA0)+Number of characters (0x05) in string: Forum
0x46 0x6F 0x72 0x75 0x6D : ASCII codes for charcaters of F o r u m

(3) The resultatnt 24-byte wide Transmission Frame is: ( (spaces and linefeeds are shown for clarity)
0x94
0xA8 0x67 0x65 0x74 0x5F 0x64 0x61 0x74 0x61
0xCD 0x12 0x34
0xCA 0x3F 0xC8 0xF5 0xC3
0xA5 0x46 0x6F 0x72 0x75 0x6D

(4) The above 24 bytes data are sent by the MCU one-after-another as 10-bit (1-StartBit, 8-dataBit, 1-StopBit) asynchronous frame at Bd = 115200. The total arrival time is about:
(10 x 24) x1/115200 = 2.08 ms.

3. Data Receiption Decoding and Storage by MPU
(1) MPU executes following Python code to receive, decode, and store the 24-byte data received from MCU.

Bridge.provide("get_data", getDataCallback)

(2) Once the matching tag get_data has een detected, the Bridge.provide() mechanism parses the remaining byte stream as they arrive to extract the data items as int, float, and string and save them in the corresponding variables y , z , msg of the getDataCallback() function. After that the control is transferred to the getDataCallback() function to print the variables and to send ack/status to MCU if there is any.

def getDataCallback(y, z, msg): //Python automatically applies data types
   print("\n--- Data Received Successfully ---")
   print(f"y   (int)   : {hex(y)} (Decimal: {y})") #shows: y (int) : 0x1234 (Decimal: 4660)
   print(f"z   (float) : {z:.2f}") #shows: z   (float) : 1.57
   print(f"msg (string): {msg}") #shows: msg (string): Forum

4. Application
C++ Sketch

#include "Arduino_RouterBridge.h"

void setup() {
  // Initialize communication layer
  Bridge.begin(); 
}

void loop() {
  // Fire the notification asynchronously every 5 seconds
  Bridge.call("get_data", 0x1234, 1.57, "Forum");
  Monitor.println("Sending Data to MPU");
  
  delay(5000); 
}

Python Script

import time
from arduino.app_utils import App, Bridge

def getDataCallback(y, z, msg):
        print("==Printing received data at 5-sec interval==")
        print(f"y   (int)   : {hex(y)} ({y})")
        print(f"z   (float) : {z:.2f}")
        print(f"msg (string): {msg}")

# Register the callback function under the "get_data" key
Bridge.provide("get_data", getDataCallback)

def loop():
        print("==Sleeping for 2-sec...!==")
        time.sleep(2)
App.run(user_loop=loop)

B: MPU ----------> MCU
Bridge.call() waits to receive data/message/response from MPU. The returned value is collected by the MCU using the result() method of an object of type RpcCall (a helper class). Here, we will see how this event happens. However, MPU can also initiate Bridge.call() and Bridge.notify() functions to send data to the MCU.

Hints:
At MCU side:

unsigned long prMillis = millis();
bool status = false;  //will turn to true when message comes from MPU
String value = ""; //value will be loaded with message that comes from MPU 

do
{
     RpcCall rpc = Bridge.call("get_data", 0x1234, 1.57, "Forum");
     status = rpc.result(value);
}
while((staus != true) && (millis() - prMillis < 3));  //timeout: 3 ms

if(status == false)
{
   Monitor.println("Communication error!");
   while(true);     //wait for ever
}
else
{
   Monitor.println(value); //shows: Communication is ok!
}

At MPU side:

def getDataCallback(y, z, msg):
        print("==Printing received data at 5-sec interval==")
        print(f"y   (int)   : {hex(y)} ({y})")
        print(f"z   (float) : {z:.2f}")
        print(f"msg (string): {msg}")
        return "Communication is ok!"

Is the above timeout logic ok? I have not yet tested.

If the positive response does not come from MPU within 3 ms, the MCU will timeout and will show error message.

If the positive response comes from MPU, the MCU will not wait for 3 ms; it will immediately time out and then will print ok message.

I suggest you fire up your trusty UNO Q and give it a try!

Thank you for the motivational response. I am going to give a try on UNO Q.

It is surprising that people owning the UNO Qs have no time to react
with critiques on UNO Q related posts!!!

unsigned long prMillis = millis();
bool status = false;  //will turn to true when message comes from MPU
String value = ""; //value will be loaded with message that comes from MPU 

do
{
     RpcCall rpc = Bridge.call("get_data", 0x1234, 1.57, "Forum");
     status = rpc.result(value);
}
while((status != true) && (millis() - prMillis < 20));  //timeout: 20 ms
Monitor.print("Elapsed time: ");  //measuring response time or timeout period
Monitor.print(millis() - prMillis);  Monitor.println(" ms");
 
if(status == false)
{
   Monitor.println("Communication error!");
   while(true);     //wait for ever
}
else
{
   Monitor.println(value); //shows: Communication is ok!
}

I have tested the above logic in UNO Q; the logic does not work. The control never comes out of while() loop as long as && millis() - prMilis < 20 is present in the argument of while(). I have verified, by removing && millis() - prMilis < 20 from while(), that the entire round-trip time is about 15/16 ms.

I would be glad to hear from the readers why mine logic does not work.

It depends on Arduino App Lab or arduino-app-cli internal processing procedures:

  1. After flashing sketch into the bank 1 of MCU, openocd resets MCU and waits 100ms for MCU boot.
  2. python/main.py starts to run with the docker container of python-app-base. It can also be time consuming.
  3. /dev/ttyHS1 data also is intercepted by arduino-router to be transferred to unix socket data for RPC. It also takes time.
  4. getDataCallback function is executed and returns result with RPC
  5. arduino-router sends back the result to /dev/ttyHS1
  6. RpcCall rpc has result

So 3ms wait time is not enough for Bridge.call response.

I think you misinterpreted your observation. I tried it out and found that it does come out of the first while loop as expected, but the problem is that you never see the expected output in the Serial Monitor. So it is actually the second infinite while loop you end up in:

You can test that by using this modified version of the sketch, which produces output while in that second while loop:

#include "Arduino_RouterBridge.h"

void setup() {
  // Initialize communication layer
  Bridge.begin();
}

void loop() {
  unsigned long prMillis = millis();
  bool status = false;  //will turn to true when message comes from MPU
  String value = ""; //value will be loaded with message that comes from MPU 

  do
  {
      RpcCall rpc = Bridge.call("get_data", 0x1234, 1.57, "Forum");
      status = rpc.result(value);
  }
  while((status != true) && (millis() - prMillis < 20));  //timeout: 20 ms
  Monitor.print("Elapsed time: ");  //measuring response time or timeout period
  Monitor.print(millis() - prMillis);  Monitor.println(" ms");
  
  if(status == false)
  {
    Monitor.println("Communication error!");
    while(true)     //wait for ever
    {
      Monitor.println("I'm in the error loop");
    }
  }
  else
  {
    Monitor.println(value); //shows: Communication is ok!
  }
}

If you want to see the initial Monitor output in Serial Monitor, Try adding a delay in the setup function to give the monitor time to initialize:

#include "Arduino_RouterBridge.h"

void setup() {
  // Initialize communication layer
  Bridge.begin();
  delay(5000);
}

void loop() {
  unsigned long prMillis = millis();
  bool status = false;  //will turn to true when message comes from MPU
  String value = ""; //value will be loaded with message that comes from MPU 

  do
  {
      RpcCall rpc = Bridge.call("get_data", 0x1234, 1.57, "Forum");
      status = rpc.result(value);
  }
  while((status != true) && (millis() - prMillis < 20));  //timeout: 20 ms
  Monitor.print("Elapsed time: ");  //measuring response time or timeout period
  Monitor.print(millis() - prMillis);  Monitor.println(" ms");
  
  if(status == false)
  {
    Monitor.println("Communication error!");
    while(true);     //wait for ever
  }
  else
  {
    Monitor.println(value); //shows: Communication is ok!
  }
}

This is the output on Serial Monitor.


I'm in the error loop
I'm in the error loop
I'm in the error loop

The app works when the timeout factor is removed from the while() loop.

Have you tested some very long 1 second timeout like (millis() - prMillis < 1000)?

A: The following app works without timeout factor:
Sketch:

#include "Arduino_RouterBridge.h"
unsigned long prMillis = millis();//micros();
bool status = false;  //will turn to true when message comes from MPU
String value = ""; //value will be loaded with message that comes from MPU 

void setup() {
  // Initialize communication layer
  Bridge.begin();
  Monitor.begin();
  delay(5000);
}

void loop() 
{
  // Fire the notification asynchronously every 5 seconds
  Monitor.println("Sending Data to MPU");
  
  do
  {
     RpcCall rpc = Bridge.call("get_data", 0x1234, 1.57, "Forum");
     status = rpc.result(value);
  }
  while(status != true);// && millis() - prMillis < 20ul);  //timeout: 20 ms

  Monitor.print("Elapsed time: ");  //measuring response time or timeout period
  Monitor.print(millis() - prMillis);  Monitor.println(" ms");
  //prMillis = millis();
 
  if(status == false)
  {
     Monitor.println("Communication error!");
     while(true);     //wait for ever
  }
  else
  {
     Monitor.println(value); //shows: Communication is ok!
  }
  delay(5000);
  prMillis = millis();
}

Script:

import time
from arduino.app_utils import App, Bridge

def getDataCallback(y, z, msg):
        print("==Printing received data at 5-sec interval==")
        print(f"y   (int)   : {hex(y)} ({y})")
        print(f"z   (float) : {z:.2f}")
        print(f"msg (string): {msg}")
        return "Data communication ok!"

# Register the callback function under the "get_data" key
Bridge.provide("get_data", getDataCallback)

def loop():
        print("==Sleeping for 2-sec...!==")
        time.sleep(2)
App.run(user_loop=loop)

Output:
Serial Monitor


Sending Data to MPU
Elapsed time: 15 ms
Data communication ok!
Sending Data to MPU
Elapsed time: 15 ms

Python Cosole

==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
==Sleeping for 2-sec...!==
==Sleeping for 2-sec...!==
==Sleeping for 2-sec...!==
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum

B: The following sketch does not work because it has timeout fator in the while() loop.

#include "Arduino_RouterBridge.h"
unsigned long prMillis = millis();//micros();
bool status = false;  //will turn to true when message comes from MPU
String value = ""; //value will be loaded with message that comes from MPU 

void setup() {
  // Initialize communication layer
  Bridge.begin();
  Monitor.begin();
  delay(5000);
}

void loop() 
{
  // Fire the notification asynchronously every 5 seconds
  Monitor.println("Sending Data to MPU");
  
  do
  {
     RpcCall rpc = Bridge.call("get_data", 0x1234, 1.57, "Forum");
     status = rpc.result(value);
  }
  while(status != true) && millis() - prMillis < 20ul);  //timeout: 20 ms

  Monitor.print("Elapsed time: ");  //measuring response time or timeout period
  Monitor.print(millis() - prMillis);  Monitor.println(" ms");
  //prMillis = millis();
 
  if(status == false)
  {
     Monitor.println("Communication error!");
     while(true);     //wait for ever
  }
  else
  {
     Monitor.println(value); //shows: Communication is ok!
  }
  delay(5000);
  prMillis = millis();
}

I modified the sketch.ino with retry count until 1s timeout. It works:

python/main.py:

import time
from arduino.app_utils import App, Bridge

def getDataCallback(y, z, msg, i):
        print("==Printing received data at 5-sec interval==")
        print(f"y   (int)   : {hex(y)} ({y})")
        print(f"z   (float) : {z:.2f}")
        print(f"msg (string): {msg}")
        print(f"retry count: {i}")

# Register the callback function under the "get_data" key
Bridge.provide("get_data", getDataCallback)

def loop():
        print("==Sleeping for 2-sec...!==")
        time.sleep(2)
App.run(user_loop=loop)

sketch/sketch.ino:

#include "Arduino_RouterBridge.h"

void setup() {
  // Initialize communication layer
  Bridge.begin();
  delay(5000);
}

void loop() {
  unsigned long prMillis = millis();
  bool status = false;  //will turn to true when message comes from MPU
  String value = ""; //value will be loaded with message that comes from MPU

  int i = 0;
  do
  {
      delay(100);
      RpcCall rpc = Bridge.call("get_data", 0x1234, 1.57, "Forum", i);
      status = rpc.result(value);
  }
  while((status != true) && (++i < 10));  //timeout:  1s
  Monitor.print("Elapsed time: ");  //measuring response time or timeout period
  Monitor.print(millis() - prMillis);  Monitor.println(" ms");

  if(status == false)
  {
    Monitor.println("Communication error!");
    while(true);     //wait for ever
  }
  else
  {
    Monitor.println(value); //shows: Communication is ok!
  }
}

result:

==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 0
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 1
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 2
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 3
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 4
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 5
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 6
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 7
==Sleeping for 2-sec...!==
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 8
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
retry count: 9

There is no timeout factor. I wish that the following construct might work

do
{
   //----- code here---------------
}
while(status != true) && millis() - prMillis < 20ul);  //timeout: 20 ms

If that does not work -- that's fine; but, we need to know the reason -- is the logic wrong as it is not compatible with the mecanism of how Router Bridge works?

main.py and sketch.ino are slightly modified and run with Arduino App Lab in PC:|

python/main.py

import time
from arduino.app_utils import App, Bridge

def getDataCallback(y, z, msg):
        print("==Printing received data at 5-sec interval==")
        print(f"y   (int)   : {hex(y)} ({y})")
        print(f"z   (float) : {z:.2f}")
        print(f"msg (string): {msg}")
        return True

# Register the callback function under the "get_data" key
Bridge.provide("get_data", getDataCallback)

def loop():
        print("==Sleeping for 2-sec...!==")
        time.sleep(2)
App.run(user_loop=loop)

sketch/sketch.ino

#include "Arduino_RouterBridge.h"

void setup() {
  // Initialize communication layer
  Bridge.begin();
  delay(5000);
}

int i = 0;
void loop() {
  unsigned long prMillis = millis();
  bool status;  // will turn to true when message comes from MPU
  bool result;  // MPU returns True
  
  do
  {
      RpcCall rpc = Bridge.call("get_data", 0x1234, 1.57, "Forum");
      status = rpc.result(result);
  }
  while((status != true) && (millis() - prMillis) <100);  //timeout:  100ms
  Monitor.print("Elapsed time: ");  //measuring response time or timeout period
  Monitor.print(millis() - prMillis);  Monitor.println(" ms");
  
  if(status == false)
  {
    Monitor.println("Communication error!");
    while(true);     //wait for ever
  }
  else
  {
    Monitor.println(result ? "True" : "False"); //shows: Communication is ok!
  }
}

Python

...
=Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
==Printing received data at 5-sec interval==
y   (int)   : 0x1234 (4660)
z   (float) : 1.57
msg (string): Forum
======== App shutdown completed =====================
2026-05-29 15:39:13.110 INFO - [MainThread] App:  App is shutting down

Serial Monitor

...
Elapsed time: 14 ms
True
Elapsed time: 12 ms
True
Elapsed time: 13 ms
True
Elapsed time: 13 ms
True
Elapsed time: 13 ms
True
Elapsed time: 14 ms
...

@msyang

You have fixed it, but the congratulation is deferred until you tell me if you have fixed it by trial-and-error or you have found the real cause.

I clarified the return type of Python and sketch. The getDataCallback function in main.py returns True and RpcCall checks result with booltype.

Before applying modification there are many retries even if python getDataCallback has been called. It was caused by rpc.result(String value)

@msyang

Congratulations!

However, you have not answered to none of my two questions I raised in post #15.

So, your fixaton appears to be tentative and volatile; yet, it has served as the basis for my extensive investigation into the actual reason why my application is not working with timeout .

Hi @GolamMostafa, thank you for posting this overview.
I have some correction on the packet serialization.
The Bridge uses the rpclib standard for the RPCs which in turn uses msgpack serialization under the hood.

So after the 0x94 byte, that is the 4-elements array descriptor, a RPC type byte follows (0-call, 1-response, 2-notify), then most importantly the message ID integer. This can be as small as a single byte, but can expand to several bytes for big numbers. Based on the implementation MsgPack can be optimized to use expanded types the bigger the number gets.
Then, after the message ID, you have the method string (starting with 0xA8 for a 8-char method name as you pointed out)
Everything after the method string, ie the method parameters, must be packed inside a N elements array.
So, in your case the integer 0x1234 is preceded by a 0x93 (array3) descriptor, since you're passing 3 parameters to the get_data callback

The Message Frame I presented was based on information supplied by Gemini. Thank you very much for your post, which comes from a member of the Arduino Development Team. I can rely on it to revise and correct my original Message Frame.

As I have not yet fully understood every aspect of your post #18, I may need to ask forum members and read your linked documents for further clarification and examples.

Actually timeout is not used when status is true which means getDataCallback function was called successfully without RPC timeout error.