-
Notifications
You must be signed in to change notification settings - Fork 9
/
cache.js
159 lines (148 loc) · 5.9 KB
/
cache.js
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
// cache
// manages a set of key-item pairs.
// this is an intermediary between the raw device data and the shdr output.
// when a key-item value is set, the cache will perform any associated output
// calculations and send shdr output to attached tcp socket, IF value changed.
//. eg ___
// import { lightFormat } from 'date-fns'
// import { formatISO9075 } from 'date-fns'
// import dayjs from 'dayjs'
export class Cache {
constructor() {
this._map = new Map() // key-item pairs //. why not just {} ?
this._mapKeyToOutputs = {} // list of outputs assoc with each key, //. eg ?
}
// addOutputs
// each cache key can have multiple output calculations associated with it.
// this builds a map from a key to a list of outputs.
// each output goes to the same tcp socket.
// called from adapter.js for each device source.
// outputs is [{ key, category, type, representation, socket, dependsOn, value }, ...]
// eg [{ key: 'ac1-power_condition', value: (fn), dependsOn: ['ac1-power_fault', 'ac1-power_warning'] }, ...]
// so this builds a map from those dependsOn values to the output object.
// eg { 'ac1-power_fault': [{ key:'ac1-power_condition', value: (fn), ...}], ... }
// addOutputs(outputs, socket) {
addOutputs(outputs) {
console.log(`cache.addOutputs - add ${outputs.length} outputs`)
for (const output of outputs) {
// console.log(output.key, output.dependsOn)
// output.socket = socket // attach tcp socket to each output also
// add dependsOn eg ['ac1-power_fault', 'ac1-power_warning']
for (const key of output.dependsOn) {
if (this._mapKeyToOutputs[key]) {
this._mapKeyToOutputs[key].push(output)
} else {
this._mapKeyToOutputs[key] = [output]
}
}
}
}
// attach tcp socket to each output, or clear if socket=null
setSocket(outputs, socket) {
console.log(`Cache - setSocket...`)
if (socket) {
console.log(`Cache - send last known data values to agent...`)
}
for (const output of outputs || []) {
output.socket = socket
if (output.socket) {
// send last known data value to agent
const shdr = getShdr(output, output.lastValue || 'UNAVAILABLE')
console.log(`Cache - send ${shdr.slice(0, 60)}...`)
try {
output.socket.write(shdr + '\n')
} catch (error) {
console.log(error)
}
}
}
}
// set a key-value pair in the cache.
// eg set('ac1-power_warning', true)
// options is { timestamp, quiet }
// timestamp is an optional STRING that is used in the SHDR
//. explain distinction between value param and value variable below, with examples
set(key, value, options = {}) {
if (!options.quiet) {
const s = typeof value === 'string' ? `"${value.slice(0, 99)}..."` : value
console.log(`Cache - set ${key}: ${s}`)
}
// update the cache value
this._map.set(key, value)
// get list of outputs associated with this key
// eg ['ac1-power_condition']
const outputs = this._mapKeyToOutputs[key] || []
// calculate outputs and send changed shdr values to tcp
for (const output of outputs) {
// calculate value of this cache output
const value = getValue(this, output)
// if value changed, send shdr to agent via tcp socket
if (value !== output.lastValue) {
output.lastValue = value
if (output.socket) {
const shdr = getShdr(output, value, options.timestamp) // timestamp can be ''
if (!options.quiet) {
console.log(`Cache - value changed, send "${shdr.slice(0, 60)}..."`)
}
try {
output.socket.write(shdr + '\n')
} catch (error) {
console.log(error)
}
} else {
console.log(`Cache - no socket to write to`)
}
}
}
}
// get a value from cache
// eg get('pr1-avail')
get(key) {
return this._map.get(key)
}
}
// calculate value for the given cache output (can use other cache keyvalues)
function getValue(cache, output) {
//. rename .value to .getValue or .valueFn
const { value: getValue } = output
const value = getValue(cache) // do calculation
return value
}
// calculate SHDR using the given output object.
// cache is the Cache object.
// output has { key, category, type, representation, value, shdr, ... }.
// timestamp is an optional STRING that goes at the front of the shdr.
// can save some time/space by not including it.
function getShdr(output, value, timestamp = '') {
const { key, category, type, subType, representation, nativeCode } = output
let shdr = ''
// handle different shdr types and representations
// this first is the default representation, so don't require category to be defined in outputs.yaml
if (category === 'EVENT' || category === 'SAMPLE' || category === undefined) {
if (type === 'MESSAGE') {
// The next special format is the Message. There is one additional field,
// native_code, which needs to be included:
// 2014-09-29T23:59:33.460470Z|message|CHG_INSRT|Change Inserts
// From https://github.com/mtconnect/cppagent#adapter-agent-protocol-version-17 -
shdr = `${timestamp}|${key}|${nativeCode}|${value}`
} else {
shdr = `${timestamp}|${key}|${value}`
}
} else if (category === 'CONDITION') {
//. pick these values out of the value, which should be an object
//. also, can have >1 value for a condition - how handle?
if (!value || value === 'UNAVAILABLE') {
shdr = `${timestamp}|${key}|${value}||||${value}`
} else {
const level = value // eg 'WARNING' -> element 'Warning'
const nativeCode = 'nativeCode'
const nativeSeverity = 'nativeSeverity'
const qualifier = 'qualifier'
const message = value
shdr = `${timestamp}|${key}|${level}|${nativeCode}|${nativeSeverity}|${qualifier}|${message}`
}
} else {
console.warn(`warning: unknown category '${category}'`)
}
return shdr
}