Sending data back and forth between MPU script and MCU sketch of UNO Q platform

I'm trying to write an app that simply passes a variable to the sketch from main via the bridge and prints it in the sketch console, and also prints the value passed from the main in it's console. I got that working (see code below).

But what I am having a problem with and need help with is passing generated data from a sketch through the bridge backup to the main while printing the values generated in both consoles, to see if it is passed properly.

I will try to get that new non-working code up to the forum when I get it formatted for viewing. Help and suggestions would be appreciated. K

# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0

from arduino.app_utils import *
import time

# Init varible states
led_state = False
com_loop = 0

def loop():
    # Make the variables available to everyone
    global led_state
    global com_loop
    time.sleep(1)
    led_state = not led_state
    # Value of data sent to sketch
    com_loop = com_loop + 1
    print("main loop count: ")
    print(com_loop)
    # Send the data to the sketch
    Bridge.call("set_led_state", led_state, com_loop)

App.run(user_loop=loop)

Python Console Output
Activating python virtual environment
======== App is starting ============================
2026-02-15 03:22:11.337 INFO - [MainThread] App: App started
main loop count:
1
main loop count:
2
main loop count:
3
main loop count:
4

// SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
//
// SPDX-License-Identifier: MPL-2.0

#include "Arduino_RouterBridge.h"

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);

    Bridge.begin();
    // Get data from main
    Bridge.provide("set_led_state", set_led_state);
}

void loop() {
}

// Getting and printing data from main python
// com_loop has the int data to print from main
void set_led_state(bool state, int com_loop) {
    // LOW state means LED is ON
    digitalWrite(LED_BUILTIN, state ? LOW : HIGH);
    Monitor.println(com_loop);
    if(com_loop == 1) {
      Monitor.println("First data passed");
    }
}

Sketch Console Output:
1
First data passed
2
3
4

1. What do you mean by bridge backup?

2. If I have understood your post #1, you want to achieve the following objectives:
(1) To transfer data from MCU (C++ sketch genertaed data) to MPU (Python script is the receiver).

(2) Value passed will be shown on Monitor Console (coupled with sketch) and Python Console (coupled with script) of App Lab.

3. You may try the following hardware setup (Fig-1) and the application (taken from here #1, 6.); where, the MCU acquires data from ADC0-channel and pass it to the MPU using the internal router bridge. If you have no Potentiometer, then you can directly short by a jumper the 3.3V pin with the A0-pin.


Figure-1:

Python Script for MPU (tested)

from arduino.app_utils import *
import time

last_print = 0
interval = 0.1  # seconds between loop checks

# A container to hold the latest ADC value
latest_adc = None

# This function will be called automatically when MCU sends notify
def on_adc(value):
    global latest_adc
    latest_adc = value

# Subscribe to MCU notifications
Bridge.provide("adc_data", on_adc)

def loop():
    global last_print, latest_adc
    current_time = time.time()
    
    # Check every 100ms if there is new data
    if latest_adc is not None and current_time - last_print >= 1:
        voltage = latest_adc * (3.3 / 1023.0)
        print(f"ADC: {latest_adc}, Voltage: {voltage:.3f} V") #shows on: Python console
        last_print = current_time

App.run(user_loop=loop)

Arduino Sketch for MCU (tested)

#include "Arduino_RouterBridge.h"

void setup() 
{
    Bridge.begin(); 
    Monitor.begin();  
    analogReadResolution(10);       
    delay(5000);   //let the bridge hardware be initiated
}

void loop() 
{
    int adc = analogRead(A0);        
    Bridge.notify("adc_data", adc);   
    Monitor.print("Seding data to MPU."); //shows on: Monitor Console
    Monitor.print(adc); Monitor.println(" V");
    delay(1000);                       
}

I strongly suspect that what @kelid meant was

It would make sense in the context of the topic title

Hi @kelid.

From the documentation:

https://docs.arduino.cc/tutorials/uno-q/user-manual/#core-components

  • provide(name, function): Exposes a local MCU function to Linux. Note: The function executes in the high-priority background RPC thread. Keep these functions short and thread-safe.
  • provide_safe(name, function): Exposes a local MCU function, but ensures it executes within the main loop() context. Use this if your function interacts with standard Arduino APIs (like digitalWrite or Serial) to avoid concurrency crashes.

So the code in your set_led_state function is not appropriate for use in a callback specified via Bridge.provide. The most simple solution will be to use Bridge.provide_safe instead. You can accomplish that by changing this line of your sketch:

Bridge.provide("set_led_state", set_led_state);

To this:

Bridge.provide_safe("set_led_state", set_led_state);

The alternative would be doing something like this:

// SPDX-License-Identifier: MPL-2.0

#include "Arduino_RouterBridge.h"

bool setLedStateFlag = false;
bool ledState;
int pyValue;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  Bridge.begin();
  // Get data from main
  Bridge.provide("set_led_state", set_led_state);
}

void loop() {
  if(setLedStateFlag) {
    // Handle data received from Python script.

    // LOW state means LED is ON
    digitalWrite(LED_BUILTIN, ledState ? LOW : HIGH);
    Monitor.println(pyValue);
    if(pyValue == 1) {
      Monitor.println("First data passed");
    }

    setLedStateFlag = false;
  }
}

// Get data from main python
// com_loop has the int data to print from main
void set_led_state(bool state, int com_loop) {
  setLedStateFlag = true;
  ledState = state;
  pyValue = com_loop;
}

Note how the code in the set_led_state callback function is kept very minimal; only assigning the received data to global variables. The code that acts on the received data is placed in the loop function.

1.

If Bridge.provide_safe() is applied to the following sketch of #1 of OP, then what would be the new shape/structure of the sketch? This is for my clear understanding the difference between provide() and provide_safe() methods.

#include "Arduino_RouterBridge.h"

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);

    Bridge.begin();
    // Get data from main
    Bridge.provide("set_led_state", set_led_state);
}

void loop() {
}

void set_led_state(bool state, int com_loop) {
    // LOW state means LED is ON
    digitalWrite(LED_BUILTIN, state ? LOW : HIGH);
    Monitor.println(com_loop);
    if(com_loop == 1) {
      Monitor.println("First data passed");
    }
}

2. Is the callback function (the set_led_state) an Interrupt Service Routine?

3. OP's sketch misses Monitor.begin() in the setup() function. Should it be there?

// SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
//
// SPDX-License-Identifier: MPL-2.0

#include "Arduino_RouterBridge.h"

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);

    Bridge.begin();
    // Get data from main
    Bridge.provide_safe("set_led_state", set_led_state);
}

void loop() {
}

// Getting and printing data from main python
// com_loop has the int data to print from main
void set_led_state(bool state, int com_loop) {
    // LOW state means LED is ON
    digitalWrite(LED_BUILTIN, state ? LOW : HIGH);
    Monitor.println(com_loop);
    if(com_loop == 1) {
      Monitor.println("First data passed");
    }
}

I don't believe so. The function is called from a Zephyr OS thread:

I think it is safest to always call Monitor.begin() in any sketch that uses the Monitor object.

With the current "Arduino_RouterBridge" library's code, it happens that it isn't actually required to call Monitor.begin() in sketches that call Bridge.begin(). However, it is possible that the library code could change over time in this respect.

I want to say -- if loop() thread would contain Monitor.println("Hello') line. then there would be very much chance for a conflict to occur with Moitor.println(com_loop) of the callback function in gaining access to Monitor peripheral.

In the sketch of post #1, the loop() thread is empty; so, there is not much (or not at all) chance for concurrency crash. So, do we really need provide_safe() method here?

The problem is that we start with a simple sketch like this, then add more code over time. If we eschew best practices because they aren't absolutely required in the simple sketch, then that may bite us later with difficult to troubleshoot problems in the more complex sketch of our real project.

For this reason, it is best to follow best practices faithfully.

Thank you for the insightful and motivating guidance.

Thanks for your response to my query. Your code example helped me understand better the bridge library. I'm still mildly confused about the usage and location of Bridge functions (objects?) like Bridge.begin, Bridge.provide, Bridge.call, Bridge.notify, Bridge.put, Bridge.get.

I can find documents explaining bridge functions, unfortunately no table, or figure that shows them used together with an explanation on their use, and which part of the app (main or sketch) to put the bridge library objects into.

I understand What Bridge.begin does, but other library functions even though explained don't really give me a good feeling explanation of use and location.

Main must have to work at least:

  • from arduino.app_utils import *
  • App.run(user_loop=loop)

Sketch must have to work at least:

  • #include "Arduino_RouterBridge.h"
  • Bridge.begin();

My code uses Bridge.call, and Bridge.provide to get data moved around. Mr. ptillisch's code uses Bridge.provide, and Bridge.notify.

All I am trying to do in my included code is generate an integer (simple test case) in main and pass it to the sketch. While also printing it out in main and proving the same data gets passed from main to sketch by printing it out in sketch (works).

Mr. ptillisch's code generates data in the sketch, prints it, then passes it back up to main. While also printing out and proving the data gets passed from sketch to main (works).

I want to make an app that passes data in both directions at the same time. If I can get that working i'll try other forms of data.

Suggestions would be appreciated.

main >> sketch and sketch >> main

My code takes a boolean, and an int and passes it from the main to the sketch.
Prints the  int value from main, passes the value to sketch and increments it then prints it from sketch.

# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0

# Test copy

from arduino.app_utils import *
import time

# Init varible states
led_state = False
com_loop = 0

def loop():
    # Make the variables available to everyone
    global led_state
    global com_loop
    time.sleep(1)
    led_state = not led_state
    # Value of data sent to sketch
    com_loop = com_loop + 1
    print("main loop count: ")
    print(com_loop)
    # Send the data to the sketch
    Bridge.call("set_led_state", led_state, com_loop)

App.run(user_loop=loop)
// SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
//
// SPDX-License-Identifier: MPL-2.0

#include "Arduino_RouterBridge.h"

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);

    Bridge.begin();
    // Get data from main
    Bridge.provide("set_led_state", set_led_state);
}

void loop() {
}

// Getting and printing data from main python
// com_loop has the int data to print from main
void set_led_state(bool state, int com_loop) {
    // LOW state means LED is ON
    digitalWrite(LED_BUILTIN, state ? LOW : HIGH);
    Monitor.println(com_loop);
    if(com_loop == 1) {
      Monitor.println("First data passed");
    }  
}

Mr. ptillisch code example takes data from sketch (prints it from sketch) and prints the data also in main (gets it from sketch).

from arduino.app_utils import *
import time

last_print = 0
interval = 0.1  # seconds between loop checks

# A container to hold the latest ADC value
latest_adc = None

# This function will be called automatically when MCU sends notify
def on_adc(value):
    global latest_adc
    latest_adc = value

# Subscribe to MCU notifications
Bridge.provide("adc_data", on_adc)

def loop():
    global last_print, latest_adc
    current_time = time.time()
    
    # Check every 100ms if there is new data
    if latest_adc is not None and current_time - last_print >= 1:
        voltage = latest_adc * (3.3 / 1023.0)
        print(f"ADC: {latest_adc}, Voltage: {voltage:.3f} V") #shows on: Python console
        last_print = current_time

App.run(user_loop=loop)
#include "Arduino_RouterBridge.h"

void setup() 
{
    Bridge.begin(); 
    Monitor.begin();  
    analogReadResolution(10);       
    delay(5000);   //let the bridge hardware be initiated
}

void loop() 
{
    int adc = analogRead(A0);        
    Bridge.notify("adc_data", adc);
    Monitor.print("Sending data to MPU. "); //shows on: Monitor Console
    Monitor.print(adc); Monitor.println(" V");
    delay(1000);                       
}

"Functions" is the correct term. Bridge specifically is an object, but things like Bridge.begin are functions.

The term you have invented "main" is confusing. Please refer to this as "Python script".

As you are aware, an Arduino App may consist of two distinct components:

  • Python script
  • Arduino sketch

Similarly, there are two completely separate pieces of software that facilitate communication between the App components:

  • The arduino.app_utils.bridge Python module, which is used in the App's Python script
  • The "Arduino_RouterBridge" Arduino library, which is used in the App's sketch code.

These two pieces of software have similar APIs, which may make it easy to get confused between the two. It is important that you understand that they are two separate things, each of which can only be utilized in the code of the relevant App component.

That is incorrect. With the exception of replacing Bridge.provide with Bridge.provide_safe, my code uses the same functions as yours

You are thinking of @GolamMostafa's code:

The difference between Bridge.call and Bridge.notify is explained here:

https://docs.arduino.cc/tutorials/uno-q/user-manual/#core-components

  • call(method, args...): Invokes a function on the Linux side and waits for a result.
  • notify(method, args...): Invokes a function on the Linux side without waiting for a response (fire-and-forget).

Maybe a simple example App will help you:

Python script

# SPDX-License-Identifier: CC0-1.0

import time
from arduino.app_utils import App, Bridge

python_data = 0


def print_sketch_data(sketch_data):
    print(sketch_data)


def loop():
    global python_data
    Bridge.notify("printPythonData", python_data)
    python_data += 1

    time.sleep(1)


Bridge.provide("print_sketch_data", print_sketch_data)

App.run(user_loop=loop)

Sketch

// SPDX-License-Identifier: CC0-1.0

#include <Arduino_RouterBridge.h>

int sketchData = 0;

void setup() {
    Monitor.begin();

    Bridge.begin();
    Bridge.provide_safe("printPythonData", printPythonData);
}

void loop() {
  // Send data to Python script.
  Bridge.notify("print_sketch_data", sketchData);
  sketchData += 1;

  delay(1000);
}

// Print data received from Python script.
void printPythonData(int pythonData) {
    Monitor.println(pythonData);
}

As I said above, that is @GolamMostafa's code, not mine.

A: Let us first get the UNO Q vocabularies:
1. Python vs Arduino (they are programming languages)
2. script vs sketch (they are programs)
3. MPU vs MCU (they are processors)
4. Linux vs Zephyr (they are operating systems)
5. Linux MPU vs Zephyr MCU
6. Pyton Console vs Monitor Console (Fig-1, 2) (they are display devices)

B: Router Bridge
In UNO Q Platform, any message/data originated from MCU goes to MPU via a physical communication path called Router Bridge (Fg-1, 1). After that the message/data goes to USB-C connector of UNO Q Board and then to USB-A connector of PC and then to Pyton Consoleor and Monitor Console.

The programming codes for data transaction between MPU and MCU are in the Arduino_RouterBridge.h Library which contains the following functions (as per documentation) to accomplis data exchange between MPU and MCU (or script and sketch or python and arduino). Please, you mention the funtcion for which you need clarification.

1. Bride.begin()
2. Bridge.call(name, args...)

3. Bridge.notify(name, args...)
The MCU/sketch (or MPU/script) uses this function to inform MPU (or MCU) that it is sending a string type data under the hood name (or tag) and one or more data items under the hood args... (or data...). The MCU (or MPU) does not wait to receive any response from MPU (or MCU). For example: in Fig-1 below, the MCU is notifing the MPU.


Figure-1:

int adcValue = analogRead(A0);
Bridge.notify("adc_data", adcValue);` //tag and data are transmitted in ASCII

At the MPU/script side, there is the following declaration with the same tag name and a function; where the function receives the data coming from MCU.

def on_adc(value):
    global latest_adc
    latest_adc = value

Bridge.provide("adc_data", on_adc);   //tag and function identifiers

When the MPU receives the tag ("adc_data"), the tag matches with the tag of Bridge.provide() function; as a result, the function on-adc is immediately ivoked, and the received data is copied into the varible value.

See #2 for the full codes of the application.

4. Bridge.provide(name, function)
5. Bridge.provide_safe(name, function)

C:


Figure-1: (Conceptual level and may NOT be actual)


Figure-2: (Conceptual level and may NOT be actual)