-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexample.cpp
More file actions
349 lines (293 loc) · 9.3 KB
/
example.cpp
File metadata and controls
349 lines (293 loc) · 9.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
/*
@file motion-sdk-cpp-example/example.cpp
@version 2.6
Copyright (c) 2018, Motion Workshop
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <Client.hpp>
#include <Format.hpp>
#include <fstream>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
/**
Utility class to store all of the options to run our Motion SDK data stream.
*/
class command_line_options {
public:
command_line_options();
int parse(int argc, char **argv);
int print_help(std::ostream *out, char *program_name);
std::string message;
std::string filename;
int frames;
std::string address;
unsigned port;
std::string separator;
std::string newline;
bool header;
}; // class command_line_options
/**
Convert the flat XML <node id="Hips" key="4" ... /> list into a map of
4 => "Hips" values. Matches the key of the Format maps.
*/
bool parse_name_map(
const std::string &xml_node_list,
std::map<std::size_t,std::string> &name_map);
/**
Connect to a Motion Service by IP address and port number. Defaults to
loopback (127.0.0.1) address and port 32076 (which is the Configurable data
service).
Request a list of channels by sending an XML list. Read frames forever and
write out one row in a CSV spreadsheet per frame.
Frames are a fixed size, so one channel is one column and one frame is one
row in the CSV format.
*/
int stream_data_to_csv(
std::ostream *output,
std::ostream *error,
const command_line_options &options)
{
using Motion::SDK::Client;
using Motion::SDK::Format;
// Open connection to the configurable data service.
Client client(options.address, options.port);
if (!client.isConnected()) {
*error
<< "failed to connect to Motion Service on " << options.address << ":"
<< options.port;
return -1;
}
{
// Request the channels that we want from every connected device. The full
// list is available here:
//
// https://www.motionshadow.com/download/media/configurable.xml
//
// Select the local quaternion (Lq) and positional constraint (c)
// channels here. 8 numbers per device per frame. Ask for inactive nodes
// which are not necessarily attached to a sensor but are animated as part
// of the Shadow skeleton.
const std::string xml_definition =
"<?xml version=\"1.0\"?>"
"<configurable inactive=\"1\">"
"<Lq/>"
"<c/>"
"</configurable>";
if (!client.writeData(
Client::data_type(xml_definition.begin(), xml_definition.end()))) {
*error
<< "failed to send channel list request to Configurable service";
return -1;
}
}
if (!client.waitForData(1)) {
*error
<< "no active data stream available, giving up";
return -1;
}
bool print_header = options.header;
std::map<std::size_t,std::string> name_map;
if (print_header) {
std::string xml_node_list;
if (client.getXMLString(xml_node_list)) {
if (!parse_name_map(xml_node_list, name_map)) {
*error
<< "failed to parse XML name map";
return -1;
}
}
}
int num_frames = 0;
for (;;) {
// Read one frame of data from all connected devices.
Client::data_type data;
if (!client.readData(data)) {
*error
<< "data stream interrupted or timed out";
return -1;
}
auto list = Format::Configurable(data.begin(), data.end());
if (print_header) {
const std::string ChannelName[] = {
"Lqw", "Lqx", "Lqy", "Lqz",
"cw", "cx", "cy", "cz"
};
bool have_output_line = false;
for (const auto &item : list) {
auto itr = name_map.find(item.first);
if (name_map.end() == itr) {
*error
<< "device missing from name map, unable to print header";
return -1;
}
if (8 != item.second.size()) {
*error
<< "expected 8 channels but found " << item.second.size()
<< ", unable to print header";
return -1;
}
for (std::size_t i=0; i<item.second.size(); ++i) {
if (have_output_line) {
*output << options.separator;
} else {
have_output_line = true;
}
*output << itr->second << "." << ChannelName[i];
}
}
if (!have_output_line) {
*error
<< "unknown data format, unabled to print header";
return -1;
}
*output << options.newline;
print_header = false;
}
// Iterate through the entries, one per device.
bool have_output_line = false;
for (const auto &item : list) {
// Iterate through the channels per device. From the channel list we
// know that each node has 8 channels.
// [Lqw, Lqx, Lqy, Lqz, cw, cx, cy, cz]
// Lq is unit quaternion rotation in the skeletal joint coordinate frame
// cw is unitless scalar, 0 is not constrained, 1 is fully constrained
// cx, cy, cz are global position in centimeters
for (std::size_t i=0; i<item.second.size(); ++i) {
if (have_output_line) {
*output << options.separator;
} else {
have_output_line = true;
}
*output << item.second[i];
}
}
if (!have_output_line) {
*error
<< "unknown data format in stream";
return -1;
}
*output << options.newline;
if (options.frames > 0) {
if (++num_frames >= options.frames) {
break;
}
}
}
return 0;
}
bool parse_name_map(
const std::string &xml_node_list,
std::map<std::size_t,std::string> &name_map)
{
// If you have an XML parser go ahead and use that instead. For this example,
// we will use regular expressions to avoid a dependency on an external
// library.
std::regex re("<node id=\"([^\"]+)\" key=\"(\\d+)\"");
auto itr = std::sregex_iterator(
xml_node_list.begin(), xml_node_list.end(),
re);
bool result = false;
for (; itr != std::sregex_iterator(); ++itr) {
const int key = std::stoi((*itr)[2].str());
if (key > 0) {
name_map.emplace(key, (*itr)[1]);
result = true;
}
}
return result;
}
int main(int argc, char **argv)
{
command_line_options options;
if (0 != options.parse(argc, argv)) {
return options.print_help(&std::cerr, *argv);
}
// Stream frames to a CSV spreadsheet file.
std::ofstream fout;
if (!options.filename.empty()) {
fout.open(options.filename, std::ios_base::out | std::ios_base::binary);
}
// Capture error messages so we do not interfere with the CSV output stream.
std::ostringstream err;
const int result = stream_data_to_csv(
fout.is_open() ? &fout : &std::cout,
&err,
options);
if (0 != result) {
std::cerr << err.str();
}
return result;
}
command_line_options::command_line_options()
: message(), filename(), frames(), address("127.0.0.1"), port(32076),
separator(","), newline("\n"), header(false)
{
}
int command_line_options::parse(int argc, char **argv)
{
for (int i=1; i<argc; ++i) {
const std::string arg(argv[i]);
if ("--file" == arg) {
++i;
if (i < argc) {
filename = argv[i];
} else {
message = "Missing required argument for --file";
return -1;
}
} else if ("--frames" == arg) {
++i;
if (i < argc) {
frames = std::stoi(argv[i]);
} else {
message = "Missing required argument for --frames";
return -1;
}
} else if ("--header" == arg) {
header = true;
} else if ("--help" == arg) {
return 1;
} else {
message = "Unrecognized option \"" + arg + "\"";
return -1;
}
}
return 0;
}
int command_line_options::print_help(std::ostream *out, char *program_name)
{
if (!message.empty()) {
*out
<< message << newline << newline;
}
*out
<< "Usage: " << program_name << " [options...]" << newline
<< newline
<< "Allowed options:" << newline
<< " --help show help message" << newline
<< " --file arg output file" << newline
<< " --frames N read N frames" << newline
<< " --header show channel names in the first row" << newline
<< newline;
return 1;
}