Implementation
Resultaten bereken() {
final fouten = <String>[];
final waarschuwingen = <String>[];
// 1. Valideer
final I = invoer.effectieveStroom; // totale stroom
final n = (invoer.nParallel >= 1) ? invoer.nParallel : 1;
final catAders = invoer.aantalAders; // altijd het werkelijke adertal
// Singel in 3-fase circuit: gebruik izC3/izE3 (3 belaste aders) i.p.v. izC/izE (2 belaste aders)
final singelIn3Fase = invoer.aantalAders == 1 && invoer.systeem == Systeemtype.ac3Fase;
final iPerKabel = I / n; // stroom per kabel
if (n > 1 && !invoer.systeem.isAC) {
waarschuwingen.add(
'Parallel kabels is alleen van toepassing bij AC-systemen. '
'nParallel genegeerd (n=1 gebruikt).',
);
}
if (I <= 0) {
fouten.add('Stroom/vermogen moet > 0 zijn.');
return Resultaten(fouten: fouten, iGevraagd: I);
}
if (invoer.spanningV <= 0) {
fouten.add('Spanning moet > 0 V zijn.');
return Resultaten(fouten: fouten, iGevraagd: I);
}
if (invoer.lengteM <= 0) {
fouten.add('Kabellengte moet > 0 m zijn.');
return Resultaten(fouten: fouten, iGevraagd: I);
}
// 2. Correctiefactoren
final isolProp = isolatieEigenschappen[invoer.isolatie]!;
final tOmgBase = invoer.isGrondkabel ? invoer.grondtempC : invoer.omgevingstempC;
// Zonstralings-toeslag alleen voor bovengrondse leggingen (IEC 60364-5-52 / NEN 1010)
final tOmg = tOmgBase + (invoer.isGrondkabel ? 0.0 : invoer.zonlichtToeslagK);
final fT = Correctiefactoren.fTemperatuur(
tOmg,
isolProp.maxTempContinu,
tReferentie: isolProp.refTempTabel,
);
double fH = 1.0, fV = 1.0, fBundel = 1.0;
(int, int)? bundelPos;
if (invoer.bundel != null && invoer.bundel!.totaalKabels > 1) {
fH = Correctiefactoren.fHorizontaalBundeling(invoer.bundel!.nHorizontaal);
fV = Correctiefactoren.fVerticalStapeling(invoer.bundel!.nVerticaal);
fBundel = fH * fV;
bundelPos = invoer.bundel!.slechtstePositie;
}
final fGrond = invoer.isGrondkabel
? Correctiefactoren.fBodemweerstand(invoer.lambdaGrond)
: 1.0;
final fLegging = Correctiefactoren.fLegging(invoer.legging, invoer.isolatie);
// fBase: gecombineerde IEC 60364-5-52 correctiefactor (zonder cyclisch en harmonischen)
final fBase = fT * fLegging * fBundel * fGrond;
if (fBase <= 0) {
fouten.add(
'Gecombineerde correctiefactor ≤ 0 '
'(f_T=${ fT.toStringAsFixed(3)}). Omgevingstemperatuur te hoog?',
);
return Resultaten(
fouten: fouten,
iGevraagd: I,
tEffectief: tOmg,
fT: fT, fLegging: fLegging,
fBundel: fBundel, fGrond: fGrond, fTotaal: fBase,
fHorizontaal: fH, fVerticaal: fV,
);
}
// 2b. Harmonischencorrectie (NEN 1010 Bijlage 52.E.1)
// Alleen van toepassing bij ac3Fase met 4 of 5 aders en h3 > 0.
var fHarmonisch = 1.0;
var iDesign = iPerKabel; // maatgevende stroom voor kabelkeuze
var iNeutraal = 0.0;
var harmonischOpNul = false;
if (invoer.harmonischenActief) {
final harm = Correctiefactoren.fHarmonischen(iPerKabel, invoer.derdeHarmonischePct);
fHarmonisch = harm.fHarm;
iDesign = harm.iDesign;
iNeutraal = harm.iNeutraal;
harmonischOpNul = harm.iDesign == harm.iNeutraal;
if (harmonischOpNul) {
waarschuwingen.add(
'3e harmonische ${invoer.derdeHarmonischePct.toStringAsFixed(0)}% > 33%: '
'nulpuntsstroom I_N=${iNeutraal.toStringAsFixed(1)} A is maatgevend '
'(NEN 1010 Bijlage 52.E.1).',
);
}
}
// 3. Min. doorsnede kortsluit (per kabel: I_k / n bij gelijke impedantie)
double doorsnedeMinKs = 0.0;
if (invoer.kortsluitstroomA > 0) {
final tS = invoer.kortsluitduurMs / 1000.0;
final ikPerKabel = invoer.kortsluitstroomA / n;
doorsnedeMinKs = Kortsluit.minDoorsnede(
ikPerKabel, tS, invoer.geleider, invoer.isolatie,
);
}
// 4. Selecteer kabel (automatisch of geforceerd)
// De cyclische factor M wordt per kandidaat-kabel berekend omdat M afhankelijk
// is van de kabeldiameter en het Joule-verlies W = I²·R van die kabel.
final isolPropTMax = isolatieEigenschappen[invoer.isolatie]!.maxTempContinu;
final doCyclisch = invoer.isGrondkabel && invoer.cyclischProfiel != null;
double mVoorKabel(KabelSpec k) {
if (!doCyclisch) return 1.0;
final rM = Thermisch.rDcOpTemp(k);
final w = iPerKabel * iPerKabel * rM; // W/m bij Tmax
final deM = k.buitendiameter / 1000.0; // meter
final dp1 = invoer.cyclischHartOpHartMm > 0
? invoer.cyclischHartOpHartMm / 1000.0
: deM; // aanliggend: hart-op-hart = diameter
return CyclischeFactor.bereken(
profiel: invoer.cyclischProfiel!,
de: deM,
L: invoer.diepteM,
xi: invoer.lambdaGrond,
tMax: isolPropTMax,
tGr: invoer.grondtempC,
W: w,
N: invoer.cyclischNKringen,
dp1: dp1,
aanliggend: invoer.cyclischAanliggend,
);
}
KabelSpec? kabel;
var iz0 = 0.0;
var iz = 0.0;
var fCyclisch = 1.0;
final geforceerd = invoer.forceerDoorsnedemm2 != null;
if (geforceerd) {
// Geforceerde doorsnede: gebruik opgegeven waarde, toets alle criteria
final A = invoer.forceerDoorsnedemm2!;
final k = kabelCatalogus[(invoer.geleider, invoer.isolatie, A, catAders)];
if (k == null) {
fouten.add(
'Doorsnede $A mm² niet beschikbaar voor '
'${invoer.geleider.label} ${invoer.isolatie.label}.',
);
return Resultaten(
fouten: fouten,
iGevraagd: I,
tEffectief: tOmg,
fT: fT, fLegging: fLegging,
fBundel: fBundel, fGrond: fGrond, fTotaal: fBase,
fHorizontaal: fH, fVerticaal: fV,
doorsnedeMinKortsluit: doorsnedeMinKs,
bundelPositieWorst: bundelPos,
);
}
kabel = k;
fCyclisch = mVoorKabel(k);
iz0 = singelIn3Fase ? k.izC3 : k.izC;
iz = iz0 * fBase * fCyclisch * fHarmonisch;
if (iz < iDesign) {
final grondslag = harmonischOpNul ? 'I_N' : 'I_per_kabel';
fouten.add(
'Geforceerde doorsnede onderdimensioneerd: '
'I_z=${iz.toStringAsFixed(1)} A < $grondslag=${iDesign.toStringAsFixed(1)} A'
'${n > 1 ? " (I_totaal=${I.toStringAsFixed(1)} A / $n)" : ""}.',
);
}
if (A < doorsnedeMinKs) {
fouten.add(
'Geforceerde doorsnede $A mm² < kortsluit-minimum '
'${doorsnedeMinKs.toStringAsFixed(2)} mm².',
);
}
} else {
// Automatisch: kleinste doorsnede die aan alle eisen voldoet
final doorsnedes = standaardDoorsnedes(invoer.geleider);
var gevonden = false;
for (final A in doorsnedes) {
final k = kabelCatalogus[(invoer.geleider, invoer.isolatie, A, catAders)];
if (k == null) continue;
iz0 = singelIn3Fase ? k.izC3 : k.izC;
if (iz0 <= 0) continue;
final m = mVoorKabel(k);
iz = iz0 * fBase * m * fHarmonisch;
if (iz < iDesign) continue;
if (A < doorsnedeMinKs) {
waarschuwingen.add(
'$A mm² voldoet aan stroomeis maar niet aan kortsluit-eis '
'(min. ${doorsnedeMinKs.toStringAsFixed(2)} mm²).',
);
continue;
}
kabel = k;
fCyclisch = m;
gevonden = true;
break;
}
if (!gevonden) {
final grondslag = harmonischOpNul
? 'I_N=${iDesign.toStringAsFixed(1)} A (nulpuntsstroom)'
: 'I_per_kabel=${iDesign.toStringAsFixed(1)} A';
fouten.add(
'Geen geschikte kabel gevonden voor '
'$grondslag '
'(I_totaal=${I.toStringAsFixed(1)} A / $n) '
'met f_totaal=${(fBase * fHarmonisch).toStringAsFixed(3)}.',
);
return Resultaten(
fouten: fouten,
waarschuwingen: waarschuwingen,
iGevraagd: I,
tEffectief: tOmg,
fT: fT, fLegging: fLegging,
fBundel: fBundel, fGrond: fGrond, fTotaal: fBase * fHarmonisch,
fHarmonisch: fHarmonisch, harmonischOpNul: harmonischOpNul,
iNeutraal: iNeutraal,
fHorizontaal: fH, fVerticaal: fV,
doorsnedeMinKortsluit: doorsnedeMinKs,
bundelPositieWorst: bundelPos,
);
}
}
// fTotaal = alle factoren gecombineerd (IEC 60364 × cyclisch × harmonischen)
final fTotaal = fBase * fCyclisch * fHarmonisch;
final marge = iz > 0 ? (iz / iDesign - 1) * 100 : 0.0;
// 5. Spanningsval (per kabel: I/n door één kabel = zelfde ΔU als parallel geheel)
final (deltaUV, deltaUPct) = Spanningsval.bereken(invoer, kabel!,
iOverride: iPerKabel);
final okSpanning = deltaUPct <= invoer.maxSpanningsvalPct;
if (!okSpanning) {
fouten.add(
'Spanningsval ${deltaUPct.toStringAsFixed(2)}% > '
'${invoer.maxSpanningsvalPct.toStringAsFixed(2)}%.',
);
}
// 6. Temperatuur (per kabel: I/n per kabel)
final tMax = isolProp.maxTempContinu;
final rPerM = Thermisch.rDcOpTemp(kabel);
final pPerM = Thermisch.i2rVerlies(iPerKabel, rPerM);
final double deltaT;
if (invoer.isGrondkabel) {
deltaT = Thermisch.tempStijgingGrond(
pPerM, kabel.buitendiameter,
diepteM: invoer.diepteM,
lambdaGrond: invoer.lambdaGrond,
);
} else {
deltaT = Thermisch.tempStijgingLucht(pPerM, kabel.buitendiameter);
}
final geleiderTemp = tOmg + deltaT;
final okTemp = geleiderTemp <= tMax;
if (!okTemp) {
waarschuwingen.add(
'Geleidertemp. ${geleiderTemp.toStringAsFixed(1)} °C > '
'max ${tMax.toStringAsFixed(0)} °C (vereenvoudigd model).',
);
}
// 7. Kortsluittoets (per kabel: I_k / n)
double deltaTKs = 0;
double eindtempKs = 0;
bool? okKs;
final effectieveIk = invoer.effectieveKortsluitstroomA;
if (effectieveIk > 0) {
final tS = invoer.kortsluitduurMs / 1000.0;
final ikPerKabel = effectieveIk / n;
deltaTKs = Kortsluit.tempStijging(
ikPerKabel, tS, kabel.doorsnedemm2,
kabel.geleider, kabel.isolatie,
);
eindtempKs = tMax + deltaTKs;
okKs = eindtempKs <= isolProp.maxTempKortsluit;
if (okKs == false) {
fouten.add(
'Kortsluittoets: eindtemp ${eindtempKs.toStringAsFixed(1)} °C > '
'max ${isolProp.maxTempKortsluit.toStringAsFixed(0)} °C.',
);
}
}
// 7c. Maximale leidinglengte (NEN 1010 / IEC/TS 61200-53)
double? maxLengteM;
double? ikEind;
bool? okMaxLengte;
final ia = invoer.beveiligingIa;
if (ia != null && ia > 0) {
// Fase-naar-nul spanning (TN-systeem)
final uc = invoer.systeem == Systeemtype.ac3Fase
? invoer.spanningV / math.sqrt(3)
: invoer.spanningV;
// Lus-weerstand per meter per NEN 1010 / IEC/TS 61200-53:
// R20 uit kabelcatalogus (IEC 60228 waarden), gecorrigeerd naar
// gemiddelde fouttemperatuur θgem = (θmax + θks) / 2.
// Reactantiecorrectie voor doorsneden ≥ 150 mm² (NEN 1010 bijlage 53.F).
final gProp = geleiderEigenschappen[invoer.geleider]!;
final tGem = (tMax + isolProp.maxTempKortsluit) / 2;
final tempFactor = 1 + gProp.alpha20 * (tGem - 20);
final s = kabel.doorsnedemm2;
final reactFactor = s >= 300 ? 1.30
: s >= 240 ? 1.25
: s >= 185 ? 1.20
: s >= 150 ? 1.15
: 1.00;
final r20PerM = iec60228R20PerM(invoer.geleider, kabel.doorsnedemm2); // NEN-EN-IEC 60228:2005 Tabel 1
final rLoopPerM = 2 * r20PerM * tempFactor * reactFactor / n;
if (rLoopPerM > 0) {
// Bron-Ik voor formulekeuze §5.1 vs §5.2: alleen uit bronimpedantie.
// kortsluitstroomA bevat Ia (thermische toets) — niet de bron-kortsluitstroom.
final ik = invoer.bronimpedantieActief ? invoer.ikBronBerekendA : 0.0;
if (ik > 0 && ik > ia) {
// Bekende bronimpedantie (IEC/TS 61200-53 §5.2)
maxLengteM = uc * (ik - ia) / (ik * ia * rLoopPerM);
ikEind = uc / (uc / ik + rLoopPerM * invoer.lengteM);
} else if (ik <= 0) {
// Bronimpedantie onbekend: Ze = 0 (IEC/TS 61200-53 §5.1)
maxLengteM = uc / (ia * rLoopPerM);
ikEind = uc / (rLoopPerM * invoer.lengteM);
} else {
// ik > 0 maar ik ≤ ia: beveiliging spreekt zelfs zonder kabel niet aan
maxLengteM = 0;
ikEind = uc / (uc / ik + rLoopPerM * invoer.lengteM);
}
okMaxLengte = invoer.lengteM <= maxLengteM;
if (okMaxLengte == false) {
fouten.add(
'Leidinglengte ${invoer.lengteM.toStringAsFixed(1)} m > '
'maximale lengte ${maxLengteM.toStringAsFixed(1)} m '
'(I_a=${ia.toStringAsFixed(1)} A).',
);
}
}
}
// 7d. Bronimpedantie resultaten + NEN 1010 stelsel-waarschuwingen
double? zbOhmResult;
double? zbNetOhmResult;
double? ik3fBronResult;
double? ik1fBronResult;
double? zKabelLusResult;
double? ik1fEindResult;
if (invoer.bronimpedantieActief) {
final zb = invoer.zbOhm;
if (zb > 0) {
zbOhmResult = zb;
if (!invoer.skNetOneindig && invoer.skNetMva > 0) {
zbNetOhmResult = BronImpedantie.zNetOhm(skNetMva: invoer.skNetMva,
uSecV: invoer.spanningV);
}
ik3fBronResult = BronImpedantie.ik3f(zbOhm: zb, uLlV: invoer.spanningV);
ik1fBronResult = invoer.ikBronBerekendA;
// Lusimpedantie kabel bij gegeven lengte (zelfde methode als stap 7c)
final gPropB = geleiderEigenschappen[invoer.geleider]!;
final tGemB = (tMax + isolProp.maxTempKortsluit) / 2;
final tempFactorB = 1 + gPropB.alpha20 * (tGemB - 20);
final sB = kabel.doorsnedemm2;
final reactFactorB = sB >= 300 ? 1.30
: sB >= 240 ? 1.25
: sB >= 185 ? 1.20
: sB >= 150 ? 1.15
: 1.00;
final r20PerMB = iec60228R20PerM(invoer.geleider, kabel.doorsnedemm2);
final rLoopPerMB = 2 * r20PerMB * tempFactorB * reactFactorB / n;
final zKabelLus = rLoopPerMB * invoer.lengteM;
zKabelLusResult = zKabelLus;
ik1fEindResult = BronImpedantie.ik1fEind(
zbOhm: zb,
zKabelLusOhm: zKabelLus,
uLnV: invoer.uFaseV,
);
}
// NEN 1010 aardingsstelsel-waarschuwingen
switch (invoer.aardingsstelsel) {
case Aardingsstelsel.it:
waarschuwingen.add(
'IT-stelsel: aardfoutdetectie/-bewaking vereist (NEN 1010 §2.4.7). '
'De I_k waarden gelden voor de tweede aardingsfout.',
);
case Aardingsstelsel.tt:
waarschuwingen.add(
'TT-stelsel: foutbescherming via RCD (aardlekschakelaar) vereist '
'(NEN 1010 §4.1.2.2). I_k-berekening is indicatief.',
);
case Aardingsstelsel.tnC:
waarschuwingen.add(
'TN-C: PEN-geleider niet toegestaan bij A < 10 mm² Cu of '
'A < 16 mm² Al (NEN 1010 §5.4.2).',
);
default:
break;
}
}
// 7b. Bundel: warmste (centrum) vs koudste (hoek) positie
double fBundelRand = fBundel;
double izRand = iz;
double margeRand = marge;
double tWarm = geleiderTemp;
double tKoud = geleiderTemp;
if (invoer.bundel != null && invoer.bundel!.totaalKabels > 1) {
final nH = invoer.bundel!.nHorizontaal;
final nV = invoer.bundel!.nVerticaal;
// Hoekpositie: max 1 horizontale buur en 1 verticale buur
final fHRand = nH <= 1 ? 1.0 : Correctiefactoren.fHorizontaalBundeling(2);
final fVRand = nV <= 1 ? 1.0 : Correctiefactoren.fVerticalStapeling(2);
fBundelRand = fHRand * fVRand;
izRand = iz0 * fT * fLegging * fBundelRand * fGrond * fCyclisch * fHarmonisch;
margeRand = izRand > 0 ? (izRand / iDesign - 1) * 100 : 0.0;
// Effectieve omgevingstemperatuur per positie (IEC correctiefactor omgekeerd):
// fBundel = √[(T_max−T_eff)/(T_max−T_omg)] → T_eff = T_max − (T_max−T_omg)·fBundel²
final tEffWarm = tMax - (tMax - tOmg) * fBundel * fBundel;
final tEffKoud = tMax - (tMax - tOmg) * fBundelRand * fBundelRand;
tWarm = tEffWarm + deltaT;
tKoud = tEffKoud + deltaT;
}
// 8. Eindoordeel
final voldoet = fouten.isEmpty;
return Resultaten(
kabel: kabel,
doorsnedeMinKortsluit: doorsnedeMinKs,
iGevraagd: I,
tEffectief: tOmg,
fT: fT, fLegging: fLegging, fBundel: fBundel, fGrond: fGrond,
fCyclisch: fCyclisch, fHarmonisch: fHarmonisch,
harmonischOpNul: harmonischOpNul, iNeutraal: iNeutraal,
fTotaal: fTotaal,
fHorizontaal: fH, fVerticaal: fV,
nParallel: n, iPerKabel: iPerKabel,
iz0: iz0, iz: iz, margeStroomPct: marge,
bundelPositieWorst: bundelPos,
fBundelRand: fBundelRand, izRand: izRand, margeStroomPctRand: margeRand,
geleiderTempCWarm: tWarm, geleiderTempCKoud: tKoud,
deltaUV: deltaUV, deltaUPct: deltaUPct, okSpanning: okSpanning,
i2rVerliesWPerM: pPerM, tempStijgingK: deltaT,
geleiderTempC: geleiderTemp, maxTempC: tMax, okTemp: okTemp,
deltaTKortsluitK: deltaTKs, eindtempKortsluitC: eindtempKs,
okKortsluit: okKs,
maxLengteM: maxLengteM,
ikEind: ikEind,
okMaxLengte: okMaxLengte,
zbOhm: zbOhmResult,
zbNetOhm: zbNetOhmResult,
ik3fBronA: ik3fBronResult,
ik1fBronA: ik1fBronResult,
zKabelLusOhm: zKabelLusResult,
ik1fEindA: ik1fEindResult,
voldoet: voldoet,
fouten: fouten,
waarschuwingen: waarschuwingen,
);
}