Stepdance Software Library
Loading...
Searching...
No Matches
rpc.hpp
1#include <type_traits>
2#include <utility>
3#include "WString.h"
4#include <sys/_stdint.h>
5#include "HardwareSerial.h"
6#include "Arduino.h"
7#include "usb_serial.h"
8#include <functional>
9#include <map>
10#include <ArduinoJson.h>
11
12/*
13Remote Procedure Call (RPC) Module of the StepDance Control System
14
15This module contains facilities for accessing internal stepdance functions and parameters over serial via remote procedure calls.
16
17[More Details to be Added]
18
19A part of the Mixing Metaphors Project
20(c) 2025 Ilan Moyer, Jennifer Jacobs, Devon Frost
21
22*/
23#include "Stream.h"
24#include "core.hpp"
25
26#ifndef rpc_h //prevent importing twice
27#define rpc_h
35class RPC : public Plugin{
36 public:
37 RPC();
41 void begin(); //defaults to Serial as the input stream
46 void begin(Stream *target_stream);
51 void begin(usb_serial_class *target_usb_serial);
58 void begin(HardwareSerialIMXRT *target_serial, uint32_t baud, uint16_t format = 0); //hardware serial
59
60 // --- RPC Registration ---
61 template<typename Ret, typename... Args> //function registration
62 void enroll(const String& name, Ret(*func)(Args...)){ //registers an RPC function with any signature, as handled by above templating
63 add_to_registry(name, [func, this](JsonArray args){ //creates a lambda function with access to func, that accepts a JsonArray of arguments (i.e. matching the registry map definition)
64 this->call_and_respond(func, args, std::index_sequence_for<Args...>{}); //the lambda function will then call a call_and_respond
65 });
66 rpc_index[name] = "function";
67 }
68
69 template<typename Obj, typename Ret, typename... Args> //bound method registration. This is intended to be called from within the enroll() method of a plugin
70 void enroll(const String& instance_name, const String& name, Obj& instance, Ret(Obj::*method)(Args...)){
71 add_to_registry(instance_name + "." + name, [&instance, method, this](JsonArray args){
72 this->call_and_respond(instance, method, args, std::index_sequence_for<Args...>{});
73 });
74 rpc_index[instance_name + "." + name] = "function";
75 }
76
77 void enroll(const String& name, Plugin& instance){ //enrolls a plugin instance
78 instance.enroll(this, name);
79 }
80
81 template<typename T, typename = std::enable_if_t<!std::is_base_of_v<Plugin, T>>> //parameter registration
82 void enroll(const String& name, T& parameter){
83 add_to_registry(name, [&parameter, this](JsonArray args){
84 if(!args.isNull() && args.size() > 0){ //we're setting the value of the parameter
85 parameter = args[0].as<T>();
86 reset_outbound_state();
87 outbound_json_doc["result"] = "ok";
88 serializeJson(outbound_json_doc, *rpc_stream);
89 rpc_stream->println();
90 }else{ //getting the value
91 reset_outbound_state();
92 outbound_json_doc["result"] = "ok";
93 outbound_json_doc["return"] = parameter;
94 serializeJson(outbound_json_doc, *rpc_stream);
95 rpc_stream->println();
96 }
97 });
98 rpc_index[name] = "parameter";
99 }
100
101 // --- RPC Dispatch ---
102 template<typename... Args, size_t... I> // function with no return value
103 void call_and_respond(void(*func)(Args...), JsonArray args, std::index_sequence<I...>){
104 func(args[I].as<Args>()...); //calls function with args
105 reset_outbound_state();
106 outbound_json_doc["result"] = "ok";
107 serializeJson(outbound_json_doc, *rpc_stream);
108 rpc_stream->println();
109 }
110
111 template<typename Obj, typename... Args, size_t... I> // bound method with no return value
112 void call_and_respond(Obj& instance, void(Obj::*method)(Args...), JsonArray args, std::index_sequence<I...>){
113 (instance.*method)(args[I].as<Args>()...); //calls function with args
114 reset_outbound_state();
115 outbound_json_doc["result"] = "ok";
116 serializeJson(outbound_json_doc, *rpc_stream);
117 rpc_stream->println();
118 }
119
120 template<typename Ret, typename... Args, size_t... I>
121 void call_and_respond(Ret(*func)(Args...), JsonArray args, std::index_sequence<I...>){
122 Ret ret = func(args[I].as<Args>()...); //calls function with args and returns type Ret
123 reset_outbound_state();
124 outbound_json_doc["result"] = "ok";
125 outbound_json_doc["return"] = ret;
126 serializeJson(outbound_json_doc, *rpc_stream);
127 rpc_stream->println();
128 }
129
130 template<typename Obj, typename Ret, typename... Args, size_t... I> // bound method with no return value
131 void call_and_respond(Obj& instance, Ret(Obj::*method)(Args...), JsonArray args, std::index_sequence<I...>){
132 Ret ret = (instance.*method)(args[I].as<Args>()...); //calls function with args
133 reset_outbound_state();
134 outbound_json_doc["result"] = "ok";
135 outbound_json_doc["return"] = ret;
136 serializeJson(outbound_json_doc, *rpc_stream);
137 rpc_stream->println();
138 }
139
140 private:
141 void reset_inbound_state();
142 void reset_outbound_state();
143
144 Stream *rpc_stream; //pointer to an I/O stream for the remote call
145 String inbound_string;
146 JsonDocument inbound_json_doc; //stores JSON doc based on inbound stream
147 JsonDocument outbound_json_doc;
148
149 using RPCFunction = std::function<void(JsonArray)>; //function format as it goes into the registry
150 std::map<String, RPCFunction> rpc_registry; //registry for storing rpc functions. Note that these are lambda functions wrapping the actual function (or parameter) to be called/returned.
151 std::map<String, String> rpc_index; //index of all rpc strings and their type. This can be reported back to the remote system as a convenience.
152
153 inline void add_to_registry(const String& name, RPCFunction rpc_function){
154 rpc_registry[name] = rpc_function;
155 };
156
157 void rpc_call(const String& name, JsonArray args); //makes an RPC call, and handles returning values etc.
158 void send_index(); //returns an index of all the functions registered in the RPC.
159
160 protected:
161 void loop(); // should be run inside loop
162};
163
164
165#endif //rpc_h
void begin(usb_serial_class *target_usb_serial)
Initializes the RPC with the specified USB serial class.
void begin(HardwareSerialIMXRT *target_serial, uint32_t baud, uint16_t format=0)
Initializes the RPC with the specified hardware serial.
void begin()
Initializes the RPC with the specified stream. Defaults to Serial if no stream is provided.
void begin(Stream *target_stream)
Initializes the RPC with the specified stream.