Components/Hosted/include/Hosted/Client.h
Go to the documentation of this file.
1 /****
2  * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
3  * Created 2015 by Skurydin Alexey
4  * http://github.com/SmingHub/Sming
5  * All files of the Sming Core are provided under the LGPL v3 license.
6  *
7  * Client.h
8  *
9  * @author 2021 Slavey Karadzhov <slav@attachix.com>
10  *
11  *
12  ****/
13 
14 #pragma once
15 
16 #ifndef ARCH_HOST
17 #error "Hosted::Client can be used only on the Host architecture!"
18 #endif
19 
20 #include <Stream.h>
21 #include <WHashMap.h>
22 #include <WString.h>
23 #include <simpleRPC.h>
24 #include <simpleRPC/parser.h>
25 #include <hostlib/emu.h>
26 #include <hostlib/hostmsg.h>
27 #include "Util.h"
28 
29 using namespace simpleRPC;
30 
31 namespace Hosted
32 {
33 constexpr int COMMAND_NOT_FOUND = -1;
34 
35 class Client
36 {
37 public:
39 
40  Client(Stream& stream, char methodEndsWith = ':') : stream(stream), methodEndsWith(methodEndsWith)
41  {
42  }
43 
60  template <typename... Args> bool send(const String& functionName, Args... args)
61  {
62  int functionId = getFunctionId(functionName);
63  if(functionId == COMMAND_NOT_FOUND) {
64  return false;
65  }
66 
67  rpcPrint(stream, uint8_t(functionId), args...);
68  stream.flush();
69 
70  return true;
71  }
72 
77  template <typename R> R wait()
78  {
79  size_t neededBytes = sizeof(R);
80 
81  while(stream.available() < int(neededBytes)) {
82  stream.flush();
84  }
85 
86  R result{};
87  stream.readBytes(reinterpret_cast<char*>(&result), sizeof(result));
88  return result;
89  }
90 
97  {
98  if(fetchCommands) {
99  getRemoteCommands();
100  }
101 
102  if(name.indexOf('(') != -1 && name.indexOf(')') != -1) {
103  // most probably we have a name with a signature
104  name = convertFQN(name);
105  }
106 
107  int id = commands.indexOf(name);
108  if(id < 0) {
109  return COMMAND_NOT_FOUND;
110  }
111 
112  return commands[name];
113  }
114 
120  {
121  host_debug_i("Getting remote RPC commands \033[5m...\033[0m");
122 
123  uint8_t head = 0xff;
124  stream.write(&head, 1);
125  char buffer[512];
126  ParserSettings settings;
127  settings.startMethods = ParserSettings::SimpleMethod(&Client::startMethods, this);
128  settings.startMethod = ParserSettings::SimpleMethod(&Client::startMethod, this);
129  settings.methodSignature = ParserSettings::CharMethod(&Client::methodSignature, this);
130  settings.methodName = ParserSettings::CharMethod(&Client::methodName, this);
131  settings.endMethod = ParserSettings::SimpleMethod(&Client::endMethod, this);
132  settings.endMethods = ParserSettings::SimpleMethod(&Client::endMethods, this);
133  settings.state = ParserState::ready;
134 
135  do {
136  stream.flush();
137  host_main_loop();
138 
139  size_t length = stream.readBytes(buffer, 512);
140  if(!length) {
141  continue;
142  }
143 
144  ParserResult result = parse(settings, buffer, length, methodEndsWith);
145  if(result == ParserResult::finished) {
146  break;
147  }
148 
149  if(result == ParserResult::error) {
150  host_debug_e("Invalid header");
151  return false;
152  }
153  } while(true);
154 
155  host_debug_i("Available commands:");
156 
157  for(size_t i = 0; i < commands.count(); i++) {
158  host_debug_i("\t%s => %d", commands.keyAt(i).c_str(), commands.valueAt(i));
159  }
160 
161  host_debug_i("Connected. Starting application");
162 
163  return true;
164  }
165 
166 private:
167  Stream& stream;
168  bool fetchCommands{true};
169  RemoteCommands commands;
170  uint8_t methodPosition = 0;
171  String name;
172  String signature;
173  char methodEndsWith;
174 
175  void startMethods()
176  {
177  methodPosition = 0;
178  commands.clear();
179  }
180 
181  void startMethod()
182  {
183  name = "";
184  signature = "";
185  }
186 
187  void methodSignature(char ch)
188  {
189  signature += ch;
190  }
191 
192  void methodName(char ch)
193  {
194  name += ch;
195  }
196 
197  void endMethod()
198  {
199  if(!commands.contains(name) || signature == ":") {
200  commands[name] = methodPosition;
201  }
202  commands[name + "(" + signature + ")"] = methodPosition++;
203  }
204 
205  void endMethods()
206  {
207  fetchCommands = false;
208  }
209 };
210 
211 } // namespace Hosted
Base Stream class.
Definition: Wiring/Stream.h:32
String convertFQN(const String &name)
R wait()
This method will block the execution until a message is detected.
Definition: Components/Hosted/include/Hosted/Client.h:77
The String class.
Definition: WString.h:136
int host_main_loop()
Executing this function will run once the main emulator loop.
constexpr int COMMAND_NOT_FOUND
Definition: Components/Hosted/include/Hosted/Client.h:33
bool send(const String &functionName, Args... args)
Method to send commands to the remote server.
Definition: Components/Hosted/include/Hosted/Client.h:60
Definition: Components/Hosted/include/Hosted/Client.h:31
#define host_debug_e(fmt,...)
Definition: hostmsg.h:49
Client(Stream &stream, char methodEndsWith=':')
Definition: Components/Hosted/include/Hosted/Client.h:40
Definition: Components/Hosted/include/Hosted/Client.h:35
int getFunctionId(String name)
Fetches a list of commands supported on the RPC server and gives back the id of the desired command.
Definition: Components/Hosted/include/Hosted/Client.h:96
bool getRemoteCommands()
Gets list of remote command names and their ids.
Definition: Components/Hosted/include/Hosted/Client.h:119
int indexOf(char ch, size_t fromIndex=0) const
#define host_debug_i(fmt,...)
Definition: hostmsg.h:51