Skip to content

Commit 850cf53

Browse files
committed
Close gap to original pvws project
1 parent 9bdf43c commit 850cf53

File tree

3 files changed

+181
-24
lines changed

3 files changed

+181
-24
lines changed

src/main/java/org/phoebus/pvws/PvwsWebListener.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ public class PvwsWebListener implements ServletContextListener {
3838

3939
@Override
4040
public void contextDestroyed(ServletContextEvent event) {
41-
System.out.println("sdjhgfdfugh");
4241
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
4342
sockets = (List<WebSocket>) context.getBean("sockets");
4443
if (sockets == null) {

src/main/java/org/phoebus/pvws/ws/Vtype2Json.java

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import java.nio.ByteBuffer;
1818
import java.nio.ByteOrder;
1919
import java.nio.DoubleBuffer;
20+
import java.nio.FloatBuffer;
2021
import java.nio.IntBuffer;
22+
import java.nio.ShortBuffer;
2123
import java.nio.charset.Charset;
2224
import java.nio.charset.StandardCharsets;
2325
import java.text.DecimalFormat;
@@ -58,15 +60,15 @@ else if (value instanceof VString)
5860
else if (value instanceof VEnum)
5961
handleEnum(g, (VEnum) value, last_value);
6062
else if (value instanceof VByteArray)
61-
handleLongString(g, (VByteArray) value);
63+
handleBytes(g, (VNumberArray) value, last_value);
6264

6365
// Serialize double and float arrays as b64dbl
6466
else if (value instanceof VDoubleArray)
6567
handleDoubles(g, (VNumberArray) value, last_value);
6668
else if (value instanceof VFloatArray)
67-
handleDoubles(g, (VNumberArray) value, last_value);
69+
handleFloats(g, (VNumberArray) value, last_value);
6870

69-
// Serialize remaining number arrays (int, short) as b64int
71+
// Serialize remaining number arrays (int) as b64int
7072
else if (value instanceof VNumberArray)
7173
handleInts(g, (VNumberArray) value, last_value);
7274

@@ -239,7 +241,7 @@ private static void handleInts(final JsonGenerator g, final VNumberArray value,
239241
{
240242
// Add severity if it changed
241243
if ((last_value instanceof VNumber) &&
242-
((VNumber) last_value).getAlarm().getSeverity() != severity)
244+
((VNumber) last_value).getAlarm().getSeverity() != severity)
243245
g.writeStringField("severity", severity.name());
244246
}
245247

@@ -256,6 +258,7 @@ private static void handleInts(final JsonGenerator g, final VNumberArray value,
256258
}
257259

258260

261+
259262
private static void handleEnum(final JsonGenerator g, final VEnum value, final VType last_value) throws Exception
260263
{
261264
final AlarmSeverity severity = value.getAlarm().getSeverity();
@@ -275,11 +278,98 @@ private static void handleEnum(final JsonGenerator g, final VEnum value, final V
275278
{
276279
// Add severity if it changed
277280
if ((last_value instanceof VNumber) &&
278-
((VNumber) last_value).getAlarm().getSeverity() != severity)
281+
((VNumber) last_value).getAlarm().getSeverity() != severity)
279282
g.writeStringField("severity", severity.name());
280283
}
281284

282285
g.writeNumberField("value", value.getIndex());
283286
g.writeStringField("text", value.getValue());
284287
}
288+
289+
private static void handleFloats(final JsonGenerator g, final VNumberArray value, final VType last_value) throws Exception
290+
{
291+
final AlarmSeverity severity = value.getAlarm().getSeverity();
292+
if (last_value == null)
293+
{
294+
// Initially, add complete metadata
295+
g.writeStringField("vtype", VType.typeOf(value).getSimpleName());
296+
handleDisplay(g, value.getDisplay());
297+
// Initial severity
298+
g.writeStringField("severity", severity.name());
299+
}
300+
else
301+
{
302+
// Add severity if it changed
303+
if ((last_value instanceof VNumber) &&
304+
((VNumber) last_value).getAlarm().getSeverity() != severity)
305+
g.writeStringField("severity", severity.name());
306+
}
307+
308+
final ListNumber data = value.getData();
309+
final int N = data.size();
310+
final ByteBuffer buf = ByteBuffer.allocate(N * Float.BYTES);
311+
buf.order(ByteOrder.LITTLE_ENDIAN);
312+
final FloatBuffer fltbuf = buf.asFloatBuffer();
313+
for (int i=0; i<N; ++i)
314+
fltbuf.put(data.getFloat(i));
315+
g.writeStringField("b64flt", Base64.getEncoder().encodeToString(buf.array()));
316+
}
317+
318+
private static void handleShorts(final JsonGenerator g, final VNumberArray value, final VType last_value) throws Exception
319+
{
320+
final AlarmSeverity severity = value.getAlarm().getSeverity();
321+
if (last_value == null)
322+
{
323+
// Initially, add complete metadata
324+
g.writeStringField("vtype", VType.typeOf(value).getSimpleName());
325+
handleDisplay(g, value.getDisplay());
326+
// Initial severity
327+
g.writeStringField("severity", severity.name());
328+
}
329+
else
330+
{
331+
// Add severity if it changed
332+
if ((last_value instanceof VNumber) &&
333+
((VNumber) last_value).getAlarm().getSeverity() != severity)
334+
g.writeStringField("severity", severity.name());
335+
}
336+
337+
final ListNumber data = value.getData();
338+
final int N = data.size();
339+
final ByteBuffer buf = ByteBuffer.allocate(N * Short.BYTES);
340+
buf.order(ByteOrder.LITTLE_ENDIAN);
341+
final ShortBuffer srtbuf = buf.asShortBuffer();
342+
for (int i=0; i<N; ++i)
343+
srtbuf.put(data.getShort(i));
344+
g.writeStringField("b64srt", Base64.getEncoder().encodeToString(buf.array()));
345+
}
346+
347+
private static void handleBytes(final JsonGenerator g, final VNumberArray value, final VType last_value) throws Exception
348+
{
349+
final AlarmSeverity severity = value.getAlarm().getSeverity();
350+
if (last_value == null)
351+
{
352+
// Initially, add complete metadata
353+
g.writeStringField("vtype", VType.typeOf(value).getSimpleName());
354+
handleDisplay(g, value.getDisplay());
355+
// Initial severity
356+
g.writeStringField("severity", severity.name());
357+
}
358+
else
359+
{
360+
// Add severity if it changed
361+
if ((last_value instanceof VNumber) &&
362+
((VNumber) last_value).getAlarm().getSeverity() != severity)
363+
g.writeStringField("severity", severity.name());
364+
}
365+
366+
// Convert into Base64 int64 array
367+
final ListNumber data = value.getData();
368+
final int N = data.size();
369+
final ByteBuffer buf = ByteBuffer.allocate(N * Byte.BYTES);
370+
buf.order(ByteOrder.LITTLE_ENDIAN);
371+
for (int i=0; i<N; ++i)
372+
buf.put(data.getByte(i));
373+
g.writeStringField("b64byt", Base64.getEncoder().encodeToString(buf.array()));
374+
}
285375
}

src/main/resources/static/js/pvws.js

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,21 @@ class PVWS
1717
this.url = url;
1818
this.connect_handler = connect_handler;
1919
this.message_handler = message_handler;
20-
20+
21+
// In local tests, the web socket tends to stay open indefinitely,
22+
// but in production setups with proxies etc. they often time out
23+
// after about a minute of inactivity.
24+
// The server side could periodically 'ping' from the SessionManager,
25+
// or the client could send periodic 'echo' messages.
26+
// We combine both approaches by having the client send ping requests
27+
// when the connection is idle. The server will then issue the ping,
28+
// and the client should return a pong (but there's no way to see that ping/pong in javascript).
29+
30+
// Is the connection idle? Any received message marks as non-idle.
31+
this.idle = true;
32+
// While connected, call checkIdleTimeout() via this timer
33+
this.idle_timer = null;
34+
2135
// Map of PVs to last known value,
2236
// merging metadata and value updates.
2337
this.values = {}
@@ -29,21 +43,48 @@ class PVWS
2943
this.connect_handler(false);
3044
console.log("Opening " + this.url);
3145
this.socket = new WebSocket(this.url);
32-
this.socket.onopen = event => this.handleConnection();
46+
this.socket.onopen = event => this.handleConnection(event);
3347
this.socket.onmessage = event => this.handleMessage(event.data);
3448
this.socket.onclose = event => this.handleClose(event);
3549
this.socket.onerror = event => this.handleError(event);
3650
}
37-
38-
handleConnection()
51+
52+
handleConnection(event)
3953
{
4054
console.log("Connected to " + this.url);
4155
this.connect_handler(true);
56+
57+
// Start idle check
58+
if (this.idle_timer == null)
59+
this.idle_timer = setInterval(() => this.checkIdleTimeout(), this.idle_check_ms);
4260
}
43-
61+
62+
checkIdleTimeout()
63+
{
64+
if (this.idle)
65+
{
66+
// console.log("Idle connection " + this.url);
67+
this.ping();
68+
}
69+
else
70+
{
71+
// console.log("Active connection " + this.url);
72+
// Reset to detect new messages
73+
this.idle = true;
74+
}
75+
}
76+
77+
stopIdleCheck()
78+
{
79+
if (this.idle_timer != null)
80+
clearInterval(this.idle_timer);
81+
this.idle_timer = null;
82+
}
83+
4484
handleMessage(message)
4585
{
4686
// console.log("Received Message: " + message);
87+
this.idle = false;
4788
let jm = JSON.parse(message);
4889
if (jm.type === "update")
4990
{
@@ -62,6 +103,22 @@ class PVWS
62103
// console.log(JSON.stringify(jm.value));
63104
delete jm.b64dbl;
64105
}
106+
else if (jm.b64flt !== undefined)
107+
{
108+
let bytes = toByteArray(jm.b64flt);
109+
jm.value = new Float32Array(bytes.buffer);
110+
// Convert to plain array
111+
jm.value = Array.prototype.slice.call(jm.value);
112+
delete jm.b64flt;
113+
}
114+
else if (jm.b64srt !== undefined)
115+
{
116+
let bytes = toByteArray(jm.b64srt);
117+
jm.value = new Int16Array(bytes.buffer);
118+
// Convert to plain array
119+
jm.value = Array.prototype.slice.call(jm.value);
120+
delete jm.b64srt;
121+
}
65122
else if (jm.b64int !== undefined)
66123
{
67124
let bytes = toByteArray(jm.b64int);
@@ -70,14 +127,22 @@ class PVWS
70127
jm.value = Array.prototype.slice.call(jm.value);
71128
delete jm.b64int;
72129
}
73-
130+
else if (jm.b64byt !== undefined)
131+
{
132+
let bytes = toByteArray(jm.b64byt);
133+
jm.value = new Uint8Array(bytes.buffer);
134+
// Convert to plain array, if necessary
135+
jm.value = Array.prototype.slice.call(jm.value);
136+
delete jm.b64byt;
137+
}
138+
74139
// Merge received data with last known value
75140
let value = this.values[jm.pv];
76141
// No previous value:
77142
// Default to read-only, no data
78143
if (value === undefined)
79144
value = { pv: jm.pv, readonly: true };
80-
145+
81146
// Update cached value with received changes
82147
Object.assign(value, jm);
83148
this.values[jm.pv] = value;
@@ -94,9 +159,10 @@ class PVWS
94159
console.error(event);
95160
this.close();
96161
}
97-
162+
98163
handleClose(event)
99164
{
165+
this.stopIdleCheck();
100166
this.connect_handler(false);
101167
let message = "Web socket closed (" + event.code ;
102168
if (event.reason)
@@ -114,17 +180,17 @@ class PVWS
114180
*/
115181
ping()
116182
{
183+
console.log("Sending ping to " + this.url);
117184
this.socket.send(JSON.stringify({ type: "ping" }))
118185
}
119-
186+
120187
/** Subscribe to one or more PVs
121188
* @param pvs PV name or array of PV names
122189
*/
123190
subscribe(pvs)
124191
{
125192
if (pvs.constructor !== Array)
126193
pvs = [ pvs ];
127-
// TODO Remember all PVs so we can re-subscribe after close/re-open
128194
this.socket.send(JSON.stringify({ type: "subscribe", pvs: pvs }));
129195
}
130196

@@ -135,21 +201,20 @@ class PVWS
135201
{
136202
if (pvs.constructor !== Array)
137203
pvs = [ pvs ];
138-
// TODO Forget PVs so we don't re-subscribe after close/re-open
139204
this.socket.send(JSON.stringify({ type: "clear", pvs: pvs }));
140-
205+
141206
// Remove entry for cleared PVs from this.values
142207
let pv;
143208
for (pv of pvs)
144209
delete this.values[pv];
145210
}
146-
211+
147212
/** Request list of PVs */
148213
list()
149214
{
150215
this.socket.send(JSON.stringify({ type: "list" }));
151216
}
152-
217+
153218
/** Write to PV
154219
* @param pvs PV name
155220
* @param value number or string
@@ -158,17 +223,20 @@ class PVWS
158223
{
159224
this.socket.send(JSON.stringify({ type: "write", pv: pv, value: value }));
160225
}
161-
226+
162227
/** Close the web socket.
163-
*
228+
*
164229
* <p>Socket will automatically re-open,
165230
* similar to handling an error.
166231
*/
167232
close()
168233
{
234+
this.stopIdleCheck();
169235
this.socket.close();
170236
}
171237
}
172238

173-
// TODO Larger timeout for production setup
174-
PVWS.prototype.reconnect_ms = 5000;
239+
// Attempt re-connect after 10 seconds
240+
PVWS.prototype.reconnect_ms = 10000;
241+
// Perform idle check every 30 secs
242+
PVWS.prototype.idle_check_ms = 30000;

0 commit comments

Comments
 (0)