diff --git a/README.md b/README.md index 0e68776..64792b7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,56 @@ -In this repo it will be implemented an Arduino library wrapper for RPClite to be run on Imola boards. +In this repo it will be implemented an Arduino library wrapper for RPClite to be run on Arduino UNO Q boards. -The desired API is shown in https://github.com/bcmi-labs/Arduino_BridgeImola/blob/main/desired.ino. +The desired API is shown in https://github.com/bcmi-labs/Arduino_RouterBridge/blob/main/desired.ino. This is WIP. Expects changes soon. + +## The Bridge object ## + +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 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 + + +```cpp +bool set_led(bool state) { + digitalWrite(LED_BUILTIN, state); + return state; +} + +String greet() { + return String("Hello Friend"); +} + +void setup() { + Serial.begin(115200); + while (!Serial); + + pinMode(LED_BUILTIN, OUTPUT); + + if (!Bridge.provide("set_led", set_led)) { + Serial.println("Error providing method: set_led"); + } else { + Serial.println("Registered method: set_led"); + } + + Bridge.provide_safe("greet", greet); + +} + +void loop() { + float res; + if (!Bridge.call("multiply", res, 1.0, 2.0)) { + Serial.println("Error calling method: multiply"); + Serial.println(Bridge.get_error_code()); + Serial.println(Bridge.get_error_message()); + }; + + Bridge.notify("signal", 200); + + //Bridge.update(); // Thread-unsafe update execution is granted in its own thread. It can be called manually with caution +} +``` diff --git a/desired.ino b/desired.ino deleted file mode 100644 index 073f19d..0000000 --- a/desired.ino +++ /dev/null @@ -1,40 +0,0 @@ -// Bridge.begin(); <- statica nel loader di zephyr / core -// -> rpc.call("$/reset", res); - -void setup() { - pinMode(LED_BUILTIN, OUTPUT); - - if (!Bridge.provide("set_led", set_led)) { // -> rpc.call("$/register", res, "set_led"); - // ERRORE! - }; - Bridge.provide("add", add); // -> rpc.call("$/register", res, "add"); - // ERRORE ignorato - Bridge.provide("greet", greet); // -> rpc.call("$/register", res, "greet"); - - // metodi forniti con provide possono ricevere anche notifiche (semplicemente il risultato non viene restituito) -} - -bool set_led(bool state) { - digitalWrite(LED_BUILTIN, state); - return state; -} - -int add(int a, int b) { - return a + b; -} - -String greet() { - return String("Hello Friend"); -} - -void loop() { - float res; - if (!Bridge.call("multiply", res, 1.0, 2.0)) { - // ERRORE! - }; - - Bridge.notify("signal", 200); - - // update viene chiamato automaticamente in un thread separato - // Bridge.update(); -> server.run(); -} diff --git a/examples/simple_bridge/simple_bridge.ino b/examples/simple_bridge/simple_bridge.ino index 7437b8d..24ea246 100644 --- a/examples/simple_bridge/simple_bridge.ino +++ b/examples/simple_bridge/simple_bridge.ino @@ -1,58 +1,47 @@ -#include +#include -Bridge bridge(Serial1); +//BridgeClass Bridge(Serial1); + +bool set_led(bool state) { + digitalWrite(LED_BUILTIN, state); + return state; +} + +int add(int a, int b) { + return a + b; +} + +String greet() { + return String("Hello Friend"); +} void setup() { Serial.begin(115200); while (!Serial); - - Serial1.begin(115200); - while (!Serial1); pinMode(LED_BUILTIN, OUTPUT); - if (!bridge.begin()) { - Serial.println("Error initializing bridge"); - while(1); - } else { - Serial.println("Bridge initialized successfully"); - } - - if (!bridge.provide("set_led", set_led)) { + if (!Bridge.provide("set_led", set_led)) { Serial.println("Error providing method: set_led"); } else { Serial.println("Registered method: set_led"); } - bridge.provide("add", add); + Bridge.provide("add", add); - bridge.provide("greet", greet); - -} + Bridge.provide_safe("greet", greet); -bool set_led(bool state) { - digitalWrite(LED_BUILTIN, state); - return state; -} - -int add(int a, int b) { - return a + b; -} - -String greet() { - return String("Hello Friend"); } void loop() { float res; - if (!bridge.call("multiply", res, 1.0, 2.0)) { + if (!Bridge.call("multiply", res, 1.0, 2.0)) { Serial.println("Error calling method: multiply"); - Serial.println(bridge.get_error_code()); - Serial.println(bridge.get_error_message()); - delay(1000); + Serial.println(Bridge.get_error_code()); + Serial.println(Bridge.get_error_message()); }; - bridge.notify("signal", 200); + Bridge.notify("signal", 200); - bridge.update(); + //Bridge.update(); // Thread-unsafe update execution is granted in its own thread. It can be called manually with caution } diff --git a/library.json b/library.json index 7967d64..ce24b6d 100644 --- a/library.json +++ b/library.json @@ -1,22 +1,22 @@ { - "name": "Arduino_BridgeImola", + "name": "Arduino_RouterBridge", "keywords": "rpclite,msgpack", - "description": "A RPC bridge for Imola boards", + "description": "A RPC bridge for Arduino UNO Q boards", "repository": { "type": "git", - "url": "https://github.com/bcmi-labs/Arduino_BridgeImola" + "url": "https://github.com/bcmi-labs/Arduino_RouterBridge" }, "authors": { "name": "BCMI-labs", - "url": "https://github.com/bcmi-labs/Arduino_BridgeImola", + "url": "https://github.com/bcmi-labs/Arduino_RouterBridge", "maintainer": true }, - "version": "0.0.1", + "version": "0.1.0", "license": "MIT", "frameworks": "arduino", "platforms": "*", "dependencies": { - "bcmi-labs/Arduino_RPClite": ">=0.0.1" + "bcmi-labs/Arduino_RPClite": ">=0.1.0" } } diff --git a/library.properties b/library.properties index f092c0d..9e0dfb5 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ -name=Arduino_BridgeImola -version=0.0.1 +name=Arduino_RouterBridge +version=0.1.0 author=BCMI-labs maintainer=BCMI-labs -sentence=A RPC bridge for Imola boards -paragraph=This library provides a simple RPC bridge for Imola boards, allowing communication between the board and other devices using MsgPack serialization. +sentence=A RPC bridge for Arduino UNO Q boards +paragraph=This library provides a simple RPC bridge for Arduino UNO Q boards, allowing communication between the board and other devices using MsgPack serialization. category=Communication url=https://www.arduino.cc/ architectures=* -depends=Arduino_RPClite (>=0.0.1) +depends=Arduino_RPClite (>=0.1.0) diff --git a/src/Arduino_BridgeImola.h b/src/Arduino_BridgeImola.h deleted file mode 100644 index 8af38ad..0000000 --- a/src/Arduino_BridgeImola.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef ARDUINO_BRIDGE_IMOLA_H -#define ARDUINO_BRIDGE_IMOLA_H - -#include "Arduino.h" -#include "bridge.h" - -#endif //ARDUINO_BRIDGE_IMOLA_H diff --git a/src/Arduino_RouterBridge.h b/src/Arduino_RouterBridge.h new file mode 100644 index 0000000..be9da6e --- /dev/null +++ b/src/Arduino_RouterBridge.h @@ -0,0 +1,7 @@ +#ifndef ARDUINO_ROUTER_BRIDGE_H +#define ARDUINO_ROUTER_BRIDGE_H + +#include "Arduino.h" +#include "bridge.h" + +#endif //ARDUINO_ROUTER_BRIDGE_H diff --git a/src/bridge.h b/src/bridge.h index afa0992..afc1d25 100644 --- a/src/bridge.h +++ b/src/bridge.h @@ -1,30 +1,60 @@ #pragma once -#ifndef BRIDGE_IMOLA_H -#define BRIDGE_IMOLA_H +#ifndef ROUTER_BRIDGE_H +#define ROUTER_BRIDGE_H #define RESET_METHOD "$/reset" #define BIND_METHOD "$/register" +#define UPDATE_THREAD_STACK_SIZE 500 +#define UPDATE_THREAD_PRIORITY 5 + +#define DEFAULT_SERIAL_BAUD 115200 + +#include #include -class Bridge { +void updateEntryPoint(void *, void *, void *); + +class BridgeClass { RPCClient* client = nullptr; RPCServer* server = nullptr; + HardwareSerial* serial_ptr = nullptr; ITransport* transport; + struct k_mutex read_mutex; + struct k_mutex write_mutex; + + k_tid_t upd_tid; + k_thread_stack_t *upd_stack_area; + struct k_thread upd_thread_data; + public: - Bridge(ITransport& t) : transport(&t) {} - Bridge(Stream& stream) { - transport = new SerialTransport(stream); + + BridgeClass(HardwareSerial& serial) { + serial_ptr = &serial; + transport = new SerialTransport(serial); } // Initialize the bridge - bool begin() { + bool begin(unsigned long baud=DEFAULT_SERIAL_BAUD) { + serial_ptr->begin(baud); + + k_mutex_init(&read_mutex); + k_mutex_init(&write_mutex); + client = new RPCClient(*transport); server = new RPCServer(*transport); + + upd_stack_area = k_thread_stack_alloc(UPDATE_THREAD_STACK_SIZE, 0); + upd_tid = k_thread_create(&upd_thread_data, upd_stack_area, + UPDATE_THREAD_STACK_SIZE, + updateEntryPoint, + NULL, NULL, NULL, + UPDATE_THREAD_PRIORITY, 0, K_NO_WAIT); + bool res; return call(RESET_METHOD, res); } @@ -38,19 +68,58 @@ class Bridge { return server->bind(name, func); } + template + bool provide_safe(const MsgPack::str_t& name, F&& func) { + bool res; + if (!call(BIND_METHOD, res, name)) { + return false; + } + + return server->bind(name, func, "__safe__"); + + } + void update() { - // Protect the following calls with a mutex if necessary - // server->read_request(); // <- inbound - // server->serve(); // -> outbound - server->run(); + + k_msleep(1); + // Lock read mutex + k_mutex_lock(&read_mutex, K_FOREVER); + if (!server->get_rpc()) { + k_mutex_unlock(&read_mutex); + return; + } + k_mutex_unlock(&read_mutex); + + server->process_request(); + + // Lock write mutex + k_mutex_lock(&write_mutex, K_FOREVER); + server->send_response(); + k_mutex_unlock(&write_mutex); + } template bool call(const MsgPack::str_t method, RType& result, Args&&... args) { - // Protect the following calls with a mutex if necessary - // client->send_call(); // -> outbound - // client->read_response(); // <- inbound - return client->call(method, result, std::forward(args)...); + + // Lock write mutex + k_mutex_lock(&write_mutex, K_FOREVER); + client->send_rpc(method, std::forward(args)...); + k_mutex_unlock(&write_mutex); + + // Lock read mutex + while(1) { + k_mutex_lock(&read_mutex, K_FOREVER); + if (client->get_response(result)) { + k_mutex_unlock(&read_mutex); + break; + } + k_mutex_unlock(&read_mutex); + k_msleep(1); + } + + return (client->lastError.code == NO_ERR); + } template @@ -66,6 +135,56 @@ class Bridge { return (uint8_t) client->lastError.code; } +private: + + void update_safe() { + + // Lock read mutex + k_msleep(1); + k_mutex_lock(&read_mutex, K_FOREVER); + if (!server->get_rpc()) { + k_mutex_unlock(&read_mutex); + return; + } + k_mutex_unlock(&read_mutex); + + server->process_request("__safe__"); + + // Lock write mutex + k_mutex_lock(&write_mutex, K_FOREVER); + server->send_response(); + k_mutex_unlock(&write_mutex); + + } + + friend class BridgeClassUpdater; + +}; + +class BridgeClassUpdater { +public: + static void safeUpdate(BridgeClass& bridge) { + bridge.update_safe(); // access private method + } + +private: + BridgeClassUpdater() = delete; // prevents instantiation }; -#endif // BRIDGE_IMOLA_H \ No newline at end of file +BridgeClass Bridge(Serial1); + +void updateEntryPoint(void *, void *, void *){ + while(1){ + Bridge.update(); + } +} + +static void safeUpdate(){ + BridgeClassUpdater::safeUpdate(Bridge); +} + +void __loopHook(){ + safeUpdate(); +} + +#endif // ROUTER_BRIDGE_H \ No newline at end of file