UNO Q: One board, four languages?

So, to use the Uno Q to its full extent, I'd use C/C++, Python, HTML and Javascript, right? Long time ago I wrote PHP scripts into HTML code. I liked that concept. Things happened on the server side, not in the user's browser. If we want dynamic web pages to control the Uno Q, would it be possible to use PHP instead of Javascript? On the Q, there is some kind of a web server, right? It could possibly run PHP. Then again, that wouldn't get rid of Javascript, would it? Would Javascript still be the interface language between the Python script and the web page?

Don't forget there are more than four languages available. One of the most powerful is assembler. It is not the easiest to learn, but it gives you direct access to every bit of the hardware instead of hiding it behind layers of abstraction. Even if you never write a complete project in assembly, understanding it will make you a better embedded programmer.

That would be two more languages. The MPU and the MCU probably have different assemblers. The last assembler I learned was Z80 and that's not helping.

Since the Q runs a linux variant, you probably have access to most of the languages available under linux. Which is more languages than any one person is likely to be able to list. But: Rust, Go, SmallTalk, LISP, ForTran, COBOL, BASIC, assorted scripting languages (bash, csh, lua, tcl, perl...)
See: https://wiki.debian.org/ProgrammingLanguage
(probably the limiting factor is filesystem space and RAM memory. Some of those languages have huge libraries and runtime environments.)

Sure we can add as many languages as we want. I was actually hoping to use as few as possible. But it seems that I need these four languages to make the Uno Q work as intended. Well, HTML is a language (see the L there?), though not a programming language.

I'm using the Uno Q for a big project of mine, it will be perfect for that. But for some preliminary tests I used Unihiker from DFRobot. Like Q, it runs a Linux. And I could run a perfect test program using only Python. And that included a graphic UI and some GPIO control. So, the Linux handled the GPIO quite well via the Python script. Well, almost. I controlled a single servo with one GPIO pin. Then I tried to control 16 servos through a PCA9685 and I2C. Now the Unihiker failed. But that was more of a library mismatch. It still proved that you can handle a complex thing with GPIOs and a GUI using only one sketch or script.

But I guess if Uno Q gains popularity, we will soon have libs and whatnot, which will accomplish the same. A project template would let you write your code in Linux (either Python or C/C++), you use GPIO (and I2C and whatnot) calls in same code, you build your GUI in same code. Under the hood, the code communicates with an MCU sketch which handles the actual pins, and it communicates with a web page, which you built up using the GUI functions in your code, not by writing a separate html file with parts in Javascript. And as an alternative to the web page GUI, the Q could build the same GUI as HDMI/Displayport output to the USB-C port.

If you have any working sketch where the MPU uses python script to send data itmes like: 12.7, "Green" to MCU over the UART Port based Arduino Router Bridge, please post it.

Well, I haven't tested this yet, but I imagine this would work:

MCU (C/C++):

Bridge.provide("activate_servo", activate_servo);

...

void activate_servo(int note, int angle)
{
    int duty;
    duty = map(angle, 0, 180, 205, 410);
    pca[note / 16]->setPWM(note % 16, duty);
}

MPU (Python):

Bridge.call("activate_servo", 2, 90)

From the Python script I call the function with two ints as parameters. The parameter list might work even with floats and strings. Or at least floats.

If your goal is to become an expert in as few languages as possible, consider running an AI coding agent on the Q. For example, OpenCode runs on the Uno Q since Uno Q runs Debian Linux system. Claude code probably works also but I have not tried it.

Is the arg1 = 2 is the DPin-2 to attach the Servo? And the arg2 = 90 is obviously the angular rotation of the Servo.

Would appeciate if you can test the complete app on your UNO Q and post the script and sketch.

My code is from my project including 41 servos through three PCA9685 boards. The arg1=2 refers to the third servo out of 41 servos. So that argument goes through the Bridge all the way to the activate_servo() function in the MCU sketch, where the right PCA9685 board and the right port is picked. The angle is also mapped to a value of duty, where the total PWM range is 0 - 4095, but the meaningful range for the 180 degree servo is 205 - 410.

I've used AI to help me understand the Bridge. It told me that I could use more than one argument, as long as the types are basic, like int and float. It mentioned strings, too. It even mentioned that complex datatypes should be coded to strings to be passed. But so far I even doubt that the string works. And by string, I mean the simplest form of char* in C, not the String class from C++ or Arduino. The problem is that strings are pointers, pointing to memory space, and the Python script and the C/C++ sketch don't live in the same memory space. In the best of worlds, the Bridge would take care of copying data to really pass the data that we want to get passed.

Thank you for the explanation. Some simple code could help. For example, I use the following app to pass data items 1234, 12.37, and "Green" from MCU to MPU.

sketch:

#include<Arduino_RouterBridge.h>

void setup() {
  Bridge.begin();
  delay(5000);
}

void loop() {
  Bridge.call("get_data", 1234, 12.37, "Green");
  delay(1000);
}

script:

import time

from arduino.app_utils import App, Bridge

print("Hello world!")

def getData(y, z, msg):
    print(y)
    print(z)
    print(msg)
    print("----------------------")

def main():
    Bridge.provide("get_data", getData)
    
    while True:
       time.sleep(10)

if __name__ == "__main__":
    main()

Output:

----------------------
1234
12.37
Green
----------------------
1234
12.37
Green
----------------------

The Arduino 'String' object works fine in a sketch, to pass strings to Python over the Bridge.

And you can encode multiple data types into a String to transfer data.

I just got this thing working with the following code:

MPU:

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

from arduino.app_utils import App, Bridge
import time

def linux_started():
  return True

Bridge.provide("linux_started", linux_started)

while True:
    Bridge.call("activate_servo", 18, 60)
    time.sleep(1)
    Bridge.call("activate_servo", 18, 120)
    time.sleep(1)


MCU:

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

#include <Arduino_RouterBridge.h>
#include <PCA9685.h>

PCA9685 *pca[3];

void setup() {

    Wire.begin();
    for (int i = 0; i < 3; i++)
    {
      pca[i] = new PCA9685(0x40 + i);
      pca[i]->begin();
      pca[i]->setFrequency(50);
    }
    Serial.begin(9600);
  
    Bridge.begin();
    delay(2000);
    boolean start = false;

    Bridge.provide("activate_servo", activate_servo);

    // Wait until the python is started
    while(!start)
    {
      Bridge.call("linux_started").result(start);
    }
  
}

void loop() {
  Serial.println("Waiting...");
  delay(1333);
}

void activate_servo(int note, int angle) 
{
    int duty;
    duty = map(angle, 0, 180, 205, 410);
    pca[note / 16]->setPWM(note % 16, duty);
    Serial.println(duty);
}

The MCU sketch has a loop printing "Waiting..." every 1.33 second. The MPU script has a loop doing the calls every second. The called function takes the two ints as arguments. First (18) picks the third servo from the 2nd bank, second sets the angle. The arguments are passed just fine, the servo, connected to port #2 on the 2nd PCA9685 board ticks back and forth.

Awesome! We just need to make a call with the proper set of arguments. And in setup, the line Bridge.provide("activate_servo", activate_servo); doesn't care about the arguments. It only includes the reference to the activate_servo function. The function itself has of course the correct arguments:

void activate_servo(int note, int angle)

Here's my setup:

That's handling two languages. Now I need HTML and Javascript to create the GUI. I'm connecting this to an accordion and the idea is to have 41 servos press the keys of the accordion. At this point, I need to adjust the angles for each servo and key. The better GUI, the easier calibration.

For the above, I use the following app.

Sketch:

#include<Arduino_RouterBridge.h>
String value = ""; //value is updated by Linux at MPU
bool success = false;
int data;
  
void setup() 
{
  Bridge.begin();

  // Wait until the python is started
  do
  {
      RpcCall rpc = Bridge.call("linux_started");
      success = rpc.result(value);  //any value from MPU makes status = true
  }
  while(success != true);   //true = 1 or false = 0, value = any value coming
  Monitor.begin();
  Monitor.println(value); //shows: Linux has booted...!
 }

void loop() 
{
  data++;
  Bridge.notify("python_func", data);
  delay(1000);
}

Sketch:

Script:
from arduino.app_utils import *
import time

def linuxStarted():
   return "Linux has booted...!"  

def python_func (data: int):
   print(data)

def main():
   Bridge.provide("linux_started", linuxStarted)
   Bridge.provide("python_func", python_func)
    
   while True:
       time.sleep(1)

if __name__ == "__main__":
    main()

Well, it appears Streamlit handles exactly that! I can obviously skip html and Javascript and use Streamlit to build the GUI by writing Python code.

Then again, Streamlit turns the whole concept around all of a sudden. This is how AI described it, when I asked for clarification:

Let me get this straight. If I have the MCU part with its C/C++ sketch, and I have the MPU part with its Python sketch, and I have the Web page doing the GUI stuff with Streamlit, the actual event loop is the Streamlit engine, which only uses the Python as kind of subroutines.

Yes, you have hit the nail on the head. That is exactly how the architecture works, and it is the most common trap developers fall into when using Streamlit.

Streamlit completely flips the traditional script of how Python programs run.

The Streamlit Mental Model

In a normal Python script, your code controls the event loop. In Streamlit, Streamlit controls the event loop, and your Python script is essentially one giant subroutine that gets executed from top to bottom every single time something changes on the web page. [1, 2, 3]

Because I have some time critical parts in my program, both in the Python script and the MCU sketch, Streamlit can allow the Python script to have some functions set on a timer to be run on their own, not part of the "subroutine call" of the whole Python script. Though very messy, it makes sense.

It is not clear to me why Bridge.provide() needs to be placed inside the main() function. Would it not have the same effect if it were placed before main() at the very laeft margin allowing Python to access it directly?

I guess it makes no difference. I believe it's a C/C++ thing to have all executable lines inside main() and other functions, and only leave global variable declarations outside. Being at the steep beginner learning curve of Python myself, I have a hard time reading all the executable lines in the beginning of a Python script.

Thank you for the reply. We need to discuss this subtle thing in the forum to increase the UNO Q participation.

We mat also discuss the difference of the following two frameworks:

if __name__ == "__main__":
    main()

and

App.run(user_loop=loop)

I asked AI and it confirmed that App is a class like thing, which has a method or function named run(). And one argument is called user_loop, which takes the value of a function pointer (talking C/C++ here). So, to use that exact line, you need to have a function named loop(), just like in an Arduino sketch:

def loop():
    # Your main loop starts here
    foo = bar

App.run(user_loop=loop)

  

You don't create a while True: loop yourself. You let the App do the looping of your loop(). My uneducated guess is that the App class handles the looping better, maybe giving processor time to other underlying processes between each loop.