Skip to content

Commit c0b9fce

Browse files
authored
Merge pull request #6 from CDPTechnologies/EventDataHandling
Event data handling
2 parents 423fedd + 44133d4 commit c0b9fce

4 files changed

Lines changed: 337 additions & 204 deletions

File tree

client.js

Lines changed: 173 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* client.js */
1+
// client.js
22
const WebSocket = require('ws');
33
const root = require('./generated/containerPb.js');
44
const Container = root.DBMessaging.Protobuf.Container;
@@ -21,9 +21,7 @@ class Client {
2121

2222
this.reqId = -1;
2323
this.autoReconnect = autoReconnect;
24-
25-
// Time synchronization is enabled by default.
26-
this.enableTimeSync = true;
24+
this.enableTimeSync = true; // Time synchronization is enabled by default.
2725

2826
this.isOpen = false;
2927
this.queuedRequests = {};
@@ -129,6 +127,9 @@ class Client {
129127

130128
// --- Public API methods ---
131129

130+
/**
131+
* Request the API version.
132+
*/
132133
requestApiVersion() {
133134
this._timeRequest();
134135
const requestId = this._getRequestId();
@@ -142,6 +143,9 @@ class Client {
142143
});
143144
}
144145

146+
/**
147+
* Request the list of logged nodes.
148+
*/
145149
requestLoggedNodes() {
146150
this._timeRequest();
147151
const requestId = this._getRequestId();
@@ -155,6 +159,9 @@ class Client {
155159
});
156160
}
157161

162+
/**
163+
* Request the log limits.
164+
*/
158165
requestLogLimits() {
159166
this._timeRequest();
160167
const requestId = this._getRequestId();
@@ -168,6 +175,14 @@ class Client {
168175
});
169176
}
170177

178+
/**
179+
* Request data points for given node names and time range.
180+
* @param {Array<string>} nodeNames
181+
* @param {number} startS
182+
* @param {number} endS
183+
* @param {number} noOfDataPoints
184+
* @returns {Promise<Array>}
185+
*/
171186
requestDataPoints(nodeNames, startS, endS, noOfDataPoints) {
172187
this._timeRequest();
173188
const requestId = this._getRequestId();
@@ -186,62 +201,63 @@ class Client {
186201
* Request events based on the provided query parameters.
187202
*
188203
* The `query.flags` field uses bitmask values similar to an enum:
189-
* 0 = None
190-
* 1 = NewestFirst
191-
* 2 = TimeRangeBeginExclusive
192-
* 4 = TimeRangeEndExclusive
193-
* 8 = UseLogStampForTimeRange
194-
*
195-
* The `query.senderConditions` field can be used to filter by event sender (Source).
196-
* The `query.dataConditions` field can be used to filter by data fields (key-value patterns).
197204
*
198-
* For additional information:
199-
* https://cdpstudio.com/manual/cdp/cdplogger/eventlogreader.html#Flags-enum
205+
* 0 = None
206+
* 1 = NewestFirst
207+
* 2 = TimeRangeBeginExclusive
208+
* 4 = TimeRangeEndExclusive
209+
* 8 = UseLogStampForTimeRange
210+
*
211+
* For additional information:
212+
* https://cdpstudio.com/manual/cdp/cdp2sql/logmanager-eventquery.html#Flags-enum
200213
* https://cdpstudio.com/manual/cdp/cdplogger/eventlogreader.html#cdp-event-code-flags
201214
*
202-
* We also support mapping the `evt.code` field to a human-readable string
203-
* with `getEventCodeDescription()`.
215+
* In addition, the user can simply supply the following properties in the query object:
216+
*
217+
* - **senderConditions**: An array of sender strings (exact matches by default).
218+
* - **dataConditions**: An object where each key is a data field name and the value can be:
219+
* - A string (defaults to an exact match),
220+
* - An array of strings,
221+
* - An object (or array of objects) with properties:
222+
* - `value`: the string value to match,
223+
* - `matchType`: either `"exact"` (default) or `"wildcard"`.
224+
*
225+
* enum EventQuery::MatchType:
226+
* - Exact (0): The string must match exactly.
227+
* - Wildcard (1): The string may contain wildcards.
228+
*
229+
* Example usage:
204230
*
205-
* @param {Object} query - An object matching the EventQuery schema.
206-
* Example:
207-
* {
208-
* timeRangeBegin: 1620000000,
209-
* timeRangeEnd: 1620003600,
210-
* codeMask: 0xFFFFFFFF,
211-
* limit: 100,
212-
* offset: 0,
213-
* flags: 1, // e.g. 'NewestFirst'
231+
* // Filter for events with sender exactly "CDPLoggerDemoApp.InvalidLicense":
232+
* { senderConditions: ["CDPLoggerDemoApp.InvalidLicense"] }
214233
*
215-
* // Example conditions:
216-
* senderConditions: {
217-
* conditions: [
218-
* { value: "*TemperatureSensor*", type: 1 } // 1 = Wildcard
219-
* ]
220-
* },
221-
* dataConditions: {
222-
* pressure: {
223-
* conditions: [
224-
* { value: "high", type: 0 } // 0 = Exact
225-
* ]
226-
* }
227-
* }
228-
* }
234+
* // Filter for events where the "Text" data field equals "Invalid or missing feature license detected.":
235+
* { dataConditions: { "Text": "Invalid or missing feature license detected." } }
229236
*
237+
*
238+
* @param {Object} query - A simple plain object representing the EventQuery.
230239
* @returns {Promise<Array>} Resolves with an array of events (each event includes a 'codeDescription').
231240
*/
232241
requestEvents(query) {
233242
this._timeRequest();
234243
const requestId = this._getRequestId();
244+
// Convert the simple query into a proper EventQuery message.
245+
const eventQuery = this._buildEventQuery(query);
246+
247+
248+
235249
if (!this.isOpen) {
236-
this.queuedRequests[requestId] = { type: "events", query: query };
250+
this.queuedRequests[requestId] = { type: "events", query: eventQuery };
237251
} else {
238-
this._sendEventsRequest(requestId, query);
252+
this._sendEventsRequest(requestId, eventQuery);
239253
}
240254
return new Promise((resolve, reject) => {
241255
this.storedPromises[requestId] = { resolve, reject };
242256
});
243257
}
244258

259+
// --- Internal methods ---
260+
245261
_sendEventsRequest(requestId, query) {
246262
const container = Container.create();
247263
container.messageType = Container.Type.eEventsRequest;
@@ -250,6 +266,74 @@ class Client {
250266
this.ws.send(buffer);
251267
}
252268

269+
/**
270+
* Helper method to build a proper EventQuery message from a simple plain object.
271+
*
272+
*
273+
*
274+
*
275+
* @param {Object} query - The simple plain object query.
276+
* @returns {DBMessaging.Protobuf.EventQuery} - The built EventQuery message.
277+
*/
278+
_buildEventQuery(query) {
279+
const root = require('./generated/containerPb.js');
280+
const { EventQuery } = root.DBMessaging.Protobuf;
281+
const { MatchType } = EventQuery;
282+
283+
// Create base query with primitive fields
284+
const baseQuery = {
285+
timeRangeBegin: query.timeRangeBegin || 0,
286+
timeRangeEnd: query.timeRangeEnd || Math.floor(Date.now() / 1000),
287+
codeMask: query.codeMask !== undefined ? query.codeMask : 0xFFFFFFFF,
288+
limit: query.limit || 50,
289+
offset: query.offset || 0,
290+
flags: query.flags || 0
291+
};
292+
293+
// Build sender conditions if present
294+
if (query.senders && query.senders.length > 0) {
295+
baseQuery.senderConditions = {
296+
conditions: query.senders.map(sender => ({
297+
value: sender,
298+
type: MatchType.Exact
299+
}))
300+
};
301+
}
302+
303+
// Build data conditions if present
304+
if (query.dataConditions) {
305+
const dataConds = {};
306+
for (const key in query.dataConditions) {
307+
const val = query.dataConditions[key];
308+
const conditions = [];
309+
310+
if (Array.isArray(val)) {
311+
for (const item of val) {
312+
conditions.push({
313+
value: String(item),
314+
type: MatchType.Exact
315+
});
316+
}
317+
} else {
318+
conditions.push({
319+
value: String(val),
320+
type: MatchType.Exact
321+
});
322+
}
323+
324+
// Store without any table qualification
325+
dataConds[key] = { conditions };
326+
}
327+
baseQuery.dataConditions = dataConds;
328+
}
329+
330+
return EventQuery.create(baseQuery);
331+
}
332+
333+
334+
335+
336+
253337
/**
254338
* Converts a numeric CDP event code into a descriptive string.
255339
* Multiple flags can be set simultaneously, so we combine them.
@@ -262,8 +346,8 @@ class Client {
262346
* 0x100 = SourceObjectUnavailable
263347
* 0x40000000 = NodeBoot
264348
*
265-
* @param {number} code - The event code from an eEventsResponse
266-
* @returns {string} - A human-readable combination of flags
349+
* @param {number} code - The event code from an eEventsResponse.
350+
* @returns {string} - A human-readable combination of flags.
267351
*/
268352
getEventCodeDescription(code) {
269353
const flags = [];
@@ -280,6 +364,51 @@ class Client {
280364
return flags.join(" + ");
281365
}
282366

367+
/**
368+
* Returns a human‐readable string for a given event code.
369+
*
370+
* @param {number} code - The numeric event code.
371+
* @returns {string} - The corresponding event code string.
372+
*/
373+
getEventCodeString(code) {
374+
// Return empty string if eventCode is zero
375+
if (code === 0) return "";
376+
377+
// Define the flag values (adjust these constants if needed)
378+
const EventCodeFlags = {
379+
AlarmSet: 0x1,
380+
AlarmClr: 0x2,
381+
AlarmAck: 0x4,
382+
AlarmReprise: 0x40
383+
};
384+
385+
// Check for specific combinations first
386+
if (code === EventCodeFlags.AlarmSet) return "AlarmSet";
387+
if (code === EventCodeFlags.AlarmClr) return "AlarmClear";
388+
if (code === EventCodeFlags.AlarmAck) return "Ack";
389+
if (code === EventCodeFlags.AlarmReprise) return "Reprise";
390+
if (code === (EventCodeFlags.AlarmReprise | EventCodeFlags.AlarmSet))
391+
return "RepriseAlarmSet";
392+
if (code === (EventCodeFlags.AlarmReprise | EventCodeFlags.AlarmClr))
393+
return "RepriseAlarmClear";
394+
if (code === (EventCodeFlags.AlarmReprise | EventCodeFlags.AlarmAck))
395+
return "RepriseAck";
396+
397+
// Otherwise, combine the flag strings based on which bits are set.
398+
let s = "";
399+
if (code & EventCodeFlags.AlarmReprise)
400+
s += (s ? "+" : "") + "Reprise";
401+
if (code & EventCodeFlags.AlarmSet)
402+
s += (s ? "+" : "") + "AlarmSet";
403+
if (code & EventCodeFlags.AlarmClr)
404+
s += (s ? "+" : "") + "AlarmClear";
405+
if (code & EventCodeFlags.AlarmAck)
406+
s += (s ? "+" : "") + "Ack";
407+
408+
return s;
409+
}
410+
411+
283412
_handleMessage(ws, message) {
284413
const data = Container.decode(new Uint8Array(message));
285414
this._parseMessage(data);
@@ -396,13 +525,12 @@ class Client {
396525
}
397526

398527
case Container.Type.eEventsResponse: {
399-
// Optionally enrich each event with a human-readable code description:
528+
// Enrich events with a human-readable code description.
400529
if (data.eventsResponse.events && data.eventsResponse.events.length > 0) {
401530
data.eventsResponse.events.forEach(evt => {
402531
evt.codeDescription = this.getEventCodeDescription(evt.code);
403532
});
404533
}
405-
406534
if (this.storedPromises[data.eventsResponse.requestId]) {
407535
const { resolve } = this.storedPromises[data.eventsResponse.requestId];
408536
delete this.storedPromises[data.eventsResponse.requestId];
@@ -524,9 +652,7 @@ class Client {
524652
}
525653
const requestId = reqId;
526654
this.lastTimeRequest = Date.now() / 1000;
527-
// Always send the time request.
528655
this._sendTimeRequest(requestId);
529-
// Create the promise and store the callbacks.
530656
const promise = new Promise((resolve, reject) => {
531657
this.storedPromises[requestId] = { resolve, reject };
532658
});

0 commit comments

Comments
 (0)