-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathschakelbox_logic.js
More file actions
192 lines (170 loc) · 6.15 KB
/
Copy pathschakelbox_logic.js
File metadata and controls
192 lines (170 loc) · 6.15 KB
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// Schakelbox V8 - Kernlogica (geëxtraheerd voor testing)
// Identiek aan de logica in schakelbox_simulator_v8.html en schakelbox_v8.ino
const RAIL_50C = 0, RAIL_50D = 1;
const TRAFO_PRIM_0 = 2, TRAFO_PRIM_1 = 3, TRAFO_PRIM_2 = 4;
const TRAFO_SEC_0 = 5, TRAFO_SEC_1 = 6, TRAFO_SEC_2 = 7;
const RAIL_10_1 = 8, RAIL_10_2 = 9;
const NUM_NODES = 10;
const NODE_NAMEN = [
'50kV-C', '50kV-D',
'Prim-T1', 'Prim-T2', 'Prim-T3',
'Sec-T1', 'Sec-T2', 'Sec-T3',
'10kV-1', '10kV-2'
];
const VELD_NAMEN = ['Veld 51', 'Veld 53', 'Veld 54'];
const RS_LABELS = ['RS C', 'RS D', 'RS 1', 'RS 2'];
function createState() {
return {
rs: [[false,false,false,false],[false,false,false,false],[false,false,false,false]],
vs: [[false,false],[false,false],[false,false]],
koppelPrim: false,
koppelSec: false,
aard: [[false,false,false],[false,false,false],[false,false,false]],
fouten: [], // verzamelt fouten per cyclus
};
}
function createStartScenario() {
return {
rs: [[false,true,false,true],[true,false,true,false],[false,true,false,true]],
vs: [[true,true],[true,true],[false,false]],
koppelPrim: true,
koppelSec: false,
aard: [[false,false,false],[false,false,false],[false,false,false]],
fouten: [],
};
}
// Spanning propagatie (bidirectioneel)
function propageer(s, a, b) {
if (s[a] && !s[b]) { s[b] = true; return true; }
if (s[b] && !s[a]) { s[a] = true; return true; }
return false;
}
function berekenSpanning(state, skipVeld, skipRs) {
const s = new Array(NUM_NODES).fill(false);
s[RAIL_50D] = true;
let gewijzigd = true;
while (gewijzigd) {
gewijzigd = false;
if (state.koppelPrim)
gewijzigd |= propageer(s, RAIL_50C, RAIL_50D);
for (let v = 0; v < 3; v++) {
const prim = TRAFO_PRIM_0 + v;
const sec = TRAFO_SEC_0 + v;
if (state.rs[v][0] && !(v === skipVeld && skipRs === 0))
gewijzigd |= propageer(s, RAIL_50C, prim);
if (state.rs[v][1] && !(v === skipVeld && skipRs === 1))
gewijzigd |= propageer(s, RAIL_50D, prim);
if (state.vs[v][0] && state.vs[v][1])
gewijzigd |= propageer(s, prim, sec);
if (state.rs[v][2] && !(v === skipVeld && skipRs === 2))
gewijzigd |= propageer(s, sec, RAIL_10_1);
if (state.rs[v][3] && !(v === skipVeld && skipRs === 3))
gewijzigd |= propageer(s, sec, RAIL_10_2);
}
if (state.koppelSec)
gewijzigd |= propageer(s, RAIL_10_1, RAIL_10_2);
}
return s;
}
// Spanning op een aardpunt
function aardPuntSpanning(state, spanning, v, punt) {
if (punt === 0) return spanning[TRAFO_PRIM_0 + v];
if (punt === 2) return spanning[TRAFO_SEC_0 + v];
return (state.vs[v][0] && spanning[TRAFO_PRIM_0 + v])
|| (state.vs[v][1] && spanning[TRAFO_SEC_0 + v]);
}
// RS nodes aan weerszijden
function rsNodes(v, r) {
return [
[RAIL_50C, TRAFO_PRIM_0 + v],
[RAIL_50D, TRAFO_PRIM_0 + v],
[TRAFO_SEC_0 + v, RAIL_10_1],
[TRAFO_SEC_0 + v, RAIL_10_2]
][r];
}
// Schakelfout check (soort 1): RS wisselt, check potentiaalverschil + VS
function checkSchakelfout(state, veld, rsIdx) {
const [a, b] = rsNodes(veld, rsIdx);
const s = berekenSpanning(state, veld, rsIdx);
if (s[a] === s[b]) return null;
const vsDicht = (rsIdx <= 1) ? state.vs[veld][0] : state.vs[veld][1];
if (vsDicht) {
return { soort: 1, veld: VELD_NAMEN[veld], bericht: `${RS_LABELS[rsIdx]} geschakeld onder belasting!` };
}
return null;
}
// Uitval check (soort 2): rail verliest spanning
function checkUitval(prevSpanning, spanning) {
const fouten = [];
if (prevSpanning[RAIL_10_1] && !spanning[RAIL_10_1])
fouten.push({ soort: 2, veld: 'SYSTEEM', bericht: '10kV Rail 1 geen spanning!' });
if (prevSpanning[RAIL_10_2] && !spanning[RAIL_10_2])
fouten.push({ soort: 2, veld: 'SYSTEEM', bericht: '10kV Rail 2 geen spanning!' });
return fouten;
}
// Railkoppeling check (soort 1 of 3)
function checkRailkoppeling(state, spanning) {
const fouten = [];
for (let v = 0; v < 3; v++) {
// 50kV: beide RS dicht zonder koppelveld = fout 1
if (state.rs[v][0] && state.rs[v][1] && !state.koppelPrim) {
fouten.push({ soort: 1, veld: VELD_NAMEN[v], bericht: 'RS C+D dicht ZONDER koppelveld prim!' });
}
// 10kV: beide RS dicht zonder koppelveld = fout 1
if (state.rs[v][2] && state.rs[v][3] && !state.koppelSec) {
fouten.push({ soort: 1, veld: VELD_NAMEN[v], bericht: 'RS 1+2 dicht ZONDER koppelveld sec!' });
}
}
return fouten;
}
// Verkeerde volgorde check (soort 4)
function checkVolgorde(prevVs, vs) {
const fouten = [];
for (let v = 0; v < 3; v++) {
if (prevVs[v][0] && !vs[v][0] && vs[v][1])
fouten.push({ soort: 4, veld: VELD_NAMEN[v], bericht: 'VS-prim UIT terwijl VS-sec IN.' });
if (!prevVs[v][1] && vs[v][1] && !vs[v][0])
fouten.push({ soort: 4, veld: VELD_NAMEN[v], bericht: 'VS-sec IN terwijl VS-prim UIT.' });
}
return fouten;
}
// Aarding fout check (soort 1): spanning op geaard punt
function checkAardingFout(state, spanning) {
const fouten = [];
for (let v = 0; v < 3; v++) {
for (let p = 0; p < 3; p++) {
if (state.aard[v][p] && aardPuntSpanning(state, spanning, v, p)) {
fouten.push({ soort: 1, veld: VELD_NAMEN[v], bericht: `Spanning op geaard punt ${p + 1}!` });
}
}
}
return fouten;
}
// Debounce: filtert mains-hum / hand-pickup op INPUT_PULLUP lijnen.
// Een input moet `debounceMs` stabiel zijn voordat de waarde geaccepteerd
// wordt. Identiek aan de C++ implementatie in schakelbox_v8.ino.
function createDebouncer(initialValue, debounceMs = 25) {
return {
stable: initialValue,
candidate: initialValue,
lastChange: 0,
debounceMs,
};
}
function debounceSample(d, raw, now) {
if (raw !== d.candidate) {
d.candidate = raw;
d.lastChange = now;
} else if (raw !== d.stable && (now - d.lastChange) >= d.debounceMs) {
d.stable = raw;
}
return d.stable;
}
module.exports = {
RAIL_50C, RAIL_50D, TRAFO_PRIM_0, TRAFO_SEC_0, RAIL_10_1, RAIL_10_2, NUM_NODES,
NODE_NAMEN, VELD_NAMEN, RS_LABELS,
createState, createStartScenario,
berekenSpanning, aardPuntSpanning, rsNodes,
checkSchakelfout, checkUitval, checkRailkoppeling, checkVolgorde, checkAardingFout,
createDebouncer, debounceSample,
};