totalsync-webinterface 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- totalsync_webinterface/__init__.py +8 -0
- totalsync_webinterface/camera.py +163 -0
- totalsync_webinterface/curses_interface.py +142 -0
- totalsync_webinterface/fake_picamera.py +81 -0
- totalsync_webinterface/packet.py +194 -0
- totalsync_webinterface/pin_sheet.py +101 -0
- totalsync_webinterface/serial_dummy.py +92 -0
- totalsync_webinterface/serial_dump.py +66 -0
- totalsync_webinterface/teensy_commander.py +490 -0
- totalsync_webinterface/web/CanvasCamera.js +33 -0
- totalsync_webinterface/web/CanvasPlot.js +71 -0
- totalsync_webinterface/web/favicon.ico +0 -0
- totalsync_webinterface/web/index.html +31 -0
- totalsync_webinterface/web/interface.js +342 -0
- totalsync_webinterface/web/reconnecting-websocket/LICENSE.txt +21 -0
- totalsync_webinterface/web/reconnecting-websocket/README.md +149 -0
- totalsync_webinterface/web/reconnecting-websocket/package.json +19 -0
- totalsync_webinterface/web/reconnecting-websocket/reconnecting-websocket.js +365 -0
- totalsync_webinterface/web/style.css +165 -0
- totalsync_webinterface/web/webgl-plot/LICENSE +21 -0
- totalsync_webinterface/web/webgl-plot/README.md +184 -0
- totalsync_webinterface/web/webgl-plot/dist/webglplot.esm.js +619 -0
- totalsync_webinterface/web/webgl-plot/package.json +42 -0
- totalsync_webinterface/web_interface.py +268 -0
- totalsync_webinterface/websocket_server/LICENSE +21 -0
- totalsync_webinterface/websocket_server/__init__.py +1 -0
- totalsync_webinterface/websocket_server/websocket_server.py +377 -0
- totalsync_webinterface-0.1.1.dist-info/METADATA +41 -0
- totalsync_webinterface-0.1.1.dist-info/RECORD +32 -0
- totalsync_webinterface-0.1.1.dist-info/WHEEL +4 -0
- totalsync_webinterface-0.1.1.dist-info/entry_points.txt +2 -0
- totalsync_webinterface-0.1.1.dist-info/licenses/LICENSE +676 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {WebglPlot, ColorRGBA, WebglLine} from "./webgl-plot/dist/webglplot.esm.js";
|
|
2
|
+
|
|
3
|
+
// TODO: Input buffer + downsampling for extended periods
|
|
4
|
+
// TODO: Interactivity (pause, zoom, select, time on axis)
|
|
5
|
+
|
|
6
|
+
export class Plot {
|
|
7
|
+
constructor(canvasID, numX, cfg) {
|
|
8
|
+
this.canvasID = canvasID;
|
|
9
|
+
this.canvas = document.getElementById(`canvas_${canvasID}`);
|
|
10
|
+
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
11
|
+
this.canvas.width = this.canvas.clientWidth * devicePixelRatio;
|
|
12
|
+
this.canvas.height = this.canvas.clientHeight * devicePixelRatio;
|
|
13
|
+
console.log(`Plot on canvas ${canvasID}: ${this.canvas.width} x ${this.canvas.height}`);
|
|
14
|
+
|
|
15
|
+
this.numX = numX;
|
|
16
|
+
this.cfg = cfg[this.canvasID];
|
|
17
|
+
|
|
18
|
+
this.numLines = this.cfg.numLines;
|
|
19
|
+
|
|
20
|
+
this.plot = new WebglPlot(this.canvas);
|
|
21
|
+
this.plot.removeAllLines();
|
|
22
|
+
this.lines = [];
|
|
23
|
+
|
|
24
|
+
let nLine = 0;
|
|
25
|
+
for (const [partitionID, partition] of Object.entries(this.cfg.partitions)) {
|
|
26
|
+
for (let i = 0; i < partition.numLines; i++) {
|
|
27
|
+
const rgb = getColor(document.getElementById(`${partitionID}_${i}`).style.backgroundColor)
|
|
28
|
+
const color = new ColorRGBA(rgb[0], rgb[1], rgb[2], 1);
|
|
29
|
+
|
|
30
|
+
const line = new WebglLine(color, this.numX);
|
|
31
|
+
line.lineSpaceX(-1, 2 / this.numX); // fill x with numX -1...1 array
|
|
32
|
+
line.offsetY = 2*(this.numLines-nLine-1)/(this.numLines)-1;
|
|
33
|
+
line.scaleY = partition.scaleFactors ? partition.scaleFactors[i] : 1;
|
|
34
|
+
this.plot.addLine(line);
|
|
35
|
+
this.lines.push(line);
|
|
36
|
+
nLine++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// fill with zeros at start
|
|
41
|
+
let data = new Array(numX).fill(0);
|
|
42
|
+
this.lines.forEach((line) => {
|
|
43
|
+
line.shiftAdd(data);
|
|
44
|
+
});
|
|
45
|
+
this.plot.gScaleX = 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
update_data(packets) {
|
|
49
|
+
if (packets == null) return;
|
|
50
|
+
let nLine = 0;
|
|
51
|
+
for (const [partitionID, partition] of Object.entries(this.cfg.partitions)) {
|
|
52
|
+
for (let i=0; i < partition.numLines; i++) {
|
|
53
|
+
const data = [];
|
|
54
|
+
packets.forEach((packet) => {
|
|
55
|
+
data.push(packet[partition.dataID][i]);
|
|
56
|
+
});
|
|
57
|
+
this.lines[nLine].shiftAdd(data);
|
|
58
|
+
nLine++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getColor(rgb) {
|
|
65
|
+
let sep = rgb.indexOf(",") > -1 ? "," : " ";
|
|
66
|
+
rgb = rgb.substring(4).split(")")[0].split(sep);
|
|
67
|
+
let r = parseInt(rgb[0]),
|
|
68
|
+
g = parseInt(rgb[1]),
|
|
69
|
+
b = parseInt(rgb[2]);
|
|
70
|
+
return [r / 255, g / 255, b / 255];
|
|
71
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<meta charset="UTF-8">
|
|
4
|
+
|
|
5
|
+
<html lang="en-us">
|
|
6
|
+
<script type="text/javascript" src="reconnecting-websocket/reconnecting-websocket.js"></script>
|
|
7
|
+
<head>
|
|
8
|
+
<link rel="stylesheet" href="style.css">
|
|
9
|
+
<title>Data logger interface</title>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div class="container-grid">
|
|
13
|
+
<div class="chart" id="chart_analog">
|
|
14
|
+
<canvas id="canvas_analog"></canvas>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="label-grid" id="grid_analog" style="background-color:#ccc"></div>
|
|
17
|
+
|
|
18
|
+
<div class="videoStreamContainer">
|
|
19
|
+
<img id="videoStream" src="" alt="No Stream" style="width: 100%; height:100%;object-fit:contain">
|
|
20
|
+
<button id="videoStreamButton">Reconnect</button>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="chart" id="chart_digital">
|
|
24
|
+
<canvas id="canvas_digital"></canvas>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="label-grid" id="grid_digital" style="background-color:#ccc"></div>
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
</body>
|
|
30
|
+
<script type="module" src="interface.js"></script>
|
|
31
|
+
</html>
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// noinspection DuplicatedCode
|
|
2
|
+
|
|
3
|
+
import {Plot} from "./CanvasPlot.js";
|
|
4
|
+
//import {Game} from "./CanvasGame.js";
|
|
5
|
+
import {CameraView} from "./CanvasCamera.js";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
let countUpdateTextDisplay = 0;
|
|
9
|
+
let updateModulator = 1; // only update every xth time the update function is called
|
|
10
|
+
let allLabels = [];
|
|
11
|
+
let numPoints = 2000;
|
|
12
|
+
const downsamplingFactor = 5;
|
|
13
|
+
const numAnalogIn = 8;
|
|
14
|
+
const numStates = 8;
|
|
15
|
+
const numDigitalIn = 16;
|
|
16
|
+
const numDigitalOut = 16;
|
|
17
|
+
|
|
18
|
+
const plotConfig = {
|
|
19
|
+
analog: {
|
|
20
|
+
numLines: numAnalogIn + numStates,
|
|
21
|
+
partitions: {
|
|
22
|
+
analog_input: {
|
|
23
|
+
dataID: 'analog',
|
|
24
|
+
numLines: numAnalogIn,
|
|
25
|
+
hasButton: false,
|
|
26
|
+
hasIndicator: false,
|
|
27
|
+
scaleFactors: new Array(numAnalogIn).fill(2 ** -13 * -0.9),
|
|
28
|
+
units: new Array(numAnalogIn).fill('V'),
|
|
29
|
+
unitFormat: (x) => `${(x * 2 ** -12 * 3.3).toFixed(2)}`,
|
|
30
|
+
colorFormat: (i, numLines) => `hsl(${180 / numLines * i}, 80%, 60%)`
|
|
31
|
+
},
|
|
32
|
+
states: {
|
|
33
|
+
dataID: 'states',
|
|
34
|
+
numLines: numStates,
|
|
35
|
+
hasButton: false,
|
|
36
|
+
hasIndicator: false,
|
|
37
|
+
scaleFactors: new Array(numAnalogIn).fill(2 ** -15 * 0.9), //[2 ** -16.5, 2 ** -10, 2 ** -12, 2 ** -12, 1 / 25, 2 ** -22, 2 ** -22, 2 ** -22],
|
|
38
|
+
units: ['turn', '', '', '', '', '', '', ''],
|
|
39
|
+
unitFormat: (x) => `${x >= 0 ? '0' : ''}${(x).toFixed(2)}`,
|
|
40
|
+
colorFormat: (i, numLines) => `hsl(${180 / numLines * i + 180}, 40%, 50%)`
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
digital: {
|
|
45
|
+
numLines: numDigitalIn + numDigitalOut,
|
|
46
|
+
partitions: {
|
|
47
|
+
digital_input: {
|
|
48
|
+
dataID: 'digitalIn',
|
|
49
|
+
numLines: numDigitalIn,
|
|
50
|
+
hasButton: false,
|
|
51
|
+
hasIndicator: '#0F0',
|
|
52
|
+
colorFormat: (i, numLines) => `hsl(${90 / numLines * i}, 80%, 60%)`,
|
|
53
|
+
scaleFactors: new Array(numDigitalIn).fill(1 / (15))
|
|
54
|
+
},
|
|
55
|
+
digital_output: {
|
|
56
|
+
dataID: 'digitalOut',
|
|
57
|
+
numLines: numDigitalOut,
|
|
58
|
+
hasButton: true,
|
|
59
|
+
hasIndicator: '#F00',
|
|
60
|
+
colorFormat: (i, numLines) => `hsl(${120 / numLines * i + 120}, 80%, 60%)`,
|
|
61
|
+
scaleFactors: new Array(numDigitalIn).fill(1 / (15))
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
let wgl_plots = [];
|
|
68
|
+
//let game_plot = new GamePlot();
|
|
69
|
+
|
|
70
|
+
let host_url;
|
|
71
|
+
let ws;
|
|
72
|
+
let message_queue = [];
|
|
73
|
+
|
|
74
|
+
//let game;
|
|
75
|
+
let camera;
|
|
76
|
+
|
|
77
|
+
// Labels from the pin sheet totalsync was started with, keyed by channel name:
|
|
78
|
+
// {"digital_input_2": "Wheel Encoder A", ...}. The server bakes these into
|
|
79
|
+
// index.html (see web_interface.send_index), so they are here before the first line
|
|
80
|
+
// is drawn; empty when totalsync was started without --pinsheet.
|
|
81
|
+
const channelLabels = window.CHANNEL_LABELS ?? {};
|
|
82
|
+
// Says in the console whether the labels reached the page, so "the names are not
|
|
83
|
+
// showing" can be told apart from "totalsync was started without --pinsheet" without
|
|
84
|
+
// having to read the DOM.
|
|
85
|
+
console.info(`Channel labels from pin sheet: ${Object.keys(channelLabels).length}`);
|
|
86
|
+
|
|
87
|
+
createUI(channelLabels);
|
|
88
|
+
init();
|
|
89
|
+
|
|
90
|
+
// Resize Handling, to prevent recalculation spam during event
|
|
91
|
+
let resizeId;
|
|
92
|
+
window.addEventListener("resize", () => {
|
|
93
|
+
window.clearTimeout(resizeId);
|
|
94
|
+
resizeId = window.setTimeout(doneResizing, 500);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
function processMessages() {
|
|
98
|
+
// are we buffering too much?
|
|
99
|
+
if (message_queue.length > 10000) {
|
|
100
|
+
message_queue = [];
|
|
101
|
+
}
|
|
102
|
+
let packets = [];
|
|
103
|
+
let dsPackets = [];
|
|
104
|
+
while (message_queue.length >= downsamplingFactor) {
|
|
105
|
+
packets = message_queue.splice(0, downsamplingFactor);
|
|
106
|
+
dsPackets.push(packets[packets.length - 1]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!dsPackets.length) return;
|
|
110
|
+
updateTextDisplay(dsPackets[dsPackets.length - 1]);
|
|
111
|
+
//game.update(dsPackets[dsPackets.length - 1]);
|
|
112
|
+
wgl_plots.forEach((plot) => {
|
|
113
|
+
plot.update_data(dsPackets);
|
|
114
|
+
plot.plot.update();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Animation/update_data ticks, maybe lower to 30 fps?
|
|
119
|
+
function newFrame() {
|
|
120
|
+
processMessages();
|
|
121
|
+
requestAnimationFrame(newFrame);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
requestAnimationFrame(newFrame);
|
|
125
|
+
|
|
126
|
+
function add_label(grid, channel_name, color, box = null, buttons = null, labels = {}) {
|
|
127
|
+
const div = document.createElement("div");
|
|
128
|
+
div.className = "label";
|
|
129
|
+
div.id = channel_name;
|
|
130
|
+
div.style.backgroundColor = color;
|
|
131
|
+
// Hovering the row still tells you which channel it is when a pin sheet label
|
|
132
|
+
// has replaced the name. It goes on the row, not on the text: .channel_name is
|
|
133
|
+
// pointer-events:none, and an element that takes no pointer events shows no
|
|
134
|
+
// tooltip either.
|
|
135
|
+
div.title = channel_name;
|
|
136
|
+
const p = document.createElement("div");
|
|
137
|
+
p.className = "channel_name";
|
|
138
|
+
p.id = channel_name;
|
|
139
|
+
// What the pin is wired to, when the pin sheet says so. Only the displayed text
|
|
140
|
+
// changes: the element ids stay as they are, because the buttons and the value
|
|
141
|
+
// fields are addressed through them, and so is the channel order in the plot.
|
|
142
|
+
p.textContent = labels[channel_name] ?? channel_name;
|
|
143
|
+
|
|
144
|
+
if (box != null) {
|
|
145
|
+
div.appendChild(box);
|
|
146
|
+
}
|
|
147
|
+
div.appendChild(p);
|
|
148
|
+
if (buttons != null) {
|
|
149
|
+
for (const btn of buttons) div.appendChild(btn);
|
|
150
|
+
}
|
|
151
|
+
grid.appendChild(div);
|
|
152
|
+
return div;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function add_spaceholder(grid, channel_name, numLines, numLines_max) {
|
|
156
|
+
const div = document.createElement("div");
|
|
157
|
+
div.className = "generalLabel";
|
|
158
|
+
div.id = channel_name;
|
|
159
|
+
div.style.backgroundColor = '#000';
|
|
160
|
+
if (channel_name.includes('analog')){
|
|
161
|
+
div.style.gridRow = "span " + numLines + " / " + numLines;
|
|
162
|
+
div.innerHTML = "Analog In";
|
|
163
|
+
}else if(channel_name.includes('states')){
|
|
164
|
+
div.style.gridRow = "span " + numLines + " / " + numLines_max;
|
|
165
|
+
div.innerHTML = "States";
|
|
166
|
+
}else if(channel_name.includes('digital_input')){
|
|
167
|
+
div.style.gridRow = "span " + numLines + " / " + numLines;
|
|
168
|
+
div.innerHTML = "Digital In";
|
|
169
|
+
}else if(channel_name.includes('digital_output')){
|
|
170
|
+
div.style.gridRow = "span " + numLines + " / " + numLines_max;
|
|
171
|
+
div.innerHTML = "Digital Out";
|
|
172
|
+
}
|
|
173
|
+
div.style.gridColumn = "2";
|
|
174
|
+
grid.appendChild(div);
|
|
175
|
+
return div;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function createUI(labels = {}) {
|
|
179
|
+
let addholder = true;
|
|
180
|
+
for (const [canvasID, cfg] of Object.entries(plotConfig)) {
|
|
181
|
+
let numLines_max = cfg.numLines;
|
|
182
|
+
const label_grid = document.querySelector(`#grid_${canvasID}`);
|
|
183
|
+
for (const [partitionID, partition] of Object.entries(cfg.partitions)) {
|
|
184
|
+
for (let i = 0; i < partition.numLines; i++) {
|
|
185
|
+
if (!i){
|
|
186
|
+
add_spaceholder(label_grid, `${partitionID}_merge`, partition.numLines, numLines_max);
|
|
187
|
+
}
|
|
188
|
+
let color = partition.colorFormat(i, partition.numLines);
|
|
189
|
+
let box;
|
|
190
|
+
let buttons;
|
|
191
|
+
|
|
192
|
+
// unit labels for analog channels and states
|
|
193
|
+
if (partition.units) {
|
|
194
|
+
box = document.createElement('div');
|
|
195
|
+
box.id = `${partitionID}_${i}`;
|
|
196
|
+
box.className = `unit_field ${partitionID}`
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// digital indicator box
|
|
200
|
+
if (partition.hasIndicator) {
|
|
201
|
+
box = document.createElement('div');
|
|
202
|
+
box.className = "indicator";
|
|
203
|
+
box.id = `${partitionID}_${i}`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// toggle buttons for digital output channels
|
|
207
|
+
if (partition.hasButton) {
|
|
208
|
+
const button_labels = {'toggle': 'TOGGLE', 'low': 'OFF', 'high': 'ON'};
|
|
209
|
+
buttons = Object.entries(button_labels).map(([btn_type, btn_label]) => {
|
|
210
|
+
const btn = document.createElement('button');
|
|
211
|
+
let text = document.createTextNode(btn_label);
|
|
212
|
+
btn.appendChild(text);
|
|
213
|
+
btn.className = "digital_button";
|
|
214
|
+
btn.id = `${partitionID}_${btn_type}_${i}`;
|
|
215
|
+
btn.onmousedown = btn_clicked;
|
|
216
|
+
return btn;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
add_label(label_grid, `${partitionID}_${i}`, color, box, buttons, labels);
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function init() {
|
|
229
|
+
host_url = window.location.hostname;
|
|
230
|
+
// One Plot per Canvas
|
|
231
|
+
for (const canvasID of Object.keys(plotConfig)) {
|
|
232
|
+
const webgl_plot = new Plot(canvasID, numPoints, plotConfig);
|
|
233
|
+
wgl_plots.push(webgl_plot);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Start Game
|
|
237
|
+
//game = new Game();
|
|
238
|
+
camera = new CameraView("169.254.1.97");
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
// start accepting messages
|
|
242
|
+
ws = start_websocket(5678);
|
|
243
|
+
ws.onmessage = ws_message_receive;
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
getLabels();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function doneResizing() {
|
|
250
|
+
//wglp.viewport(0, 0, canv.width, canv.height);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
function start_websocket(ws_port) {
|
|
255
|
+
const ws = new ReconnectingWebSocket(`ws://${host_url}:${ws_port.toString()}`);
|
|
256
|
+
ws.reconnectInterval = 500;
|
|
257
|
+
ws.maxReconnectInterval = 1000;
|
|
258
|
+
ws.timeoutInterval = 400;
|
|
259
|
+
return ws;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function ws_message_receive(event) {
|
|
263
|
+
if (typeof event.data !== 'string') return;
|
|
264
|
+
|
|
265
|
+
let packet;
|
|
266
|
+
try {
|
|
267
|
+
packet = JSON.parse(event.data);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
console.error(e);
|
|
270
|
+
console.log(event.data);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
message_queue.push(packet);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function getLabels(){
|
|
277
|
+
let j = 0;
|
|
278
|
+
for (const [canvasID, cfg] of Object.entries(plotConfig)) {
|
|
279
|
+
for (const [partitionID, partition] of Object.entries(cfg.partitions)) {
|
|
280
|
+
let labelarray = [];
|
|
281
|
+
for (let i = 0; i < partition.numLines; i++) {
|
|
282
|
+
// let x;
|
|
283
|
+
// try {
|
|
284
|
+
// x = packet[partition.dataID][i];
|
|
285
|
+
// } catch (e) {
|
|
286
|
+
// console.error(e);
|
|
287
|
+
// console.debug(partition.dataID);
|
|
288
|
+
// console.debug(partition)
|
|
289
|
+
// }
|
|
290
|
+
if (partition.units) {
|
|
291
|
+
const lbl = document.querySelector(`.unit_field#${partitionID}_${i}`);
|
|
292
|
+
labelarray.push(lbl);
|
|
293
|
+
}
|
|
294
|
+
if (partition.hasIndicator) {
|
|
295
|
+
const indicator = document.querySelector(`.indicator#${partitionID}_${i}`);
|
|
296
|
+
labelarray.push(indicator);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
allLabels[j] = {partitionId: partitionID, partition: partition, label: labelarray};
|
|
300
|
+
j += 1;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function updateTextDisplay(packet) {
|
|
306
|
+
countUpdateTextDisplay += 1;
|
|
307
|
+
if (countUpdateTextDisplay%updateModulator) return;
|
|
308
|
+
if (!packet) return;
|
|
309
|
+
|
|
310
|
+
for (let z = 0; z < allLabels.length; z++) {
|
|
311
|
+
for (let i = 0; i < allLabels[z].partition.numLines; i++) {
|
|
312
|
+
let x;
|
|
313
|
+
try {
|
|
314
|
+
x = packet[allLabels[z].partition.dataID][i];
|
|
315
|
+
} catch (e) {
|
|
316
|
+
console.error(e);
|
|
317
|
+
console.debug(allLabels[z].partition.dataID);
|
|
318
|
+
console.debug(allLabels[z].partition)
|
|
319
|
+
}
|
|
320
|
+
if (allLabels[z].partition.units) {
|
|
321
|
+
const lbl = allLabels[z].label[i];
|
|
322
|
+
let lblText = allLabels[z].partition.unitFormat ? allLabels[z].partition.unitFormat(x) : x.toFixed(2);
|
|
323
|
+
if (allLabels[z].partition.units) {
|
|
324
|
+
lblText = lblText + ' ' + allLabels[z].partition.units[i];
|
|
325
|
+
}
|
|
326
|
+
lbl.textContent = lblText;
|
|
327
|
+
}
|
|
328
|
+
if (allLabels[z].partition.hasIndicator) {
|
|
329
|
+
let bg_color = x ? allLabels[z].partition.hasIndicator : '#000'; // hasIndicator is flag and ON color
|
|
330
|
+
const indicator = allLabels[z].label[i];
|
|
331
|
+
indicator.style.backgroundColor = bg_color;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function btn_clicked(event) {
|
|
338
|
+
console.log(event.target.id);
|
|
339
|
+
if (ws) {
|
|
340
|
+
ws.send(event.target.id);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License:
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2010-2012, Joe Walnes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
ReconnectingWebSocket
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
A small JavaScript library that decorates the WebSocket API to provide a WebSocket connection that will automatically reconnect if the connection is dropped.
|
|
5
|
+
|
|
6
|
+
It is API compatible, so when you have:
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
var ws = new WebSocket('ws://....');
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
you can replace with:
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
var ws = new ReconnectingWebSocket('ws://....');
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Minified library with gzip compression is less than 600 bytes.
|
|
19
|
+
|
|
20
|
+
How reconnections occur
|
|
21
|
+
-----------------------
|
|
22
|
+
|
|
23
|
+
With the standard `WebSocket` API, the events you receive from the WebSocket instance are typically:
|
|
24
|
+
|
|
25
|
+
onopen
|
|
26
|
+
onmessage
|
|
27
|
+
onmessage
|
|
28
|
+
onmessage
|
|
29
|
+
onclose // At this point the WebSocket instance is dead.
|
|
30
|
+
|
|
31
|
+
With a `ReconnectingWebSocket`, after an `onclose` event is called it will automatically attempt to reconnect. In addition, a connection is attempted repeatedly (with a small pause) until it succeeds. So the events you receive may look something more like:
|
|
32
|
+
|
|
33
|
+
onopen
|
|
34
|
+
onmessage
|
|
35
|
+
onmessage
|
|
36
|
+
onmessage
|
|
37
|
+
onclose
|
|
38
|
+
// ReconnectingWebSocket attempts to reconnect
|
|
39
|
+
onopen
|
|
40
|
+
onmessage
|
|
41
|
+
onmessage
|
|
42
|
+
onmessage
|
|
43
|
+
onclose
|
|
44
|
+
// ReconnectingWebSocket attempts to reconnect
|
|
45
|
+
onopen
|
|
46
|
+
onmessage
|
|
47
|
+
onmessage
|
|
48
|
+
onmessage
|
|
49
|
+
onclose
|
|
50
|
+
|
|
51
|
+
This is all handled automatically for you by the library.
|
|
52
|
+
|
|
53
|
+
## Parameters
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
var socket = new ReconnectingWebSocket(url, protocols, options);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### `url`
|
|
60
|
+
- The URL you are connecting to.
|
|
61
|
+
- https://html.spec.whatwg.org/multipage/comms.html#network
|
|
62
|
+
|
|
63
|
+
#### `protocols`
|
|
64
|
+
- Optional string or array of protocols per the WebSocket spec.
|
|
65
|
+
- https://tools.ietf.org/html/rfc6455
|
|
66
|
+
|
|
67
|
+
#### `options`
|
|
68
|
+
- Options (see below)
|
|
69
|
+
|
|
70
|
+
## Options
|
|
71
|
+
|
|
72
|
+
Options can either be passed as the 3rd parameter upon instantiation or set directly on the object after instantiation:
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
var socket = new ReconnectingWebSocket(url, null, {debug: true, reconnectInterval: 3000});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
or
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
var socket = new ReconnectingWebSocket(url);
|
|
82
|
+
socket.debug = true;
|
|
83
|
+
socket.timeoutInterval = 5400;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### `debug`
|
|
87
|
+
- Whether this instance should log debug messages or not. Debug messages are printed to `console.debug()`.
|
|
88
|
+
- Accepts `true` or `false`
|
|
89
|
+
- Default value: `false`
|
|
90
|
+
|
|
91
|
+
#### `automaticOpen`
|
|
92
|
+
- Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close().
|
|
93
|
+
- Accepts `true` or `false`
|
|
94
|
+
- Default value: `true`
|
|
95
|
+
|
|
96
|
+
#### `reconnectInterval`
|
|
97
|
+
- The number of milliseconds to delay before attempting to reconnect.
|
|
98
|
+
- Accepts `integer`
|
|
99
|
+
- Default: `1000`
|
|
100
|
+
|
|
101
|
+
#### `maxReconnectInterval`
|
|
102
|
+
- The maximum number of milliseconds to delay a reconnection attempt.
|
|
103
|
+
- Accepts `integer`
|
|
104
|
+
- Default: `30000`
|
|
105
|
+
|
|
106
|
+
####`reconnectDecay`
|
|
107
|
+
- The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist.
|
|
108
|
+
- Accepts `integer` or `float`
|
|
109
|
+
- Default: `1.5`
|
|
110
|
+
|
|
111
|
+
#### `timeoutInterval`
|
|
112
|
+
- The maximum time in milliseconds to wait for a connection to succeed before closing and retrying.
|
|
113
|
+
- Accepts `integer`
|
|
114
|
+
- Default: `2000`
|
|
115
|
+
|
|
116
|
+
#### `maxReconnectAttempts`
|
|
117
|
+
- The maximum number of reconnection attempts that will be made before giving up. If null, reconnection attempts will be continue to be made forever.
|
|
118
|
+
- Accepts `integer` or `null`.
|
|
119
|
+
- Default: `null`
|
|
120
|
+
|
|
121
|
+
#### `binaryType`
|
|
122
|
+
- The binary type is required by some applications.
|
|
123
|
+
- Accepts strings `'blob'` or `'arraybuffer'`.
|
|
124
|
+
- Default: `'blob'`
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Methods
|
|
129
|
+
|
|
130
|
+
#### `ws.open()`
|
|
131
|
+
- Open the Reconnecting Websocket
|
|
132
|
+
|
|
133
|
+
#### `ws.close(code, reason)`
|
|
134
|
+
- Closes the WebSocket connection or connection attempt, if any. If the connection is already CLOSED, this method does nothing.
|
|
135
|
+
- `code` is optional the closing code (default value 1000). [https://tools.ietf.org/html/rfc6455#section-7.4.1](https://tools.ietf.org/html/rfc6455#section-7.4.1)
|
|
136
|
+
- `reason` is the optional reason that the socket is being closed. [https://tools.ietf.org/html/rfc6455#section-7.1.6](https://tools.ietf.org/html/rfc6455#section-7.1.6)
|
|
137
|
+
|
|
138
|
+
#### `ws.refresh()`
|
|
139
|
+
- Refresh the connection if still open (close and then re-open it).
|
|
140
|
+
|
|
141
|
+
#### `ws.send(data)`
|
|
142
|
+
- Transmits data to the server over the WebSocket connection.
|
|
143
|
+
- Accepts @param data a text string, ArrayBuffer or Blob
|
|
144
|
+
|
|
145
|
+
Like this? Check out [websocketd](https://github.com/joewalnes/websocketd) for the simplest way to create WebSocket backends from any programming language.
|
|
146
|
+
|
|
147
|
+
[Follow @joewalnes](https://twitter.com/joewalnes)
|
|
148
|
+
|
|
149
|
+
[](https://bitdeli.com/free "Bitdeli Badge")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ReconnectingWebSocket",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A small JavaScript library that decorates the WebSocket API to provide a WebSocket connection that will automatically reconnect if the connection is dropped.",
|
|
5
|
+
"main": "reconnecting-websocket.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git://github.com/joewalnes/reconnecting-websocket.git"
|
|
12
|
+
},
|
|
13
|
+
"author": "Joe Walnes",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/joewalnes/reconnecting-websocket/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/joewalnes/reconnecting-websocket"
|
|
19
|
+
}
|