Files
crumbmissions/crumbblocks/lipo_6s_charger_sim_safe_v5.html
2025-12-23 20:52:00 +01:00

323 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>🔋 6S LiPo Charger Blockly Simulation (SAFE v4)</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Blockly -->
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
<script src="https://unpkg.com/blockly/msg/de.js"></script>
<style>
:root{--bg:#1e1e1e;--panel:#121212;--txt:#e8e8e8;--accent:#4caf50;--muted:#a0a0a0;--line:#2a2a2a}
*{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%}
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto}
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto}
.hint{color:var(--muted);font-size:.9rem}
</style>
</head>
<body>
<header>
<h2>🧁 Crumbforest • 6S LiPo Charger Simulation (SAFE v4)</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"></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 -->
<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>
// === CFG (Gedankenstütze) ===
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
};
const cfgEl = document.getElementById('cfg');
cfgEl.textContent = 'CFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
// === Blockly Setup ===
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, die in den Demos genutzt werden
['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(_){} });
// Output/Print
const $ = s => document.querySelector(s);
function setOutput(t){ $('#output').textContent = t }
function appendOutput(t){ const el=$('#output'); el.textContent += (el.textContent.endsWith('\n')?'':'\n') + t; el.scrollTop = el.scrollHeight; }
window.appendOutput = appendOutput;
// text_print → Panel & Alerts 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); } };
})();
// *** LISTEN-PATCH (1-basiert → korrektes JS-Indexing & Schreiben) ***
(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`;
};
})();
// XML utils
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 gehalten) ---
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 = `
<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 = /* wie T1, nur 4.2V + dv=+0.01 + MIN/Clamp im If */;
const T3_XML = /* Drift-Check */;
const T4_XML = /* HV 4.35V */;
// 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 (nur Demo). ▶️ 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;
}
`;
// 1) Watchdog in jede while(...)
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
// 2) Alerts → Panel
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
// 3) Erzwinge Schreibvorgang + Clamp nach "new_v = cells[...] + dv;"
// - Variante mit 1-basiertem Index: cells[(i - 1)]
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;"
);
// - Variante mit 0-basiertem Index: cells[i]
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)); }
};
</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>
</body>
</html>