diff --git a/README.md b/README.md index 4ea7ac6..8bebe6c 100644 --- a/README.md +++ b/README.md @@ -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 @@ -27,7 +28,7 @@ void setup() { Bridge.begin(); Monitor.begin(); - + pinMode(LED_BUILTIN, OUTPUT); if (!Bridge.provide("set_led", set_led)) { @@ -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"); -``` diff --git a/examples/test/python/main.py b/examples/test/python/main.py new file mode 100644 index 0000000..8a4ca30 --- /dev/null +++ b/examples/test/python/main.py @@ -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() diff --git a/examples/test/test.ino b/examples/test/test.ino new file mode 100644 index 0000000..3e3b459 --- /dev/null +++ b/examples/test/test.ino @@ -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); +} \ No newline at end of file diff --git a/src/bridge.h b/src/bridge.h index 7a2858c..0ae012f 100644 --- a/src/bridge.h +++ b/src/bridge.h @@ -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 @@ -29,16 +29,29 @@ void updateEntryPoint(void *, void *, void *); +template 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)...)) {} template 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)) { @@ -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 callback_params; }; class BridgeClass { @@ -150,7 +166,7 @@ class BridgeClass { } return server->bind(name, func, "__safe__"); - + } void update() { @@ -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); @@ -185,25 +201,10 @@ class BridgeClass { } template - 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)...); - k_mutex_unlock(&write_mutex); - break; - } else { - k_yield(); - } - } - return RpcResult{msg_id_wait, client, &read_mutex, &write_mutex}; + RpcResult call(const MsgPack::str_t& method, Args&&... args) { + return RpcResult(method, client, &read_mutex, &write_mutex, std::forward(args)...); } - - template void notify(const MsgPack::str_t method, Args&&... args) { while (true) { @@ -216,18 +217,6 @@ class BridgeClass { } } - String get_error_message() const { - return static_cast(client->lastError.traceback); - } - - uint8_t get_error_code() const { - return static_cast(client->lastError.code); - } - - RpcError& get_last_client_error() const { - return client->lastError; - } - private: void update_safe() { @@ -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); @@ -258,7 +247,7 @@ class BridgeClass { } } - + } friend class BridgeClassUpdater; diff --git a/src/monitor.h b/src/monitor.h index bac7465..0c7126c 100644 --- a/src/monitor.h +++ b/src/monitor.h @@ -122,7 +122,9 @@ class BridgeMonitor: public Stream { if (size == 0) return; MsgPack::arr_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); @@ -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; // } } diff --git a/src/tcp_client.h b/src/tcp_client.h index 62da5e7..b1acbbc 100644 --- a/src/tcp_client.h +++ b/src/tcp_client.h @@ -198,7 +198,9 @@ class BridgeTCPClient : public Client { k_mutex_lock(&client_mutex, K_FOREVER); MsgPack::arr_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) { @@ -206,7 +208,7 @@ class BridgeTCPClient : public Client { } } - if (bridge->get_last_client_error().code > NO_ERR) { + if (async_rpc.error.code > NO_ERR) { _connected = false; }