CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
Observations.cpp
Go to the documentation of this file.
1#include "Observations.h"
2#include "PsyTime.h"
3#include "UnitTestFramework.h"
4
5namespace cmlabs {
6
7
13
14
15uint32 GetObsTypeFromRawData(const char* data, uint32 size) {
16 if ((size <= sizeof(ObsHeader)) || (((ObsHeader*)data)->cid != OBSERVATIONID))
17 return 0;
18 return ((ObsHeader*)data)->type;
19}
20
21
22int64 ExtractIntFromMessage(const char* name, DataMessage* msg, std::map<std::string, std::string>& mappings, bool &ok) {
23 uint32 contentType;
24 ok = true;
25 std::map<std::string, std::string>::iterator i = mappings.find(name);
26 if (i != mappings.end()) {
27 if ((contentType = msg->getContentType(i->second.c_str())) == INTID)
28 return msg->getInt(i->second.c_str(), ok);
29 else if (contentType)
30 return msg->getAsInt(i->second.c_str());
31 }
32 else {
33 if ((contentType = msg->getContentType(name)) == INTID)
34 return msg->getInt(name);
35 else if (contentType)
36 return msg->getAsInt(name);
37 }
38 ok = false;
39 return 0;
40}
41
42float64 ExtractFloatFromMessage(const char* name, DataMessage* msg, std::map<std::string, std::string>& mappings, bool &ok) {
43 uint32 contentType;
44 ok = true;
45 std::map<std::string, std::string>::iterator i = mappings.find(name);
46 if (i != mappings.end()) {
47 if ((contentType = msg->getContentType(i->second.c_str())) == DOUBLEID)
48 return msg->getFloat(i->second.c_str(), ok);
49 else if (contentType)
50 return msg->getAsFloat(i->second.c_str());
51 }
52 else {
53 if ((contentType = msg->getContentType(name)) == DOUBLEID)
54 return msg->getFloat(name);
55 else if (contentType)
56 return msg->getAsFloat(name);
57 }
58 ok = false;
59 return 0;
60}
61
62uint64 ExtractTimeFromMessage(const char* name, DataMessage* msg, std::map<std::string, std::string>& mappings, bool &ok) {
63 uint32 contentType;
64 ok = true;
65 std::map<std::string, std::string>::iterator i = mappings.find(name);
66 if (i != mappings.end()) {
67 if ((contentType = msg->getContentType(i->second.c_str())) == TIMEID)
68 return msg->getTime(i->second.c_str(), ok);
69 //else if (contentType)
70 // return msg->getAsTime(i->second.c_str());
71 }
72 else {
73 if ((contentType = msg->getContentType(name)) == TIMEID)
74 return msg->getInt(name);
75 //else if (contentType)
76 // return msg->getAsTime(name);
77 }
78 ok = false;
79 return 0;
80}
81
82std::string ExtractStringFromMessage(const char* name, DataMessage* msg, std::map<std::string, std::string>& mappings, bool &ok) {
83 uint32 contentType;
84 ok = true;
85 std::map<std::string, std::string>::iterator i = mappings.find(name);
86 if (i != mappings.end()) {
87 if ((contentType = msg->getContentType(i->second.c_str())) == CONSTCHARID)
88 return msg->getString(i->second.c_str(), ok);
89 else if (contentType)
90 return msg->getAsString(i->second.c_str());
91 }
92 else {
93 if ((contentType = msg->getContentType(name)) == CONSTCHARID)
94 return msg->getString(name);
95 else if (contentType)
96 return msg->getAsString(name);
97 }
98 ok = false;
99 return "";
100}
101
102
104
105 // 1. Construction: default-constructed observation must carry the right
106 // object id, declared size and a type matching the payload struct.
107 unittest::progress(5, "construction");
108 {
110 if (loc.header.cid != OBSERVATIONID) {
111 unittest::fail("Observations test: default ctor cid != OBSERVATIONID\n");
112 return false;
113 }
114 if (loc.header.type != OBSID_LOC) {
115 unittest::fail("Observations test: ObsLocation type != OBSID_LOC\n");
116 return false;
117 }
118 if (loc.header.size != sizeof(ObsHeader)) {
119 unittest::fail("Observations test: header.size != sizeof(ObsHeader)\n");
120 return false;
121 }
122
124 if (i.header.type != OBSID_INT) {
125 unittest::fail("Observations test: ObsInteger type != OBSID_INT\n");
126 return false;
127 }
129 if (f.header.type != OBSID_FLOAT) {
130 unittest::fail("Observations test: ObsFloat type != OBSID_FLOAT\n");
131 return false;
132 }
133 if (GetObsTypeFromRawData((const char*)NULL, 0) != 0) {
134 unittest::fail("Observations test: GetObsTypeFromRawData(NULL) != 0\n");
135 return false;
136 }
137 }
138
139 // 2. extractDataFromMessage: pull observation payload + header metadata
140 // out of a DataMessage. ObsLocation reads x/y/z/... and the header
141 // reads ObjectID/Confidence/Consensus/SystemID/created-time.
142 unittest::progress(25, "extract from message");
143 {
144 DataMessage msg(CTRL_TEST, 0);
145 msg.setFloat("x", 1.5);
146 msg.setFloat("y", 2.5);
147 msg.setFloat("z", 3.5);
148 msg.setFloat("yaw", 0.25);
149 msg.setInt("mapid", 7);
150 msg.setInt("ObjectID", 4242);
151 msg.setFloat("Confidence", 0.9);
152 msg.setFloat("Consensus", 0.8);
153 msg.setSystemID(99);
154 const uint64 created = 1234567890ULL;
155 msg.setCreatedTime(created);
156
157 std::map<std::string, std::string> mappings; // identity mapping
158
160 if (!loc.extractDataFromMessage(&msg, mappings)) {
161 unittest::fail("Observations test: extractDataFromMessage failed\n");
162 return false;
163 }
164 if (loc.data.x < 1.49 || loc.data.x > 1.51 ||
165 loc.data.y < 2.49 || loc.data.y > 2.51 ||
166 loc.data.z < 3.49 || loc.data.z > 3.51) {
167 unittest::fail("Observations test: extracted location x/y/z mismatch\n");
168 return false;
169 }
170 if (loc.data.mapID != 7) {
171 unittest::fail("Observations test: extracted mapID mismatch\n");
172 return false;
173 }
174 if (loc.header.objectID != 4242) {
175 unittest::fail("Observations test: extracted objectID mismatch\n");
176 return false;
177 }
178 if (loc.header.creatorID != 99) {
179 unittest::fail("Observations test: extracted creatorID (systemID) mismatch\n");
180 return false;
181 }
182 if (loc.header.timestamp != created) {
183 unittest::fail("Observations test: extracted timestamp mismatch\n");
184 return false;
185 }
186 if (loc.header.confidence < 0.89 || loc.header.confidence > 0.91) {
187 unittest::fail("Observations test: extracted confidence mismatch\n");
188 return false;
189 }
190
191 // A message missing the required payload keys must fail extraction.
192 DataMessage bad(CTRL_TEST, 0);
194 if (badLoc.extractDataFromMessage(&bad, mappings)) {
195 unittest::fail("Observations test: extract from empty message should fail\n");
196 return false;
197 }
198 }
199
200 // 3. Mapping indirection: extractor should follow a name->key mapping so
201 // a differently-named message field still populates the observation.
202 unittest::progress(40, "extract via mappings");
203 {
204 DataMessage msg(CTRL_TEST, 0);
205 msg.setInt("reading", -55);
206 std::map<std::string, std::string> mappings;
207 mappings["val"] = "reading";
208
210 if (!obs.extractDataFromMessage(&msg, mappings)) {
211 unittest::fail("Observations test: mapped extract failed\n");
212 return false;
213 }
214 if (obs.data.val != -55) {
215 unittest::fail("Observations test: mapped value mismatch\n");
216 return false;
217 }
218 }
219
220 // 4. isBetween range test. NOTE: per the struct implementation, the method
221 // returns TRUE when the value is OUTSIDE [a,b] and FALSE when inside.
222 // We assert against the real implemented behaviour, not the name.
223 unittest::progress(55, "range / isBetween");
224 {
225 GenericObservation<ObsInteger> lo, hi, mid, out;
226 lo.data.val = 10;
227 hi.data.val = 20;
228 mid.data.val = 15;
229 out.data.val = 100;
230 if (mid.isBetween(&lo, &hi)) {
231 unittest::fail("Observations test: in-range value reported outside\n");
232 return false;
233 }
234 if (!out.isBetween(&lo, &hi)) {
235 unittest::fail("Observations test: out-of-range value reported inside\n");
236 return false;
237 }
238 }
239
240 // 5. writeToMessage / readFromMessage round-trip by id. The observation
241 // payload must survive a serialise-into / read-back-out of a message.
242 unittest::progress(70, "write / read round-trip");
243 {
244 DataMessage msg(CTRL_TEST, 0);
246 obs.data.val = 42.25;
247 obs.header.objectID = 555;
248 const uint64 id = 1001;
249 if (!obs.writeToMessage(&msg, id)) {
250 unittest::fail("Observations test: writeToMessage failed\n");
251 return false;
252 }
253 // Writing the same id twice must be refused.
254 if (obs.writeToMessage(&msg, id)) {
255 unittest::fail("Observations test: duplicate writeToMessage should fail\n");
256 return false;
257 }
258
260 if (!back.readFromMessage(&msg, id)) {
261 unittest::fail("Observations test: readFromMessage failed\n");
262 return false;
263 }
264 if (back.data.val < 42.24 || back.data.val > 42.26) {
265 unittest::fail("Observations test: round-trip value mismatch\n");
266 return false;
267 }
268 if (back.header.objectID != 555) {
269 unittest::fail("Observations test: round-trip objectID mismatch\n");
270 return false;
271 }
272 // readFromMessage with a non-existent id must fail.
274 if (none.readFromMessage(&msg, 7777)) {
275 unittest::fail("Observations test: readFromMessage of missing id should fail\n");
276 return false;
277 }
278 }
279
280 // 6. ReadAllFromMessage aggregate: write several observations into one
281 // message under sequential ids, read them all back as a list.
282 unittest::progress(85, "ReadAllFromMessage aggregate");
283 {
284 DataMessage msg(CTRL_TEST, 0);
285 const uint32 n = 5;
286 for (uint32 k = 1; k <= n; k++) {
288 obs.data.val = (int64)(k * 10);
289 if (!obs.writeToMessage(&msg, k)) {
290 unittest::fail("Observations test: writeToMessage in loop failed\n");
291 return false;
292 }
293 }
294 std::list<GenericObservation<ObsInteger>*> all =
296 if (all.size() != n) {
297 unittest::fail("Observations test: ReadAllFromMessage count mismatch\n");
298 std::list<GenericObservation<ObsInteger>*>::iterator ci = all.begin();
299 while (ci != all.end()) { delete *ci; ci++; }
300 return false;
301 }
302 int64 sum = 0;
303 std::list<GenericObservation<ObsInteger>*>::iterator i = all.begin(), e = all.end();
304 while (i != e) {
305 sum += (*i)->data.val;
306 // print() / toJSON() must produce non-empty output.
307 if ((*i)->print().empty() || (*i)->toJSON().empty()) {
308 unittest::fail("Observations test: print/toJSON empty\n");
309 std::list<GenericObservation<ObsInteger>*>::iterator ci = all.begin();
310 while (ci != all.end()) { delete *ci; ci++; }
311 return false;
312 }
313 delete *i;
314 i++;
315 }
316 if (sum != 10 + 20 + 30 + 40 + 50) {
317 unittest::fail("Observations test: ReadAllFromMessage value sum mismatch\n");
318 return false;
319 }
320 unittest::metric("observations_roundtrip", (double)n, "obs", true);
321 }
322
323 unittest::progress(100, "done");
324 return true;
325}
326
329 "Observation construct/extract/range/serialise round-trip", "data");
330}
331
332} // namespace cmlabs
#define CONSTCHARID
Definition ObjectIDs.h:38
#define OBSERVATIONID
Definition ObjectIDs.h:74
#define DOUBLEID
Definition ObjectIDs.h:42
#define INTID
Definition ObjectIDs.h:41
#define TIMEID
Definition ObjectIDs.h:39
Typed "observation" records extracted from DataMessages: location, dimension, integer,...
#define OBSID_LOC
#define OBSID_INT
#define OBSID_FLOAT
CMSDK time: µs-resolution 64-bit timestamps and the Time Mapping Constant (TMC).
Small, dependency-free unit test harness used by all CMSDK object tests.
The central Psyclone data container: a self-contained binary message with typed, named user entries.
bool setFloat(const char *key, float64 value)
setFloat(const char* key, float64 value)
bool getInt(const char *key, int64 &value)
getInt(const char* key, int64& value)
bool setInt(const char *key, int64 value)
setInt(const char* key, int64 value)
std::string getAsString(const char *key)
getAsString(const char* key)
bool setCreatedTime(uint64 t)
setCreatedTime(uint64 t)
uint64 getTime(const char *key)
getTime(const char* key)
bool setSystemID(uint32 id)
setSystemID(uint32 id) Set message system id
bool getFloat(const char *key, float64 &value)
getFloat(const char* key, float64& value)
int64 getAsInt(const char *key)
getAsInt(const char* key)
const char * getString(const char *key)
getString(const char* key)
uint32 getContentType(const char *key)
getContentType(const char* key)
float64 getAsFloat(const char *key)
getAsFloat(const char* key)
Header + typed payload wrapper for one observation, serializable to/from DataMessage and raw memory.
bool extractDataFromMessage(DataMessage *msg, std::map< std::string, std::string > &mappings)
Populate the payload from a message using logical→actual field mappings.
ObsHeader header
Identity/provenance/confidence header.
bool readFromMessage(DataMessage *msg, uint64 id=0)
Read the observation with id id (0 = first) from msg.
static std::list< GenericObservation< T > * > ReadAllFromMessage(DataMessage *msg)
Deserialize every observation of type T carried by msg.
bool writeToMessage(DataMessage *msg, uint64 id)
Append this observation to msg under entry id id.
bool isBetween(GenericObservation< T > *a, GenericObservation< T > *b)
Range test delegated to the payload's isBetween(); see per-type notes on semantics.
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void progress(int percent, const char *action)
Report progress with a short description of the current action.
std::string ExtractStringFromMessage(const char *name, DataMessage *msg, std::map< std::string, std::string > &mappings, bool &ok)
String variant of ExtractIntFromMessage(); same mapping semantics.
bool ObservationTest()
uint64 ExtractTimeFromMessage(const char *name, DataMessage *msg, std::map< std::string, std::string > &mappings, bool &ok)
Timestamp variant of ExtractIntFromMessage(); same mapping semantics.
float64 ExtractFloatFromMessage(const char *name, DataMessage *msg, std::map< std::string, std::string > &mappings, bool &ok)
Float variant of ExtractIntFromMessage(); same mapping semantics.
bool Observations_UnitTest()
Unit test for the Observations module.
uint32 GetObsTypeFromRawData(const char *data, uint32 size)
Inspect a raw observation blob and return its OBSID_* type tag from the embedded ObsHeader.
int64 ExtractIntFromMessage(const char *name, DataMessage *msg, std::map< std::string, std::string > &mappings, bool &ok)
Read an integer field from a message, translating name through mappings first.
static struct PsyType CTRL_TEST
Definition ObjectIDs.h:82
void Register_Observations_Tests()
Register the Observations tests with the unit test framework.
Fixed-size header preceding every observation payload: sizes/ids, provenance (creator,...