410 lines
27 KiB
HTML
410 lines
27 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>🔋 6S LiPo Charger – Blockly Simulation (DIAG v6)</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<style>
|
||
:root{--bg:#0f1115;--panel:#11151a;--txt:#e6edf3;--accent:#4caf50;--muted:#9aa4b2;--line:#21262d;--bad:#ff4d4f}
|
||
*{box-sizing:border-box}
|
||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||
header .sp{flex:1}
|
||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||
#blocklyDiv{height:60vh;width:100%;border:1px solid var(--line);border-radius:10px}
|
||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto;border:1px solid var(--line)}
|
||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto;border:1px solid var(--line)}
|
||
.hint{color:var(--muted);font-size:.9rem}
|
||
.fail{color:var(--bad);font-weight:700}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (DIAG v6)</h2>
|
||
<div class="sp"></div>
|
||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||
<button id="btnClear"class="btn-gray">🗑️ Neu</button>
|
||
<button id="btnRun">▶️ Ausführen</button>
|
||
</header>
|
||
|
||
<div id="wrap">
|
||
<div id="left">
|
||
<div id="blocklyDiv"></div>
|
||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||
</div>
|
||
<div id="right">
|
||
<div id="cfg">Diag lädt …</div>
|
||
<div class="hint">
|
||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TOOLBOX (wird erst genutzt, wenn Blockly geladen ist) -->
|
||
<xml id="toolbox" style="display:none">
|
||
<category name="Variablen" custom="VARIABLE"></category>
|
||
<category name="Logik">
|
||
<block type="controls_if"></block>
|
||
<block type="logic_compare"></block>
|
||
</category>
|
||
<category name="Schleifen">
|
||
<block type="controls_repeat_ext"></block>
|
||
<block type="controls_whileUntil"></block>
|
||
<block type="controls_forEach"></block>
|
||
</category>
|
||
<category name="Mathe">
|
||
<block type="math_number"></block>
|
||
<block type="math_arithmetic"></block>
|
||
<block type="math_on_list"></block>
|
||
</category>
|
||
<category name="Listen">
|
||
<block type="lists_create_with"></block>
|
||
<block type="lists_getIndex"></block>
|
||
<block type="lists_setIndex"></block>
|
||
<block type="lists_length"></block>
|
||
</category>
|
||
<category name="Text">
|
||
<block type="text"></block>
|
||
<block type="text_join"></block>
|
||
<block type="text_print"></block>
|
||
</category>
|
||
</xml>
|
||
|
||
<script>
|
||
// ---------- Diagnose: Fehler ins Panel ----------
|
||
const out = document.getElementById('output');
|
||
const cfgEl = document.getElementById('cfg');
|
||
function setOutput(t){ out.textContent = t }
|
||
function appendOutput(t){ out.textContent += (out.textContent.endsWith('\n')?'':'\n') + t; out.scrollTop = out.scrollHeight; }
|
||
window.appendOutput = appendOutput;
|
||
window.addEventListener('error', (e)=> appendOutput('❌ JS-Error: ' + e.message));
|
||
window.addEventListener('unhandledrejection', (e)=> appendOutput('❌ Promise-Rejection: ' + (e.reason && e.reason.message ? e.reason.message : e.reason)));
|
||
|
||
// ---------- Loader: erst lokal, dann CDN ----------
|
||
const CDN = 'https://unpkg.com';
|
||
const LOAD = [
|
||
['/crumbblocks/vendor/blockly/blockly.min.js', `${CDN}/blockly/blockly.min.js`],
|
||
['/crumbblocks/vendor/blockly/javascript.min.js', `${CDN}/blockly/javascript.min.js`],
|
||
['/crumbblocks/vendor/blockly/msg/de.js', `${CDN}/blockly/msg/de.js`],
|
||
];
|
||
function loadScriptPair([localSrc, cdnSrc]){
|
||
return new Promise((resolve, reject)=>{
|
||
const s = document.createElement('script');
|
||
s.src = localSrc;
|
||
s.onload = ()=> resolve({src:localSrc, ok:true, via:'local'});
|
||
s.onerror = ()=>{
|
||
const c = document.createElement('script');
|
||
c.src = cdnSrc;
|
||
c.onload = ()=> resolve({src:cdnSrc, ok:true, via:'cdn'});
|
||
c.onerror = ()=> reject(new Error('Laden fehlgeschlagen: ' + localSrc + ' und ' + cdnSrc));
|
||
document.head.appendChild(c);
|
||
};
|
||
document.head.appendChild(s);
|
||
});
|
||
}
|
||
|
||
// ---------- Self-Test ----------
|
||
async function selfTest(){
|
||
const diag = { js_ok:true, blockly_loaded:false, eval_ok:null, scripts:[], csp_note:null };
|
||
// Lade Reihenfolge wahren
|
||
for (const p of LOAD){
|
||
try {
|
||
const res = await loadScriptPair(p);
|
||
diag.scripts.push(res);
|
||
} catch (e) {
|
||
diag.scripts.push({src:p, ok:false, via:'none', err:String(e)});
|
||
}
|
||
}
|
||
diag.blockly_loaded = !!window.Blockly;
|
||
try { new Function('return 42')(); diag.eval_ok = true; }
|
||
catch(e){ diag.eval_ok = 'blocked: ' + e.message; diag.csp_note = 'CSP erlaubt kein unsafe-eval. Siehe Hinweise unten.'; }
|
||
|
||
const CFG = {
|
||
cells: 6, v_full:4.20, v_storage:3.80, v_hv:4.35, v_min:3.20,
|
||
drift_reject:0.30, tick_s:10, dv_charge:0.01, dv_discharge:-0.01,
|
||
capacity_mAh:2200, c_rate:1.0
|
||
};
|
||
cfgEl.textContent = 'Diag:\n' + JSON.stringify(diag, null, 2) + '\n\nCFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||
|
||
if (!diag.blockly_loaded){
|
||
appendOutput('❌ Blockly nicht geladen. Prüfe /crumbblocks/vendor/... oder Internet/CSP.');
|
||
appendOutput('Tipp: Siehe „Lokale Vendor-Dateien“ unten.');
|
||
return;
|
||
}
|
||
bootBlockly(); // erst jetzt initialisieren
|
||
}
|
||
|
||
// ---------- Blockly-Init (nach bestandenem Self-Test) ----------
|
||
function bootBlockly(){
|
||
const workspace = Blockly.inject('blocklyDiv', {
|
||
toolbox: document.getElementById('toolbox'),
|
||
theme: Blockly.Themes.Dark,
|
||
renderer: 'zelos',
|
||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||
trashcan: true,
|
||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||
move: {scrollbars:true, drag:true, wheel:true}
|
||
});
|
||
|
||
// Variablen
|
||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||
|
||
// print ins Panel + Alert abfangen
|
||
(function hardenPrint(){
|
||
const gen = Blockly.JavaScript;
|
||
gen['text_print'] = function(block){
|
||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||
return 'appendOutput(String(' + arg0 + '));\n';
|
||
};
|
||
const origAlert = window.alert;
|
||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||
})();
|
||
|
||
// 1-basige Listen sauber generieren
|
||
(function patchListsGenerators(){
|
||
const gen = Blockly.JavaScript;
|
||
gen['lists_getIndex'] = function(block){
|
||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||
const code = `${list}[(${at}) - 1]`;
|
||
return [code, gen.ORDER_MEMBER];
|
||
};
|
||
gen['lists_setIndex'] = function(block){
|
||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||
};
|
||
})();
|
||
|
||
// Helpers
|
||
function textToDom(xmlText){
|
||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||
}
|
||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||
|
||
// Demos (kompakt)
|
||
const DOC_XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||
</block></value>
|
||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||
</block></value><next>
|
||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||
</next></next>
|
||
</block>
|
||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||
</xml>`.trim();
|
||
|
||
const T1_XML = `<!-- Storage -->
|
||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||
</block></value></block>
|
||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||
</block></value>
|
||
<statement name="DO">
|
||
<block type="variables_set"><field name="VAR">time_s</field>
|
||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||
</block></value>
|
||
<statement name="DO">
|
||
<block type="variables_set"><field name="VAR">new_v</field>
|
||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||
</block></value>
|
||
</block>
|
||
<next><block type="controls_if">
|
||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||
</block></next>
|
||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||
</block></next>
|
||
</statement>
|
||
</block></next>
|
||
</block>
|
||
</statement>
|
||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||
</block>
|
||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||
</block></value></block>
|
||
</xml>`.trim();
|
||
|
||
const T2_XML = T1_XML
|
||
.replace('storage','charge')
|
||
.replace('3.8','4.2')
|
||
.replace('"-0.01"','"0.01"')
|
||
.replace('storage_reached','full_reached')
|
||
.replace('"GT"','"LT"');
|
||
|
||
const T3_XML = `
|
||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||
<variables><variable>mode</variable><variable>stop_reason</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">safety_check</field></block></value></block>
|
||
<block type="variables_set" x="20" y="60"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||
<value name="ADD0"><block type="math_number"><field name="NUM">3.60</field></block></value>
|
||
<value name="ADD1"><block type="math_number"><field name="NUM">3.61</field></block></value>
|
||
<value name="ADD2"><block type="math_number"><field name="NUM">3.58</field></block></value>
|
||
<value name="ADD3"><block type="math_number"><field name="NUM">3.59</field></block></value>
|
||
<value name="ADD4"><block type="math_number"><field name="NUM">3.05</field></block></value>
|
||
<value name="ADD5"><block type="math_number"><field name="NUM">3.62</field></block></value>
|
||
</block></value>
|
||
<block type="variables_set" x="20" y="200"><field name="VAR">max_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||
<next><block type="variables_set"><field name="VAR">min_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value></block></next>
|
||
</block>
|
||
<block type="controls_if" x="20" y="270"><value name="IF0"><block type="logic_compare"><field name="OP">GTE</field>
|
||
<value name="A"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||
<value name="B"><block type="math_number"><field name="NUM">0.3</field></block></value></block></value>
|
||
<statement name="DO0"><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">reject_imbalance</field></block></value></block></statement>
|
||
</block>
|
||
<block type="text_print" x="20" y="350"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"safety_check", "delta": </field></block></value>
|
||
<value name="ADD1"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||
<value name="ADD2"><block type="text"><field name="TEXT">, "stop_reason":"</field></block></value>
|
||
<value name="ADD3"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||
<value name="ADD4"><block type="text"><field name="TEXT">", "advice":"Pack nicht verwenden." }</field></block></value>
|
||
</block></value></block>
|
||
</xml>`.trim();
|
||
|
||
const T4_XML = T2_XML
|
||
.replace('charge','hv_charge')
|
||
.replace('4.2','4.35')
|
||
.replace('full_reached','hv_reached')
|
||
.replace(/3\.7[0-9]?/g,'3.96'); // Start näher an HV
|
||
|
||
// Buttons
|
||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z. ▶️ für Report.'); };
|
||
document.getElementById('btnRnd').onclick = ()=>{
|
||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||
const XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||
</xml>`;
|
||
loadXml(XML);
|
||
setOutput('🎲 Zufallspack geladen.');
|
||
};
|
||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||
|
||
// RUN: Watchdog + Alerts→Panel + Writeback&Clamp-Fix
|
||
document.getElementById('btnRun').onclick = () => {
|
||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||
const guardHeader = `
|
||
let __wf = 0, __wf_cap = 5000;
|
||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||
`;
|
||
const clampHeader = `
|
||
function __clamp(v, t, mode){
|
||
if(mode==='charge' || mode==='hv_charge') return Math.min(v, t);
|
||
if(mode==='storage') return Math.max(v, t);
|
||
return v;
|
||
}
|
||
`;
|
||
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||
code = code.replace(
|
||
/new_v\s*=\s*cells\s*\[\s*\(\s*i\s*(?:-\s*1)?\s*\)\s*\]\s*\+\s*dv\s*;/g,
|
||
"new_v = __clamp(cells[(i - 1)] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[(i - 1)] = new_v;"
|
||
);
|
||
code = code.replace(
|
||
/new_v\s*=\s*cells\s*\[\s*i\s*\]\s*\+\s*dv\s*;/g,
|
||
"new_v = __clamp(cells[i] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[i] = new_v;"
|
||
);
|
||
|
||
setOutput('▶️ Ausführung…\n\n' + code);
|
||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||
};
|
||
}
|
||
|
||
// Start
|
||
selfTest();
|
||
</script>
|
||
|
||
<!-- Sicherheit -->
|
||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||
Packs mit starker Zellabweichung (≥0,30 V) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||
</div>
|
||
|
||
<!-- Hinweise / Fallback -->
|
||
<div style="padding:.6rem .8rem;color:var(--muted)">
|
||
<b>Lokale Vendor-Dateien (empfohlen, falls CDN/CSP blockt):</b><br/>
|
||
<code>mkdir -p /var/www/crumbblocks/vendor/blockly</code><br/>
|
||
<code>curl -L -o /var/www/crumbblocks/vendor/blockly/blockly.min.js https://unpkg.com/blockly/blockly.min.js</code><br/>
|
||
<code>curl -L -o /var/www/crumbblocks/vendor/blockly/javascript.min.js https://unpkg.com/blockly/javascript.min.js</code><br/>
|
||
<code>curl -L -o /var/www/crumbblocks/vendor/blockly/msg/de.js https://unpkg.com/blockly/msg/de.js</code><br/><br/>
|
||
|
||
<b>CSP (falls Eval/Inline blockiert → „nix passiert“):</b><br/>
|
||
Erweitere Header minimal für die Simulation:
|
||
<pre>Content-Security-Policy: default-src 'self';
|
||
script-src 'self' https://unpkg.com 'unsafe-inline' 'unsafe-eval';
|
||
style-src 'self' 'unsafe-inline';
|
||
connect-src 'self';
|
||
img-src 'self' data:;
|
||
font-src 'self' data:;</pre>
|
||
(Nur für diese Lernseite. Produktion später strenger machen.)
|
||
</div>
|
||
</body>
|
||
</html>
|