UNO Q Bridge Calls Help

Hello everyone,
I just got my UNO Q and wanted to send values from the python side of the code to the mcu to light up some LEDs on the matrix. The only example available I can find about the RPC calls is the blink LED one which sends a boolean. But I have not succeeded in sending a simple array (8*13 integers) from the python side to the mcu, does anyone know the signature of the function to receive an array from python in the sketch for such a thing? Or any example with arrays or structs?

So far i can send int (one or many if i make many arguments), boolean, and String as well. I could encode the array as a String and decode on the other side but I wonder if there is already a way to do it. I suck at programming so after not finding my way around inside MsgPack headers I tried:

  • arx::stdx::vector<int> (as it says for non stl enabled board or something) but this one does not compile with an error at some line saying it is not of type arr_t (which from the file is arx::stdx::vector) and that it is not unpackable so I dont know
  • std::vector<int> this one does not compile either
  • int* doesnt work either
  • arduino::msgpack::arr_t<uint8_t> ok this one does compile on the arduino but now the error is python side Request 'test_array_num1' failed: Wrong type parameter in position: 0 (253)

Any advice or example would be really helpful.
Thank you in advance!

PS:
If anyone stumbles on the same issue, I still havent found the real way to do it, but in the mean time I encode booleans inside of strings, and I only use 5 bits per character because some unconventional characters get deleted if I use 7 out of the 8 bits for data.

def encode_booleans_5bit(bools):
   encoded_chars = []
   value = 0
   bits_filled = 0
   for bit in bools:
       value = (value << 1) | bit
       bits_filled += 1
       if bits_filled == 5:
           # Map to ASCII starting at 34
           encoded_chars.append(chr(value + 34))
           value = 0
           bits_filled = 0
   if bits_filled > 0:
       # pad remaining bits on the right
       value <<= (5 - bits_filled)
       encoded_chars.append(chr(value + 34))
   return ''.join(encoded_chars)

This part encodes an array of booleans into a 'conventional' string that you can send through the Bridge.call function on the python side. On the arduino side you need to define a function that takes a String as an argument, and here's the arduino decoding part:

void decodeBooleans5bit(String s, bool output[104]) {
    int index = 0;
    for (int i = 0; i < s.length(); i++) {
        byte b = s[i] - 34; // remove offset to get original 5-bit value
        for (int j = 4; j >= 0; j--) { // 5 bits per character
            if (index < 104) {
                output[index++] = (b >> j) & 1;
            }
        }
    }
}

You can then just decode the String into your array, I use booleans cause i only need On/Off on the led matrix but you can adapt the code to send integers instead for example, just make sure the characters you get arent weird ( for example character 254 in python becomes 194 in arduino , I have no idea why i am too tired to think about it so I just bypassed it).

If it helps, you could also avoid sending a full array and just push each value individually from Python, something like:

for i, val in enumerate(arr):
    Bridge.call("set_cell", i, val)

And then on the MCU side you simply update your matrix:

void set_cell(int index, int value) {
    matrix[index] = value;
}

I’m not completely sure, but this seems to work reliably since the Bridge handles simple types without any issues.

1 Like

Before giving an answer I should point out I am new to the Uno Q, and I have a lot more familiarity with C than with either python or C++ so I could be mistaken!
The MCU and the MPU on this board each have their own memory space; it is not shared. For that reason passing any pointers via the bridge really would not make any sense at all. You can only pass values. In C at least both arrays and structures are passed by passing the memory address of the beginning of the array/struct. I expect C++ is essentially the same in that regard. So you can see an issue because an memory location in one MPU means nothing in the MCU and vice versa. On top of that Python does not have the same data types as C/C++. Python has lists, tuples and dictionaries which are actually objects and not data types. One can do the same sort of things with a Python list but it is not the same under the hood as a C/C++ array. I expect one would have to iterate over the list to send each member over the bridge.
There may be a potential pitfall even with integers. “Python's built-in int type handles arbitrary-precision integers, meaning their size is dynamically adjusted based on the value they hold, limited only by available memory.” That is not true in C/C++. I have not looked into the potential issues that might create or how this is handled on the Uno Q.

Good point Just one small thing even though pointers won’t work you’re not strictly limited to single values Since the bridge uses MsgPack you can technically send arrays or lists from Python and unpack them on the MCU side if you decode them right It’s still value based but with some structure preserved

Okay, well I would like to see an example.
I did find an example there two floats are sent. In the climate monitoring and storage example temperature and humidity data is collected on the MCU side and sent over the bridge. The syntax is simple:
Bridge.notify("record_sensor_samples", celsius, humidity);
It looks like the samples are being taken only once a second. If one can send two or more floats per record it seems likely that one could do the same with ints. Any packing must be handled transparently by the libraries as it does not look like the user program needs to worry about that even thou the MsgPack library was included in the project.

Sure here’s a small example to show what I meant
Since the bridge uses MsgPack internally multiple values are packed as a MsgPack array automatically So even though we can’t pass raw pointers we can pass structured data as multiple arguments and it will arrive on the other side as a list

#include <EsphomeBridge.h>

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

void loop() {
  // send three integers as one MsgPack array
  Bridge.notify("numbers_packet", 10, 20, 30);
  delay(1000);
}

Python side:

@bridge.on("numbers_packet")
def handler(a, b, c):
    print("Received list:", [a, b, c])

Behind the scenes the library packs the three values into a MsgPack array so the Python side receives them as a normal list
You’re right that it’s value-based but some structure is preserved because of how MsgPack handles argument lists

This works the same way for floats, ints or mixed types as long as you treat them as positional values rather than pointers

Hope this shows the concept more clearly

Thank you for your response, indeed that works as well but I was thinking of a way of sending it the fastest. I'm doing a verlet particle sim on python side that I downsample to 8*13 and send those values through RPC calls to the mpu. So far I have 20fps-ish with the String method but I dont know if my simulation is unoptimized (probably) or if the calls are slow, I still need to investigate though but my intuition tells me there is some overhead witht the rpc calls.

EDIT: right now the gravity is fixed but I also still have to plug an MPU6050 on the arduino pins ( I dont have the modulino version) so I will need to send the readings from that back as well which means even more rpc calls

Thank you for your answer. Indeed they both have their own memory space but my understanding is that MsgPack should be able to deal with not simple types as well, if you look at the source code it has some functions that deal with arrays (reading the pointer, copying the value and packing them), vectors and even maps although most of these features in the headers are locked behind CPP check directives (I am unable to provide a snippet of this as my vs code is a mess from opening a lot of files so I dont know where they are anymore but I saw this somewhere).

I took a look in the source code of MsgPack in the Unpacker header and it has a lot of these #if ARX_HAVE_LIBSTDCPLUSPLUS >= 201103L // Have libstdc++11 which I assume is false for the arduino, and in the else of those there are still some types that are supposed to be supported. For example

        template <typename T>
        auto unpackable(const arr_t<T>& arr) const ->
            typename std::enable_if<!std::is_same<T, char>::value && !std::is_same<T, uint8_t>::value, bool>::type {
            (void)arr;
            return isArray();
        }

This snippet is the one that was always crashing when compiling with a method that takes an array, and my limited understanding is that the whole header is basically a big trick to automatically wrap the methods that you provide with the Bridge depending on the type of the arguments. This is how I discovered that on the arduino side it compiles with this type arduino::msgpack::arr_t<uint8_t> but the corresponding type on python side is not an array and it fails in runtime.

You’re right some of the MsgPack container types are disabled on the Arduino side so complex arrays don’t map cleanly to Python That’s why arr_t<uint8_t> breaks even though the headers look like they should support it

If you want more speed the fastest method is to avoid sending many values through RPC Just pack your 8Ă—13 frame into a single byte buffer and send it as one argument That bypasses most of the RPC overhead

Example:

uint8_t frame[104];
Bridge.notify("frame_packet", frame, 104);

python:

@bridge.on("frame_packet")
def handler(data: bytes):
    grid = list(data)

This is usually much faster than sending strings or multiple integers.

Okay I think you might have gotten it the other way around :rofl: sorry If I wasnt clear, I am trying to push the led data from pyhon to the arduino :sweat_smile: what I need to send later the other way around though, the sensor data, could really use your method thanks!

Ahh got it my bad I thought you were sending the frame from the MCU to Python not the other way around
If the direction is Python → Arduino then yes you’ll need the inverse: pack the 8×13 frame on the Python side and send it as a single bytes payload to the MCU That should still avoid most of the RPC overhead

Something like:

Python:

frame = bytes(your_flattened_8x13_list)
bridge.notify("frame_packet", frame)

Arduino:

@bridge.on("frame_packet")
void handleFrame(uint8_t* data, size_t len) {
    // len should be 104
    // process your LED grid here
}

Same idea just reversed direction

And yeah for sensor data going back to Python the previous method should work perfectly

Yeah this one does not compile either... super long compilation error basically saying the MsgPack doesnt have a template matching it

python:

frame = bytes(your_flattened_8x13_list)  # 104 values
bridge.notify("frame_packet", frame)

Arduino side:

@bridge.on("frame_packet")
void handleFrame(msgpack::arr_t<uint8_t> data) {
    // data.size() should be 104
    for (size_t i = 0; i < data.size(); i++) {
        uint8_t v = data[i];
        // update your LED grid with v
    }
}

test this

1 Like

It appears to me what is needed from Arduino/Qualcomm is a document that clearly lays out what is implemented with respect to the bridge on the Uni Q and and working examples of how it is used in different scenarios. If such a document exists already no one is providing a link to it. I also wonder the extent to which this bridge implementation may evolve with newer software updates. Understanding the best practises with respect to using the bridge is rather important for developing new custom applications on this platform. It is at the heart of what differentiates this board from boards like the Raspberry Pi.

2 Likes

YESSSS! This works thanks, sorry for the late reply I was busy trying to run python scripts outside of docker with different version and using the python libraries.

This works outside of AppLab (havent tested this one yet), but directly as normal python script (but i suppose it works as well with a different syntax inside of app lab)
In python (yes this is literally three dots no code to be written there)

@call()
def handleFrame(frame: bytes) -> int:
    """Calls 'handleFrame' on the MCU, waiting indefinitely for a response."""
    ...

And to call it

#frame = bytes([1,2,3,4])
handleFrame(frame)

With this on the arduino side

void handleFrame(msgpack::arr_t<uint8_t> data) {
    Monitor.print("Received bytes: ");
    // data.size() should be 104
    for (size_t i = 0; i < data.size(); i++) {
        uint8_t v = data[i];
        Monitor.print(v);
        Monitor.print(" ");
    }
    Monitor.println(" end");
}

Which outputs

Received bytes: 1 2 3 4 end

( i will mark this one as the solution )

Please read

And consider if you have done this correctly.

You did not solve the problem yourself. You just verified a solution given to you by "taha_zarif90" this is the member that deserves the solution.

This is an important common curtesy you owe to that member.

3 Likes

You are right I havent solved it myself however the proposed solution is not fully complete and is missing an important part in python, the declaration of the function with bytes types as argument which I added as a code snippet and breaks the code otherwise (if you use any other of type like list or dont declare it, like in that response, there is a runtime error about not matching function). I do not care about the credit, and I am grateful to taha_zarif90, I was just trying to give a complete solution for anyone coming across this thread like me. But if you still think I should give the solution then no problem, again I wasnt trying to be rude or anything so I apologize.

Edit: oh right there is one reponse earlier where the function is there, so all I did was make a complete solution out of it, but okay again I am sorry🙏

1 Like

Thanks for this Taha. I have been looking for this information. If anyone is interested, I used my scope to determine the RPC timing. In the provided example which changes Uno Q GPIO pins from a web dashboard, 1 int and 1 boolean are sent. I measured the time to perform the RPC call as 8.3 ms. Of this time, only 4 us was needed for the ST MCU to execute the C code, the rest was taken up by the RPC overhead. Seeing this, I knew it would be necessary to send complete arrays, all at once, not as single variables over multiple RPC calls.