Loop.cpp
Go to the documentation of this file.
1 #include <swarmio/tool/Loop.h>
2 #include <swarmio/tool/Command.h>
5 #include <swarmio/Exception.h>
6 #include <swarmio/data/Helper.h>
7 #include <iomanip>
8 #include <iostream>
9 #include <exception>
10 #include <chrono>
11 #include <regex>
12 
13 using namespace swarmio;
14 using namespace swarmio::tool;
15 using namespace std::literals::chrono_literals;
16 
17 Loop::Loop(Endpoint* endpoint, LogBuffer* logBuffer)
18  : ClientProfile(endpoint), _logBuffer(logBuffer)
19 {
20  _repl.install_window_change_handler();
21  _repl.set_highlighter_callback(&Command::HighlighterCallback, nullptr);
23 }
24 
25 void Loop::NodeWasDiscovered(const Node* node) noexcept
26 {
27  std::lock_guard<std::mutex> guard(_mutex);
28 
29  // Add to vector
30  _nodes.push_back(node);
31 }
32 
33 std::string Loop::GetPrompt()
34 {
35  if (_selectedNode == nullptr)
36  {
37  return std::string("\x1b[1;32mswarm\x1b[0m> ");
38  }
39  else
40  {
41  return std::string("\x1b[1;32mmembr\x1b[0m> ");
42  }
43 }
44 
45 void Loop::Run()
46 {
47  for (;;)
48  {
49  // Get command
50  const char* input = _repl.input(GetPrompt());
51  if (input == nullptr)
52  {
53  if (errno == EAGAIN)
54  {
55  // Retry
56  continue;
57  }
58  else
59  {
60  // Exit
61  break;
62  }
63  }
64  else if (*input == '\0')
65  {
66  // Empty command
67  continue;
68  }
69 
70  // Add to history
71  std::string command = input;
72  _repl.history_add(command);
73 
74  // Parse and execute command
75  if (!ExecuteCommand(command))
76  {
77  break;
78  }
79  }
80 }
81 
82 void Loop::ExecuteInfoCommand(const Command& command)
83 {
84  if (_selectedNode != nullptr)
85  {
86  // UUID
87  std::cout << "UUID: " << _selectedNode->GetUUID() << std::endl;
88  std::cout << "Name: " << _selectedNode->GetName() << std::endl;
89  std::cout << "Device class: " << _selectedNode->GetDeviceClass() << std::endl;
90 
91  // Send request
93 
94  // Wait for request
95  if (awaiter.WaitForResponse(5s))
96  {
97  auto response = awaiter.GetResponse();
98 
99  // Echo support
100  std::cout << "Pingable: " << (response.echo_enabled() ? "true" : "false") << std::endl;
101 
102  // Keys
103  if (response.keyvalue_schema().fields_size() > 0)
104  {
105  std::cout << "Parameters: ";
106  data::Helper::WriteToStream(std::cout, response.keyvalue_schema());
107  std::cout << std::endl;
108  }
109 
110  // Events
111  if (response.event_schema().fields_size() > 0)
112  {
113  std::cout << "Events: ";
114  data::Helper::WriteToStream(std::cout, response.event_schema());
115  std::cout << std::endl;
116  }
117 
118  // Telemetry
119  if (response.telemetry_schema().fields_size() > 0)
120  {
121  std::cout << "Telemetry: ";
122  data::Helper::WriteToStream(std::cout, response.telemetry_schema());
123  std::cout << std::endl;
124  }
125  }
126  else
127  {
128  std::cout << "Discovery timed out." << std::endl;
129  }
130 
131  // Status
133  if (report.values_size() > 0)
134  {
135  std::cout << "Current status: ";
136  data::Helper::WriteToStream(std::cout, report.values());
137  std::cout << std::endl;
138  }
139  }
140  else
141  {
142  std::cout << "No member selected." << std::endl;
143  }
144 }
145 
147 {
149  std::cout << "A global discovery request was sent." << std::endl;
150 }
151 
152 void Loop::ExecuteGetCommand(const Command& command)
153 {
154  if (_selectedNode != nullptr)
155  {
156  if (command.HasPath())
157  {
159  if (awaiter.WaitForResponse(5s))
160  {
161  auto value = awaiter.GetResponse();
162  switch (value.value_case())
163  {
164  case data::Variant::ValueCase::kBoolValue:
165  std::cout << (value.bool_value() ? "true" : "false") << std::endl;
166  break;
167 
168  case data::Variant::ValueCase::kDoubleValue:
169  std::cout << value.double_value() << std::endl;
170  break;
171 
172  case data::Variant::ValueCase::kIntValue:
173  std::cout << value.int_value() << std::endl;
174  break;
175 
176  case data::Variant::ValueCase::kStringValue:
177  std::cout << "\"" << value.string_value() << "\"" << std::endl;
178  break;
179 
180  default:
181  std::cout << "<unknown>" << std::endl;
182  break;
183  }
184  }
185  else
186  {
187  std::cout << "Timeout." << std::endl;
188  }
189  }
190  else
191  {
192  std::cout << "No resource path specified." << std::endl;
193  }
194  }
195  else
196  {
197  std::cout << "No member selected." << std::endl;
198  }
199 }
200 
201 void Loop::ExecuteSetCommand(const Command& command)
202 {
203  if (_selectedNode != nullptr)
204  {
205  if (command.GetParameters().size() == 1)
206  {
207  auto pair = command.GetParameters().begin();
208  auto awaiter = services::keyvalue::Service::Set(GetEndpoint(), _selectedNode, pair->first, ConvertToVariant(pair->second));
209  if (awaiter.WaitForResponse(5s))
210  {
211  if (awaiter.GetResponse())
212  {
213  std::cout << "Value was set." << std::endl;
214  }
215  else
216  {
217  std::cout << "Request rejected." << std::endl;
218  }
219  }
220  else
221  {
222  std::cout << "Timeout." << std::endl;
223  }
224  }
225  else
226  {
227  std::cout << "Invalid number of parameters." << std::endl;
228  }
229  }
230  else
231  {
232  std::cout << "No member selected." << std::endl;
233  }
234 }
235 
236 void Loop::ExecutePingCommand(const Command& command)
237 {
238  if (_selectedNode != nullptr)
239  {
240  // Determine packet size
241  int size = 1024;
242  if (command.HasPath())
243  {
244  // Parse
245  size = std::stoi(command.GetPath());
246 
247  // Check
248  if (size < 0 || size > 1024 * 1024 * 256)
249  {
250  std::cout << "Invalid ping packet size." << std::endl;
251  }
252  }
253 
254  // Send request
255  auto awaiter = _pingService.Ping(GetEndpoint(), _selectedNode, size);
256 
257  // Wait for request
258  if (awaiter.WaitForResponse(5s))
259  {
260  std::cout << "Response time: " << std::fixed << std::setprecision(2) << awaiter.GetResponseInMilliseconds() << "ms" << std::endl;
261  }
262  else
263  {
264  std::cout << "Timeout." << std::endl;
265  }
266  }
267  else
268  {
269  std::cout << "No member selected." << std::endl;
270  }
271 }
272 
273 void Loop::ExecuteEventCommand(const Command& command)
274 {
275  if (command.HasPath())
276  {
277  // Build events
278  data::event::Notification event;
279  event.set_name(command.GetPath());
280 
281  // Add parameters
282  for (auto p : command.GetParameters())
283  {
284  (*event.mutable_parameters())[p.first] = ConvertToVariant(p.second);
285  }
286 
287  // Send event
288  if (_selectedNode == nullptr)
289  {
291  std::cout << "Event broadcasted." << std::endl;
292  }
293  else
294  {
296  if (awaiter.WaitForResponse(5s))
297  {
298  if (awaiter.GetResponse())
299  {
300  std::cout << "Event handled remotely." << std::endl;
301  }
302  else
303  {
304  std::cout << "Event rejected." << std::endl;
305  }
306  }
307  else
308  {
309  std::cout << "Timeout." << std::endl;
310  }
311  }
312  }
313  else
314  {
315  std::cout << "No event specified." << std::endl;
316  }
317 }
318 
320 {
321  std::lock_guard<std::mutex> guard(_mutex);
322  if (_nodes.size() > 0)
323  {
324  // Header
325  std::cout << "MID" << "\t" << "UUID" << "\t" << "Class" << "\t" << "Name" << std::endl;
326 
327  // List nodes
328  for (unsigned i = 0; i < _nodes.size(); ++i)
329  {
330  std::cout << i << "\t" << _nodes[i]->GetUUID() << "\t" << _nodes[i]->GetDeviceClass() << "\t" << _nodes[i]->GetName() << "\t" << _nodes[i]->GetDescription() << std::endl;
331  }
332  }
333  else
334  {
335  std::cout << "No swarm members found." << std::endl;
336  }
337 }
338 
340 {
341  // Check if we have a parameter
342  if (command.HasPath())
343  {
344  // Parse
345  unsigned idx = std::stoul(command.GetPath());
346 
347  // Retreive from nodes vector
348  std::lock_guard<std::mutex> guard(_mutex);
349  if (idx < _nodes.size())
350  {
351  _selectedNode = _nodes[idx];
352  std::cout << "Member selected: " << _selectedNode->GetUUID() << std::endl;
353  }
354  else
355  {
356  std::cout << "Invalid member index." << std::endl;
357  }
358  }
359  else if (_selectedNode != nullptr)
360  {
361  _selectedNode = nullptr;
362  std::cout << "Member unselected." << std::endl;
363  }
364  else
365  {
366  std::cout << "No effect, no member was selected." << std::endl
367  << "To select a member, specify a MID." << std::endl;
368  }
369 }
370 
372 {
373  if (_subscriptions.size() > 0)
374  {
375  for (auto& subscription : _subscriptions)
376  {
377  std::cout << "Subscription #" << subscription.GetIdentifier() << std::endl;
378  std::cout << " " << "Node: " << subscription.GetTarget()->GetUUID() << std::endl;
379  if (subscription.WaitForResponse(0ms))
380  {
381  auto response = subscription.GetResponse();
382 
383  // Tick
384  std::cout << " " << "Tick: " << response.tick() << std::endl;
385 
386  // Values
387  if (response.values_size() > 0)
388  {
389  std::cout << " " << "Current values: ";
390  data::Helper::WriteToStream(std::cout, response.values(), true, 1);
391  std::cout << std::endl;
392  }
393  else
394  {
395  std::cout << " " << "Last update was empty." << std::endl;
396  }
397  }
398  else
399  {
400  std::cout << " " << "No update received so far." << std::endl;
401  }
402  }
403  }
404  else
405  {
406  std::cout << "No subscriptions." << std::endl;
407  }
408 }
409 
411 {
412  if (_selectedNode != nullptr)
413  {
414  // Determine interval
415  uint32_t interval = 1;
416  auto intervalParameter = command.GetParameters().find("interval");
417  if (intervalParameter != command.GetParameters().end())
418  {
419  interval = std::stoi(intervalParameter->second);
420  }
421 
422  // Determine keys
423  std::list<std::string> keys;
424  auto keyParameter = command.GetParameters().find("key");
425  if (keyParameter != command.GetParameters().end())
426  {
427  keys.push_back(keyParameter->second);
428  }
429 
430  // Submit request
432 
433  // Success
434  std::cout << "Subscribed." << std::endl;
435  }
436  else
437  {
438  // Error
439  std::cout << "No member selected." << std::endl;
440  }
441 }
442 
444 {
445  uint64_t identifier = std::stoi(command.GetPath());
446  auto element = std::find_if(_subscriptions.begin(), _subscriptions.end(), [identifier](const services::telemetry::UpdateAwaiter& awaiter){ return awaiter.GetIdentifier() == identifier; });
447  if (element != _subscriptions.end())
448  {
449  _subscriptions.erase(element);
450  std::cout << "Unsubcribed." << std::endl;
451  }
452  else
453  {
454  std::cout << "Not found." << std::endl;
455  }
456 }
457 
458 void Loop::ExecuteLogCommand(const Command& command)
459 {
460  if (_logBuffer != nullptr)
461  {
462  for (auto& message : _logBuffer->GetMessages())
463  {
464  std::cout << message.timestamp() << "\t" << message.level() << "\t" << message.message() << std::endl;
465  }
466  }
467  else
468  {
469  std::cout << "No log buffer found." << std::endl;
470  }
471 }
472 
473 bool Loop::ExecuteCommand(const std::string& input)
474 {
475  try
476  {
477  // Parse command
478  auto command = Command::Parse(input);
479 
480  // Handle verbs
481  if (command.Is("members"))
482  {
484  }
485  else if (command.Is("select"))
486  {
488  }
489  else if (command.Is("info"))
490  {
492  }
493  else if (command.Is("ping"))
494  {
496  }
497  else if (command.Is("event"))
498  {
500  }
501  else if (command.Is("set"))
502  {
504  }
505  else if (command.Is("rediscover"))
506  {
508  }
509  else if (command.Is("get"))
510  {
512  }
513  else if (command.Is("subscriptions"))
514  {
516  }
517  else if (command.Is("subscribe"))
518  {
520  }
521  else if (command.Is("unsubscribe"))
522  {
524  }
525  else if (command.Is("log"))
526  {
528  }
529  else if (command.Is("help"))
530  {
531  // Display help
532  std::cout << "Available commands:" << std::endl
533  << " - members" << std::endl
534  << " - rediscover" << std::endl
535  << " - info" << std::endl
536  << " - select [MID]" << std::endl
537  << " - event NAME [KEY=VALUE]..." << std::endl
538  << " - get KEY" << std::endl
539  << " - set KEY=VALUE" << std::endl
540  << " - subscriptions" << std::endl
541  << " - subscribe [key=KEY] [interval=N]" << std::endl
542  << " - unsubscribe [SID]" << std::endl
543  << " - ping [SIZE]" << std::endl
544  << " - help" << std::endl
545  << " - log" << std::endl
546  << " - exit" << std::endl;
547  }
548  else if (command.Is("exit"))
549  {
550  // Say goodbye
551  std::cout << "Goodbye!" << std::endl;
552 
553  // Exit
554  return false;
555  }
556  else
557  {
558  // Unknown command
559  std::cout << "Unknown command verb: " << command.GetVerb() << std::endl
560  << "Try 'help' to list available commands." << std::endl;
561  }
562  }
563  catch (const std::exception& e)
564  {
565  std::cout << e.what() << std::endl;
566  }
567 
568  // Continue
569  return true;
570 }
571 
572 data::Variant Loop::ConvertToVariant(const std::string& value)
573 {
574  if (std::regex_match(value, std::regex("[0-9]+", std::regex_constants::icase)))
575  {
576  // Integer
577  data::Variant variant;
578  variant.set_int_value(std::stoi(value));
579  return variant;
580  }
581  else if (std::regex_match(value, std::regex("[0-9]+\\.[0-9]+", std::regex_constants::icase)))
582  {
583  // Double
584  data::Variant variant;
585  variant.set_double_value(std::stod(value));
586  return variant;
587  }
588  else if (std::regex_match(value, std::regex("true", std::regex_constants::icase)))
589  {
590  // Bool - true
591  data::Variant variant;
592  variant.set_bool_value(true);
593  return variant;
594  }
595  else if (std::regex_match(value, std::regex("false", std::regex_constants::icase)))
596  {
597  // Bool - false
598  data::Variant variant;
599  variant.set_bool_value(false);
600  return variant;
601  }
602  else
603  {
604  // String
605  data::Variant variant;
606  variant.set_string_value(value);
607  return variant;
608  }
609 }
virtual const std::string & GetDeviceClass() const =0
Get the class of the underlying device.
static void Trigger(Endpoint *endpoint, const data::event::Notification &event)
Trigger an event globally.
double GetResponseInMilliseconds()
Translate the precise response to a floating point number of milliseconds.
static void HighlighterCallback(const std::string &input, replxx::Replxx::colors_t &colors, void *unused)
Highlighter callback for commands.
Definition: Command.cpp:8
static void WriteToStream(std::ostream &stream, const Variant &value, bool prettyPrint=true, int indentationLevel=0)
Write a string representation of a variant to the stream.
Definition: Helper.cpp:255
DiscoveryAwaiter CachedQuery(const Node *node)
Send a Discovery query to a remote node, or if the information already exists in the cache...
void ExecuteMembersCommand(const Command &command)
Execute a &#39;members&#39; command.
Definition: Loop.cpp:319
XmlRpcServer s
static UpdateAwaiter Subscribe(Endpoint *endpoint, const Node *node, uint32_t interval=1)
Subscribe to all named values on the remote node.
static TimingAwaiter Ping(Endpoint *endpoint, const Node *node, size_t size)
Measure the latency to a remote node.
std::vector< const Node * > _nodes
Nodes and their indices.
Definition: Loop.h:52
std::mutex _mutex
Mutex to protect nodes vector.
Definition: Loop.h:64
void FinishConstruction()
Called when the last constructor has finished its job.
Definition: Mailbox.h:46
void ExecuteSetCommand(const Command &command)
Execute an &#39;set&#39; command.
Definition: Loop.cpp:201
void ExecuteEventCommand(const Command &command)
Execute an &#39;event&#39; command.
Definition: Loop.cpp:273
Loop(Endpoint *endpoint, LogBuffer *logBuffer)
Construct a new Loop object.
Definition: Loop.cpp:17
std::list< services::telemetry::UpdateAwaiter > _subscriptions
Currently held subscriptions.
Definition: Loop.h:58
swarmio::services::discovery::Service _discoveryService
Discovery service.
Definition: Profile.h:24
std::list< g3::LogMessage > GetMessages()
Get a copy of the list of messages.
Definition: LogBuffer.h:52
bool HasPath() const
Does the command have a path specified?
Definition: Command.h:79
swarmio::services::telemetry::Service _telemetryService
Telemetry service.
Definition: Profile.h:36
void ExecuteLogCommand(const Command &command)
Execute an &#39;log&#39; command.
Definition: Loop.cpp:458
void ExecutePingCommand(const Command &command)
Execute a &#39;ping&#39; command.
Definition: Loop.cpp:236
static Command Parse(std::string command)
Parse a string as a command.
Definition: Command.cpp:108
void GlobalQuery()
Sends a global Discovery request.
std::string GetPrompt()
Build prompt string.
Definition: Loop.cpp:33
void ExecuteGetCommand(const Command &command)
Execute an &#39;get&#39; command.
Definition: Loop.cpp:152
data::telemetry::Status GetCachedStatus(const Node *node)
static ErrorAwaiter Set(Endpoint *endpoint, const Node *node, const std::string &path, const data::Variant &value)
Set a remote value.
const Node * _selectedNode
Selected node.
Definition: Loop.h:33
ROSLIB_DECL std::string command(const std::string &cmd)
An Awaiter that has a longer lifetime and is updated periodically.
Definition: UpdateAwaiter.h:12
void Run()
Run the command loop.
Definition: Loop.cpp:45
Abstract base class for Endpoint implementations.
Definition: Endpoint.h:25
void ExecuteSelectCommand(const Command &command)
Execute a &#39;select&#39; command.
Definition: Loop.cpp:339
LogBuffer * _logBuffer
Log buffer.
Definition: Loop.h:39
Endpoint * GetEndpoint()
Get the associated Endpoint.
Definition: Mailbox.h:144
T GetResponse()
Get the response value. Will throw an exception if called before the response is received.
Definition: Awaiter.h:127
bool ExecuteCommand(const std::string &command)
Execute a command given by the user.
Definition: Loop.cpp:473
virtual const std::string & GetUUID() const =0
Returns the unique identifier of the node.
static ValueAwaiter Get(Endpoint *endpoint, const Node *node, const std::string &path)
Get a remote value.
const std::map< std::string, std::string > & GetParameters() const
Get the map of parameters.
Definition: Command.h:119
const std::string & GetPath() const
Get the path.
Definition: Command.h:109
void ExecuteSubscribeCommand(const Command &command)
Execute a &#39;subscribe&#39; command.
Definition: Loop.cpp:410
void ExecuteRediscoverCommand(const Command &command)
Execute an &#39;rediscover&#39; command.
Definition: Loop.cpp:146
data::Variant ConvertToVariant(const std::string &value)
Convert a string parameter to a variant.
Definition: Loop.cpp:572
replxx::Replxx _repl
REPL interface.
Definition: Loop.h:27
void ExecuteSubscriptionsCommand(const Command &command)
Execute a &#39;subscriptions&#39; command.
Definition: Loop.cpp:371
void ExecuteInfoCommand(const Command &command)
Execute an &#39;info&#39; command.
Definition: Loop.cpp:82
virtual const std::string & GetName() const =0
Returns the (possibly non-unique) name of the node.
Represents a Node the Endpoint knows about and can send messages to.
swarmio::services::ping::Service _pingService
Ping service.
Definition: Profile.h:30
Parser for commands of the default form.
Definition: Command.h:15
void ExecuteUnsubscribeCommand(const Command &command)
Execute an &#39;unsubscribe&#39; command.
Definition: Loop.cpp:443
virtual void NodeWasDiscovered(const Node *node) noexceptoverride
Called when a new Node has been discovered.
Definition: Loop.cpp:25


swarmros
Author(s):
autogenerated on Fri Apr 3 2020 03:42:48