diff --git a/README.md b/README.md index f6f2d3d..cd260b3 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ void loop() { if (!async_rpc.result(sum)) { Monitor.println("Error calling method: add"); Monitor.print("Error code: "); - Monitor.println(async_rpc.error.code); + Monitor.println(async_rpc.getErrorCode()); Monitor.print("Error message: "); - Monitor.println(async_rpc.error.traceback); + Monitor.println(async_rpc.getErrorMessage()); } // Implicit boolean cast. Use with caution as in this case the call is indeed diff --git a/examples/simple_bridge/simple_bridge.ino b/examples/simple_bridge/simple_bridge.ino index 508098f..22eaba3 100644 --- a/examples/simple_bridge/simple_bridge.ino +++ b/examples/simple_bridge/simple_bridge.ino @@ -59,8 +59,8 @@ void loop() { Serial.print("Result of the operation is: "); Serial.println(res); } else { - Serial.println(outcome.error.code); - Serial.println(outcome.error.traceback); + Serial.println(outcome.getErrorCode()); + Serial.println(outcome.getErrorMessage()); } Bridge.notify("signal", 200); diff --git a/examples/test/test.ino b/examples/test/test.ino index 619bfaa..e03d662 100644 --- a/examples/test/test.ino +++ b/examples/test/test.ino @@ -64,8 +64,8 @@ void loop() { 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); + Monitor.println("Error code: "+String(async_res.getErrorCode())); + Monitor.println("Error message: "+async_res.getErrorMessage()); } float div = 0; @@ -73,8 +73,8 @@ void loop() { 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); + Monitor.println("Error code: "+String(async_res1.getErrorCode())); + Monitor.println("Error message: "+async_res1.getErrorMessage()); } div = 0; @@ -82,8 +82,8 @@ void loop() { 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); + Monitor.println("Error code: "+String(async_res2.getErrorCode())); + Monitor.println("Error message: "+async_res2.getErrorMessage()); } x = false; @@ -91,7 +91,7 @@ void loop() { 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)); + Monitor.println("Error expecting bool result: "+String(async_res3.getErrorCode())); } // Avoid the following: diff --git a/examples/test_rpc_thread/python/main.py b/examples/test_rpc_thread/python/main.py new file mode 100644 index 0000000..67db4a0 --- /dev/null +++ b/examples/test_rpc_thread/python/main.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: Copyright (C) 2025 ARDUINO SA +# +# SPDX-License-Identifier: MPL-2.0 + +import time +from arduino.app_utils import * + +led_state = False + +def loopback(message): + time.sleep(1) + return message + +Bridge.provide("loopback", loopback) +App.run() diff --git a/examples/test_rpc_thread/test_rpc_thread.ino b/examples/test_rpc_thread/test_rpc_thread.ino new file mode 100644 index 0000000..8447616 --- /dev/null +++ b/examples/test_rpc_thread/test_rpc_thread.ino @@ -0,0 +1,115 @@ +#include +#include + +// Thread config +#define THREAD_STACK_SIZE 500 +#define THREAD_PRIORITY 5 + + +void rpc_thread_entry(void *p1, void *p2, void *p3) { + RpcCall *call = reinterpret_cast*>(p1); + struct k_mutex *mtx = reinterpret_cast(p2); + + // Give setup() time to complete first result() + k_sleep(K_MSEC(400)); + + Serial.println("\n--- Second Thread ---"); + Serial.println("Calling result() again..."); + + k_mutex_lock(mtx, K_FOREVER); + + MsgPack::str_t msg; + bool ok = call->result(msg); + + if (ok) { + Serial.println("ERR - Second call succeeded (unexpected!)"); + Serial.print("Message: "); + Serial.println(msg.c_str()); + } else { + Serial.println("OK - Second call FAILED as expected (already executed)"); + Serial.print("Error Code: 0x"); + Serial.println(call->getErrorCode(), HEX); + Serial.print("Error Message: "); + Serial.println(call->getErrorMessage().c_str()); + } + + k_mutex_unlock(mtx); + + Serial.println("--- Second Thread End ---\n"); +} + + +void setup() { + Serial.begin(115200); + k_sleep(K_MSEC(2000)); + + Serial.println("\n=== Threaded RPC Test ===\n"); + + Serial.println("*** Main Thread (setup) ***"); + + Bridge.begin(); + Monitor.begin(); + + static struct k_mutex loop_mtx; + k_mutex_init(&loop_mtx); + + RpcCall loopback_call = Bridge.call("loopback", "TEST"); + + if (loopback_call.isError()) { + Serial.println("OK - RPC call in Error mode before execution"); + Serial.print("Error Code: 0x"); + Serial.println(loopback_call.getErrorCode(), HEX); + Serial.print("Error Message: "); + Serial.println(loopback_call.getErrorMessage().c_str()); + } else { + Serial.println("ERR - RPC call not in Error mode before execution (unexpected)"); + } + + Serial.println("Waiting for the other side...\n"); + delay(2000); + + Serial.println("calling .result() on RPC call (main thread)"); + + MsgPack::str_t msg; + k_mutex_lock(&loop_mtx, K_FOREVER); + bool ok = loopback_call.result(msg); + k_mutex_unlock(&loop_mtx); + + if (ok) { + Serial.println("OK - First call succeeded."); + Serial.print("Message: "); + Serial.println(msg.c_str()); + } else { + Serial.println("ERR - First call FAILED (unexpected)."); + } + + // ---- Launch second thread ---- + Serial.println("\nStarting second thread..."); + + struct k_thread rpc_thread; + + k_thread_stack_t *rpc_stack_area = k_thread_stack_alloc(THREAD_STACK_SIZE, 0); + + k_tid_t rpc_tid = k_thread_create( + &rpc_thread, + rpc_stack_area, + THREAD_STACK_SIZE, + rpc_thread_entry, + &loopback_call, // p1 → RpcCall* + &loop_mtx, // p2 → mutex + NULL, + THREAD_PRIORITY, + 0, + K_FOREVER + ); + + k_thread_start(rpc_tid); + Serial.println("Second thread launched... joining"); + k_thread_join(&rpc_thread, K_FOREVER); + Serial.println("*** Main thread end ending setup ***"); + +} + +void loop() { + k_sleep(K_MSEC(5000)); +} diff --git a/src/bridge.h b/src/bridge.h index 8e684d0..8c2c77a 100644 --- a/src/bridge.h +++ b/src/bridge.h @@ -27,22 +27,56 @@ #include #include +#include + void updateEntryPoint(void *, void *, void *); template class RpcCall { + + RpcError error; + + void setError(int code, MsgPack::str_t text) { + k_mutex_lock(&call_mutex, K_FOREVER); + error.code = code; + error.traceback = std::move(text); + k_mutex_unlock(&call_mutex); + } + public: - RpcError error{GENERIC_ERR, "This call is not executed yet"}; - RpcCall(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)...)) {} + RpcCall(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)...)) { + k_mutex_init(&call_mutex); + setError(GENERIC_ERR, "This call is not yet executed"); + } + + bool isError() { + k_mutex_lock(&call_mutex, K_FOREVER); + const bool out = error.code > NO_ERR; + k_mutex_unlock(&call_mutex); + return out; + } + + int getErrorCode() { + k_mutex_lock(&call_mutex, K_FOREVER); + const int out = error.code; + k_mutex_unlock(&call_mutex); + return out; + } + + MsgPack::str_t getErrorMessage() { + k_mutex_lock(&call_mutex, K_FOREVER); + MsgPack::str_t out = error.traceback; + k_mutex_unlock(&call_mutex); + return out; + } template bool result(RType& result) { if (!atomic_cas(&_executed, 0, 1)){ // this thread lost the race - error.code = GENERIC_ERR; - error.traceback = "This call result is no longer available"; + setError(GENERIC_ERR, "This call is no longer available"); return false; } @@ -60,13 +94,15 @@ class RpcCall { while(true) { if (k_mutex_lock(read_mutex, K_MSEC(10)) == 0 ) { - if (client->get_response(msg_id_wait, result, error)) { + RpcError temp_err; + if (client->get_response(msg_id_wait, result, temp_err)) { k_mutex_unlock(read_mutex); // if (error.code == PARSING_ERR) { // k_mutex_lock(write_mutex, K_FOREVER); // client->notify(BRIDGE_ERROR, error.traceback); // k_mutex_unlock(write_mutex); // } + setError(temp_err.code, temp_err.traceback); break; } k_mutex_unlock(read_mutex); @@ -76,7 +112,7 @@ class RpcCall { } } - return error.code == NO_ERR; + return !isError(); } bool result() { @@ -100,6 +136,7 @@ class RpcCall { RPCClient* client; struct k_mutex* read_mutex; struct k_mutex* write_mutex; + struct k_mutex call_mutex{}; std::tuple callback_params; }; @@ -145,6 +182,8 @@ class BridgeClass { if (is_started()) return true; + k_mutex_lock(&bridge_mutex, K_FOREVER); + serial_ptr->begin(baud); transport = new SerialTransport(*serial_ptr); @@ -159,7 +198,6 @@ class BridgeClass { UPDATE_THREAD_PRIORITY, 0, K_NO_WAIT); k_thread_name_set(upd_tid, "bridge"); - k_mutex_lock(&bridge_mutex, K_FOREVER); bool res = false; started = call(RESET_METHOD).result(res) && res; k_mutex_unlock(&bridge_mutex); diff --git a/src/hci.h b/src/hci.h index a992e8b..faa4c5a 100644 --- a/src/hci.h +++ b/src/hci.h @@ -53,10 +53,12 @@ template class BridgeHCI { bool begin(const char *device = "hci0") { k_mutex_init(&hci_mutex); + k_mutex_lock(&hci_mutex, K_FOREVER); // Pre-allocate recv buffer to avoid allocations during recv calls recv_buffer.reserve(BufferSize); if (!(*bridge) && !bridge->begin()) { + k_mutex_unlock(&hci_mutex); return false; } @@ -65,16 +67,18 @@ template class BridgeHCI { initialized = result; } - return initialized; + k_mutex_unlock(&hci_mutex); + return result; } void end() { + k_mutex_lock(&hci_mutex, K_FOREVER); + if (!initialized) { + k_mutex_unlock(&hci_mutex); return; } - k_mutex_lock(&hci_mutex, K_FOREVER); - bool result; bridge->call(HCI_CLOSE_METHOD).result(result); initialized = false; @@ -83,16 +87,20 @@ template class BridgeHCI { } explicit operator bool() const { - return initialized; + k_mutex_lock(&hci_mutex, K_FOREVER); + bool out = initialized; + k_mutex_unlock(&hci_mutex); + return out; } int send(const uint8_t *buffer, size_t size) { + k_mutex_lock(&hci_mutex, K_FOREVER); + if (!initialized) { + k_mutex_unlock(&hci_mutex); return -1; } - k_mutex_lock(&hci_mutex, K_FOREVER); - BinaryView send_buffer(buffer, size); size_t bytes_sent; const bool ret = bridge->call(HCI_SEND_METHOD, send_buffer).result(bytes_sent); @@ -106,12 +114,13 @@ template class BridgeHCI { } int recv(uint8_t *buffer, size_t max_size) { + k_mutex_lock(&hci_mutex, K_FOREVER); + if (!initialized) { + k_mutex_unlock(&hci_mutex); return -1; } - k_mutex_lock(&hci_mutex, K_FOREVER); - recv_buffer.clear(); bool ret = bridge->call(HCI_RECV_METHOD, max_size).result(recv_buffer); @@ -130,12 +139,14 @@ template class BridgeHCI { } int available() { + + k_mutex_lock(&hci_mutex, K_FOREVER); + if (!initialized) { + k_mutex_unlock(&hci_mutex); return 0; } - k_mutex_lock(&hci_mutex, K_FOREVER); - bool result; bool ret = bridge->call(HCI_AVAIL_METHOD).result(result); diff --git a/src/monitor.h b/src/monitor.h index 4c5f70f..1abedcb 100644 --- a/src/monitor.h +++ b/src/monitor.h @@ -42,14 +42,17 @@ class BridgeMonitor: public Stream { if (is_connected()) return true; + k_mutex_lock(&monitor_mutex, K_FOREVER); bool bridge_started = (*bridge); if (!bridge_started) { bridge_started = bridge->begin(); } - if (!bridge_started) return false; + if (!bridge_started) { + k_mutex_unlock(&monitor_mutex); + return false; + } - k_mutex_lock(&monitor_mutex, K_FOREVER); bool out = false; _connected = bridge->call(MON_CONNECTED_METHOD).result(out) && out; k_mutex_unlock(&monitor_mutex); @@ -121,9 +124,9 @@ class BridgeMonitor: public Stream { } bool reset() { + k_mutex_lock(&monitor_mutex, K_FOREVER); bool res; bool ok = bridge->call(MON_RESET_METHOD).result(res) && res; - k_mutex_lock(&monitor_mutex, K_FOREVER); _connected = !ok; k_mutex_unlock(&monitor_mutex); return ok; @@ -151,7 +154,7 @@ class BridgeMonitor: public Stream { } } - // if (async_rpc.error.code > NO_ERR) { + // if (async_rpc.getErrorCode() > NO_ERR) { // _connected = false; // } diff --git a/src/tcp_client.h b/src/tcp_client.h index 8a26e30..39c851d 100644 --- a/src/tcp_client.h +++ b/src/tcp_client.h @@ -202,7 +202,7 @@ class BridgeTCPClient : public Client { } } - if (async_rpc.error.code > NO_ERR) { + if (async_rpc.getErrorCode() > NO_ERR) { _connected = false; }