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

576 lines
36 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)</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)</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="Reject bei 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, ~3,2/Z) • 22,2 V (Nominal) • 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, // Full
v_storage: 3.80, // Storage
v_hv: 4.35, // HV
v_min: 3.20, // "ehrlich leer"
drift_reject: 0.30, // harte Zellabweichung ablehnen
tick_s: 10,
dv_charge: 0.01, // V/Z pro Tick (vereinfacht)
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}
});
// Vordefinierte 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']
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
// Output Helpers
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;
// === Patch 1: 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); } };
})();
// XML util
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); }
// === DOC XML ===
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 → 25,2V • Storage 3,80V/Z → 22,8V • HV 4,35V/Z → 26,1V • Ende ~3,20V/Z → 19,2V.</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). Packs mit harten Unterschieden nicht nutzen.</field></block></value></block>
</xml>
`.trim();
// === T1: Storage (3.80V/Z) Entladen bis Ziel, mit Clamp ===
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>max_v</variable><variable>min_v</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">0</field></block></value>
<value name="ADD1"><block type="math_number"><field name="NUM">1</field></block></value>
<value name="ADD2"><block type="math_number"><field name="NUM">2</field></block></value>
<value name="ADD3"><block type="math_number"><field name="NUM">3</field></block></value>
<value name="ADD4"><block type="math_number"><field name="NUM">4</field></block></value>
<value name="ADD5"><block type="math_number"><field name="NUM">5</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="8"></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":"[/* siehe 'cells' */]" }</field></block></value>
</block>
</value>
</block>
</xml>
`.trim();
// === T2: Full (4.20V/Z) Laden bis Ziel, mit Clamp ===
const T2_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>max_v</variable><variable>min_v</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">charge</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">4.2</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.70</field></block></value>
<value name="ADD1"><block type="math_number"><field name="NUM">3.68</field></block></value>
<value name="ADD2"><block type="math_number"><field name="NUM">3.72</field></block></value>
<value name="ADD3"><block type="math_number"><field name="NUM">3.69</field></block></value>
<value name="ADD4"><block type="math_number"><field name="NUM">3.71</field></block></value>
<value name="ADD5"><block type="math_number"><field name="NUM">3.67</field></block></value>
</block></value>
</block>
<block type="variables_set" x="20" y="280"><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="320">
<field name="MODE">WHILE</field>
<value name="BOOL">
<block type="logic_compare"><field name="OP">LT</field>
<value name="A"><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>
<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">0</field></block></value>
<value name="ADD1"><block type="math_number"><field name="NUM">1</field></block></value>
<value name="ADD2"><block type="math_number"><field name="NUM">2</field></block></value>
<value name="ADD3"><block type="math_number"><field name="NUM">3</field></block></value>
<value name="ADD4"><block type="math_number"><field name="NUM">4</field></block></value>
<value name="ADD5"><block type="math_number"><field name="NUM">5</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">GT</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">full_reached</field></block></value></block></next>
</block>
<block type="text_print" x="20" y="790">
<value name="TEXT">
<block type="text_join">
<mutation items="8"></mutation>
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"charge", "target_cell_v":4.2, "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":"[/* siehe 'cells' */]" }</field></block></value>
</block>
</value>
</block>
</xml>
`.trim();
// === T3: Reject bei Drift >= 0.30V ===
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="10"></mutation>
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode": "safety_check", "max_v": </field></block></value>
<value name="ADD1"><block type="variables_get"><field name="VAR">max_v</field></block></value>
<value name="ADD2"><block type="text"><field name="TEXT">, "min_v": </field></block></value>
<value name="ADD3"><block type="variables_get"><field name="VAR">min_v</field></block></value>
<value name="ADD4"><block type="text"><field name="TEXT">, "delta": </field></block></value>
<value name="ADD5"><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="ADD6"><block type="text"><field name="TEXT">, "stop_reason": "</field></block></value>
<value name="ADD7"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
<value name="ADD8"><block type="text"><field name="TEXT">", "advice": "Pack nicht verwenden."</field></block></value>
<value name="ADD9"><block type="text"><field name="TEXT"> }</field></block></value>
</block>
</value>
</block>
</xml>
`.trim();
// === T4: HV 4.35V/Z Laden bis Ziel, mit Clamp ===
const T4_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">hv_charge</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">4.35</field></block></value></block>
<block type="variables_set" x="20" y="100"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
<block type="variables_set" x="20" y="140"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value></block>
<block type="variables_set" x="20" y="180"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></block>
<block type="variables_set" x="20" y="220"><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.95</field></block></value>
<value name="ADD1"><block type="math_number"><field name="NUM">3.96</field></block></value>
<value name="ADD2"><block type="math_number"><field name="NUM">3.97</field></block></value>
<value name="ADD3"><block type="math_number"><field name="NUM">3.94</field></block></value>
<value name="ADD4"><block type="math_number"><field name="NUM">3.98</field></block></value>
<value name="ADD5"><block type="math_number"><field name="NUM">3.96</field></block></value>
</block></value>
<block type="variables_set" x="20" y="360"><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="400">
<field name="MODE">WHILE</field>
<value name="BOOL">
<block type="logic_compare"><field name="OP">LT</field>
<value name="A"><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>
<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">0</field></block></value>
<value name="ADD1"><block type="math_number"><field name="NUM">1</field></block></value>
<value name="ADD2"><block type="math_number"><field name="NUM">2</field></block></value>
<value name="ADD3"><block type="math_number"><field name="NUM">3</field></block></value>
<value name="ADD4"><block type="math_number"><field name="NUM">4</field></block></value>
<value name="ADD5"><block type="math_number"><field name="NUM">5</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">GT</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">hv_reached</field></block></value></block></next>
</block>
<block type="text_print" x="20" y="820">
<value name="TEXT">
<block type="text_join">
<mutation items="6"></mutation>
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"hv_charge", "target_cell_v":4.35, "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">", "cells_v":"[/* 'cells' */]" }</field></block></value>
</block>
</value>
</block>
</xml>
`.trim();
// === Zufalls-Startwerte (nur Inspektion) ===
function randomDemoXml(){
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('');
return `
<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>
`.trim();
}
// === 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: Reject bei Drift ≥ 0,30V. ▶️ 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 = ()=>{ loadXml(randomDemoXml()); setOutput('🎲 Zufallspack geladen.'); };
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
// === Patch 2: Watchdog im Run-Handler ===
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 + ')'); } }
`;
code = guardHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
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 (sichtbar für Kids/Trainer) -->
<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 (<i>≥0,30 V</i>) nicht verwenden. Keine echten Ströme werden hier geschaltet.
</div>
</body>
</html>