Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 28 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ In this repo it will be implemented an Arduino library wrapper for RPClite to be
Including Arduino_RouterBridge.h gives the user access to a Bridge object that can be used both as a RPC client and/or server to execute and serve RPCs to/from the CPU Host running a GOLANG router.

- The Bridge object is pre-defined on Serial1 and automatically initialized inside the main setup()
- The Bridge.call method is blocking and works the same as in RPClite
- The Bridge.call method is non-blocking and returns a RpcResult async object
- RpcResult class implements a blocking .result method that waits for the RPC response and returns true if the RPC returned with no errors
- The Bridge can provide callbacks to incoming RPC requests both in a thread-unsafe and thread-safe fashion (provide & provide_safe)
- Thread-safe methods execution is granted in the main loop thread where update_safe is called. By design users cannot access .update_safe() freely
- Thread-unsafe methods are served in an update callback, whose execution is granted in a separate thread. Nonetheless users can access .update() freely with caution
Expand All @@ -27,7 +28,7 @@ void setup() {

Bridge.begin();
Monitor.begin();

pinMode(LED_BUILTIN, OUTPUT);

if (!Bridge.provide("set_led", set_led)) {
Expand All @@ -41,27 +42,33 @@ void setup() {
}

void loop() {
float res;
if (!Bridge.call("multiply", 1.0, 2.0).result(res)) {
Monitor.println("Error calling method: multiply");
Monitor.println(Bridge.get_error_code());
Monitor.println(Bridge.get_error_message());
float sum;

// CALL EXAMPLES

// Standard chained call: Bridge.call("method", params...).result(res)
if (!Bridge.call("add", 1.0, 2.0).result(sum)) {
Monitor.println("Error calling method: add");
};

// Async call
RpcResult async_rpc = Bridge.call("add", 3.0, 4.5);
if (!async_rpc.result(sum)) {
Monitor.println("Error calling method: add");
Monitor.print("Error code: ");
Monitor.println(async_rpc.error.code);
Monitor.print("Error message: ");
Monitor.println(async_rpc.error.traceback);
}

// Implicit boolean cast. Use with caution as in this case the call is indeed
// executed expecting a fallback nil result (MsgPack::object::nil_t)
if (!Bridge.call("send_greeting", "Hello Friend")) {
Monitor.println("Error calling method: send_greeting");
};

// Please use notify when no reult (None, null, void, nil etc.) is expected from the opposite side
// the following is executed immediately
Bridge.notify("signal", 200);
}
```

## Best practices ##
Avoid catching Bridge call RpcResult without invoking its .result right away
```cpp
// OK
float out;
RpcResult res = Bridge.call("multiply", 1.0, 2.0);
res.result(out);
Monitor.println("TEST");

// NOT OK
//RpcResult res = Bridge.call("multiply", 1.0, 2.0);
//Monitor.println("TEST");
```
39 changes: 39 additions & 0 deletions examples/test/python/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import time
from arduino.app_utils import *


def no_args_no_result():
print("0 args no result")
return

def no_args_bool_result():
print("0 args bool result")
return True

def one_args_float_result(a):
print("1 args float result")
return a^2

def two_args_float_result(a, b):
print("2 args float result")
return a/b

def one_args_no_result(msg: str):
print("1 args no result")
print(f"Message received: {msg}")

def two_args_no_result(msg: str, x: int):
print("2 args no result")
print(f"Message received: {msg} {str(x)}")


if __name__ == "__main__":

Bridge.provide("0_args_no_result", no_args_no_result)
Bridge.provide("0_args_bool_result", no_args_bool_result)
Bridge.provide("1_args_float_result", one_args_float_result)
Bridge.provide("2_args_float_result", two_args_float_result)
Bridge.provide("1_args_no_result", one_args_no_result)
Bridge.provide("2_args_no_result", two_args_no_result)

App.run()
102 changes: 102 additions & 0 deletions examples/test/test.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
This file is part of the Arduino_RouterBridge library.

Copyright (c) 2025 Arduino SA

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

*/


#include "Arduino_RouterBridge.h"

bool x;
int i=0;

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

void loop() {

// testing monitor
Monitor.println("\r\n"+String(i));
i++;

// working
Bridge.call("0_args_no_result");

if (Bridge.call("0_args_no_result")){
Monitor.println("0_args_no_result TRUE without result"); // returns true because there's no result
}
else{
Monitor.println("0_args_no_result FALSE without result");
}

if (Bridge.call("0_args_bool_result")){
Monitor.println("0_args_bool_result TRUE without result");
}
else{
Monitor.println("0_args_bool_result FALSE without result"); // returns false -> check the result
}

x = false;
if (Bridge.call("0_args_bool_result").result(x)){
Monitor.println("0_args_bool_result TRUE with result: "+String(x)); // returns true - the perfect call
}
else{
Monitor.println("0_args_bool_result FALSE witt result: "+String(x));
}

int y = -1;
if (Bridge.call("0_args_bool_result").result(y)){
Monitor.println("0_args_bool_result TRUE with result: "+String(y)+" (wrong result type)");
}
else {
Monitor.println("0_args_bool_result FALSE with result: "+String(y)+" (wrong result type)"); // returns false - wrong type
}

float pow;
RpcResult async_res = Bridge.call("1_args_float_result", 2.0, 3.0); // passing 2 args to a function expecting 1
if (async_res.result(pow)) {
Monitor.println("Result of assignment and then result: "+String(pow)); // returns true, so the right result
} else {
Monitor.println("Error code: "+String(async_res.error.code));
Monitor.println("Error message: "+async_res.error.traceback);
}

float div = 0;
RpcResult async_res1 = Bridge.call("2_args_float_result", 2.0); // passing 1 arg when 2 are expected
if (async_res1.result(div)) {
Monitor.println("Result of assignment and then result: "+String(div)); // returns true, so the right result
} else {
Monitor.println("Error code: "+String(async_res1.error.code));
Monitor.println("Error message: "+async_res1.error.traceback);
}

div = 0;
RpcResult async_res2 = Bridge.call("2_args_float_result", 2.0, "invalid"); // passing a wrong type arg
if (async_res2.result(div)) {
Monitor.println("Result of assignment and then result: "+String(div)); // returns true, so the right result
} else {
Monitor.println("Error code: "+String(async_res2.error.code));
Monitor.println("Error message: "+async_res2.error.traceback);
}

x = false;
RpcResult async_res3 = Bridge.call("0_args_bool_result");
if (async_res3.result(x)) {
Monitor.println("Result of assignment and then result: "+String(x)); // returns true, so the right result
} else {
Monitor.println("Error expecting bool result: "+String(async_res3.error.code));
}

// Avoid the following:
// the call happens in the destructor falling back to the "no_result" case (a type mismatch here)
RpcResult async_res4 = Bridge.call("0_args_bool_result");

delay(1000);
}
63 changes: 26 additions & 37 deletions src/bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

*/

#pragma once
Expand All @@ -29,16 +29,29 @@

void updateEntryPoint(void *, void *, void *);

template<typename... Args>
class RpcResult {

public:
RpcError error;

RpcResult(uint32_t id, RPCClient* c, struct k_mutex* rm, struct k_mutex* wm) : msg_id_wait(id), client(c), read_mutex(rm), write_mutex(wm) {}
RpcResult(const MsgPack::str_t& m, RPCClient* c, struct k_mutex* rm, struct k_mutex* wm, Args&&... args): method(m), client(c), read_mutex(rm), write_mutex(wm), callback_params(std::forward_as_tuple(std::forward<Args>(args)...)) {}

template<typename RType> bool result(RType& result) {
if (_executed) return error.code == NO_ERR;

while (true) {
if (k_mutex_lock(write_mutex, K_MSEC(10)) == 0) {
std::apply([this](const auto&... elems) {
client->send_rpc(method, msg_id_wait, elems...);
}, callback_params);
k_mutex_unlock(write_mutex);
break;
} else {
k_yield();
}
}

while(true) {
if (k_mutex_lock(read_mutex, K_MSEC(10)) == 0 ) {
if (client->get_response(msg_id_wait, result, error)) {
Expand Down Expand Up @@ -74,11 +87,14 @@ class RpcResult {
}

private:
uint32_t msg_id_wait;
RPCClient* client;
uint32_t msg_id_wait{};
bool _executed = false;

MsgPack::str_t method;
RPCClient* client;
struct k_mutex* read_mutex;
struct k_mutex* write_mutex;
std::tuple<Args...> callback_params;
};

class BridgeClass {
Expand Down Expand Up @@ -150,7 +166,7 @@ class BridgeClass {
}

return server->bind(name, func, "__safe__");

}

void update() {
Expand All @@ -171,7 +187,7 @@ class BridgeClass {

// Lock write mutex
while (true) {

if (k_mutex_lock(&write_mutex, K_MSEC(10)) == 0){
server->send_response(req);
k_mutex_unlock(&write_mutex);
Expand All @@ -185,25 +201,10 @@ class BridgeClass {
}

template<typename... Args>
RpcResult call(const MsgPack::str_t& method, Args&&... args) {

uint32_t msg_id_wait;

// Lock write mutex
while (true) {
if (k_mutex_lock(&write_mutex, K_MSEC(10)) == 0) {
client->send_rpc(method, msg_id_wait, std::forward<Args>(args)...);
k_mutex_unlock(&write_mutex);
break;
} else {
k_yield();
}
}
return RpcResult{msg_id_wait, client, &read_mutex, &write_mutex};
RpcResult<Args...> call(const MsgPack::str_t& method, Args&&... args) {
return RpcResult<Args...>(method, client, &read_mutex, &write_mutex, std::forward<Args>(args)...);
}



template<typename... Args>
void notify(const MsgPack::str_t method, Args&&... args) {
while (true) {
Expand All @@ -216,18 +217,6 @@ class BridgeClass {
}
}

String get_error_message() const {
return static_cast<String>(client->lastError.traceback);
}

uint8_t get_error_code() const {
return static_cast<uint8_t>(client->lastError.code);
}

RpcError& get_last_client_error() const {
return client->lastError;
}

private:

void update_safe() {
Expand All @@ -248,7 +237,7 @@ class BridgeClass {

// Lock write mutex
while (true) {

if (k_mutex_lock(&write_mutex, K_MSEC(10)) == 0){
server->send_response(req);
k_mutex_unlock(&write_mutex);
Expand All @@ -258,7 +247,7 @@ class BridgeClass {
}

}

}

friend class BridgeClassUpdater;
Expand Down
6 changes: 4 additions & 2 deletions src/monitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ class BridgeMonitor: public Stream {
if (size == 0) return;

MsgPack::arr_t<uint8_t> message;
bool ret = bridge->call(MON_READ_METHOD, size).result(message);
RpcResult async_rpc = bridge->call(MON_READ_METHOD, size);

const bool ret = async_rpc.result(message);

if (ret) {
k_mutex_lock(&monitor_mutex, K_FOREVER);
Expand All @@ -132,7 +134,7 @@ class BridgeMonitor: public Stream {
k_mutex_unlock(&monitor_mutex);
}

// if (bridge.lastError.code > NO_ERR) {
// if (async_rpc.error.code > NO_ERR) {
// is_connected = false;
// }
}
Expand Down
6 changes: 4 additions & 2 deletions src/tcp_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,17 @@ class BridgeTCPClient : public Client {
k_mutex_lock(&client_mutex, K_FOREVER);

MsgPack::arr_t<uint8_t> message;
const bool ret = bridge->call(TCP_READ_METHOD, connection_id, size).result(message);
RpcResult async_rpc = bridge->call(TCP_READ_METHOD, connection_id, size);

const bool ret = async_rpc.result(message);

if (ret) {
for (size_t i = 0; i < message.size(); ++i) {
temp_buffer.store_char(static_cast<char>(message[i]));
}
}

if (bridge->get_last_client_error().code > NO_ERR) {
if (async_rpc.error.code > NO_ERR) {
_connected = false;
}

Expand Down