11// xPower Flow Card — Modern power flow card for solar hybrid inverters
22// Copyright (C) 2025 BTNBx — MIT License
3- const V = '1.2.5 ' ;
3+ const V = '1.2.6 ' ;
44
55/* ═══════════════════════════════════════
66 xPower Flow Card — i18n
@@ -163,7 +163,7 @@ const PRESETS={
163163 } ,
164164 victron :{
165165 label :'Victron (Venus OS)' , inverter_name :'VICTRON' ,
166- bat_polarity :'negative ' , grid_polarity :'positive' ,
166+ bat_polarity :'positive ' , grid_polarity :'positive' ,
167167 e :{ solar :'sensor.pv_power' , battery :'sensor.battery_power' , soc :'sensor.battery_soc' ,
168168 grid :'sensor.grid_power' , load :'sensor.ac_consumption' , grid_voltage :'sensor.grid_voltage' ,
169169 battery_voltage :'sensor.battery_voltage' , pv_voltage :'sensor.pv_voltage' ,
@@ -185,6 +185,18 @@ const PRESETS={
185185 daily_export :'' , daily_load :'' , daily_charge :'' , daily_discharge :'' ,
186186 battery_temperature :'' }
187187 } ,
188+ solis :{
189+ label :'Solis (SolisCloud)' , inverter_name :'SOLIS' ,
190+ bat_polarity :'positive' , grid_polarity :'positive' ,
191+ e :{ solar :'sensor.solis_pv_power' , battery :'sensor.solis_battery_power' , soc :'sensor.solis_battery_soc' ,
192+ grid :'sensor.solis_grid_power' , load :'sensor.solis_house_load' , grid_voltage :'sensor.solis_grid_voltage' ,
193+ battery_voltage :'sensor.solis_battery_voltage' , pv_voltage :'sensor.solis_pv_voltage' ,
194+ temperature :'sensor.solis_inverter_temperature' , frequency :'sensor.solis_grid_frequency' ,
195+ grid_status :'' , daily_solar :'sensor.solis_today_energy_generation' , daily_import :'sensor.solis_today_grid_import' ,
196+ daily_export :'sensor.solis_today_grid_export' , daily_load :'sensor.solis_today_load_consumption' ,
197+ daily_charge :'sensor.solis_today_battery_charge' , daily_discharge :'sensor.solis_today_battery_discharge' ,
198+ battery_temperature :'sensor.solis_battery_temperature' }
199+ } ,
188200 custom :{
189201 label :'Custom' , inverter_name :'Inverter' ,
190202 bat_polarity :'negative' , grid_polarity :'positive' ,
@@ -197,6 +209,7 @@ const PRESETS={
197209const DEFAULTS = {
198210 preset :'deye' ,
199211 ...PRESETS . deye . e ,
212+ solar2 :'' , pv_voltage2 :'' ,
200213 bat_polarity :'negative' , grid_polarity :'positive' ,
201214 shutdown_soc :20 , battery_capacity :5120 , language :'pt' ,
202215 inverter_name :'DEYE' ,
@@ -207,12 +220,14 @@ const DEFAULTS={
207220} ;
208221
209222const ENTITY_FIELDS = [
210- { key :'solar' , label :'Solar Power' } , { key :'battery' , label :'Battery Power' } ,
223+ { key :'solar' , label :'Solar Power (MPPT1)' } , { key :'solar2' , label :'Solar Power (MPPT2)' } ,
224+ { key :'battery' , label :'Battery Power' } ,
211225 { key :'soc' , label :'Battery SOC' } , { key :'grid' , label :'Grid Power' } ,
212226 { key :'load' , label :'Load Power' } , { key :'grid_voltage' , label :'Grid Voltage' } ,
213- { key :'battery_voltage' , label :'Battery Voltage' } , { key : 'pv_voltage' , label : 'PV Voltage' } ,
227+ { key :'battery_voltage' , label :'Battery Voltage' } ,
214228 { key :'temperature' , label :'Inverter Temp.' } , { key :'battery_temperature' , label :'Battery Temp.' } ,
215229 { key :'frequency' , label :'Grid Frequency' } , { key :'grid_status' , label :'Grid Status' } ,
230+ { key :'pv_voltage' , label :'PV Voltage (MPPT1)' } , { key :'pv_voltage2' , label :'PV Voltage (MPPT2)' } ,
216231 { key :'daily_solar' , label :'Daily Solar' } , { key :'daily_import' , label :'Daily Import' } ,
217232 { key :'daily_export' , label :'Daily Export' } , { key :'daily_load' , label :'Daily Load' } ,
218233 { key :'daily_charge' , label :'Daily Charge' } , { key :'daily_discharge' , label :'Daily Discharge' } ,
@@ -336,8 +351,8 @@ class XPowerFlowCardEditor extends HTMLElement{
336351 <div class="field">
337352 <label>${ L . editor_bat_pol } </label>
338353 <select id="ed-bpol" style="${ selStyle } ">
339- <option value="negative" ${ c . bat_polarity === 'negative' ?'selected' :'' } >${ L . charging } (Deye, Sunsynk, Victron )</option>
340- <option value="positive" ${ c . bat_polarity === 'positive' ?'selected' :'' } >${ L . charging } (Huawei, Fronius, SolarEdge)</option>
354+ <option value="negative" ${ c . bat_polarity === 'negative' ?'selected' :'' } >${ L . charging } (Deye, Sunsynk, Growatt )</option>
355+ <option value="positive" ${ c . bat_polarity === 'positive' ?'selected' :'' } >${ L . charging } (Huawei, Fronius, SolarEdge, Victron, Solis )</option>
341356 </select>
342357 </div>
343358 <div class="field">
@@ -492,7 +507,7 @@ svg{width:100%;height:auto;display:block}
492507.ss #hs{fill:none;stroke:rgba(102,187,106,0.7);stroke-width:1.2}.sc #hl{fill:none;stroke:rgba(38,198,218,0.7);stroke-width:1.2}.sg #hg{fill:none;stroke:rgba(66,165,245,0.7);stroke-width:1.2}.sbt #hb2{fill:none;stroke:rgba(124,77,255,0.7);stroke-width:1.2}
493508.ss #hsa{fill:rgba(102,187,106,0.12);stroke:none}.sc #hla{fill:rgba(38,198,218,0.12);stroke:none}.sg #hga{fill:rgba(66,165,245,0.12);stroke:none}.sbt #hb2a{fill:rgba(124,77,255,0.12);stroke:none}
494509</style>
495- <ha-card><svg viewBox="0 0 526 520 "><defs><filter id="glow" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><g transform="translate(25.5,10) scale(0.95)">
510+ <ha-card><svg viewBox="0 0 526 510 "><defs><filter id="glow" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><g transform="translate(25.5,10) scale(0.95)">
496511<g id="wicons" style="display:none" transform="translate(-20,0)">
497512<rect class="wb" x="-2" y="-4" width="86" height="20"/>
498513<g transform="translate(0,1)"><rect x="6" y="0" width="2.5" height="7" rx="1.2" fill="none" stroke="var(--t3)" stroke-width="0.7"/><circle cx="7.2" cy="9" r="2.5" fill="none" stroke="var(--t3)" stroke-width="0.7"/><line x1="7.2" y1="3" x2="7.2" y2="7" stroke="var(--red)" stroke-width="1" stroke-linecap="round"/><circle cx="7.2" cy="9" r="1.2" fill="var(--red)"/></g>
@@ -508,7 +523,7 @@ svg{width:100%;height:auto;display:block}
508523<g id="nGrid" class="ct"><g id="gridIcon" transform="translate(66,225) scale(1.65) translate(-66,-196)"><rect x="64" y="181" width="4" height="30" rx="1" fill="var(--red)" opacity="0.7"/><rect x="54" y="183" width="24" height="3" rx="1" fill="var(--red)" opacity="0.6"/><rect x="57" y="192" width="18" height="2.5" rx="1" fill="var(--red)" opacity="0.5"/><path d="M60,211 L64,199 L68,199 L72,211" fill="var(--red)" opacity="0.4"/><circle cx="56" cy="184" r="1.5" fill="var(--red)" opacity="0.8"/><circle cx="76" cy="184" r="1.5" fill="var(--red)" opacity="0.8"/><circle cx="58" cy="193" r="1.2" fill="var(--red)" opacity="0.7"/><circle cx="74" cy="193" r="1.2" fill="var(--red)" opacity="0.7"/><line x1="54" y1="184" x2="46" y2="181" stroke="var(--red)" stroke-width="0.8" opacity="0.3"/><line x1="78" y1="184" x2="86" y2="181" stroke="var(--red)" stroke-width="0.8" opacity="0.3"/></g><text x="66" y="268" class="vm" style="fill:var(--red)" id="vg"></text><text x="66" y="190" class="vl">${ L . grid } </text><text x="66" y="286" class="vc" id="gv"></text><circle id="gsd" cx="92" cy="189" r="4" fill="rgba(255,255,255,0.12)"/><text x="66" y="300" class="vd" id="dg"></text></g>
509524<g id="nLoad" class="ct"><g id="loadIcon" transform="translate(434,225) scale(1.65) translate(-434,-188)"><path d="M416,188 L434,174 L452,188 Z" fill="var(--load)" opacity="0.8"/><rect x="420" y="187" width="28" height="18" rx="1" fill="var(--load)" opacity="0.6"/><rect x="430" y="195" width="8" height="10" rx="1" fill="rgba(0,0,0,0.3)"/><rect x="422" y="190" width="6" height="5" rx="0.5" fill="rgba(255,255,255,0.15)"/><rect x="440" y="190" width="6" height="5" rx="0.5" fill="rgba(255,255,255,0.15)"/><rect x="441" y="176" width="5" height="8" rx="1" fill="var(--load)" opacity="0.5"/></g><text x="434" y="268" class="vm" style="fill:var(--load)" id="vl"></text><text x="434" y="190" class="vl">${ L . load } </text><text x="434" y="288" class="vd" id="dl"></text></g>
510525<g id="nBat" class="ct"><g id="batIcon" transform="translate(250,400) scale(1.70) translate(-250,-351)"><rect x="232" y="341" width="32" height="20" rx="3" fill="var(--battery)" opacity="0.75"/><rect x="264" y="345.5" width="6" height="11" rx="2" fill="var(--battery)" opacity="0.9"/><rect x="235" y="344" width="26" height="14" rx="1.5" fill="rgba(0,0,0,0.35)"/><rect id="bl" x="235" y="344" width="26" height="14" rx="1.5" fill="var(--battery)" opacity="0.45"/></g><text x="250" y="440" class="vm" style="fill:var(--solar)" id="vb"></text><text x="250" y="462" class="vs" id="vc"></text><text x="250" y="372" class="vl">${ L . battery } </text><text x="310" y="394" class="vc" id="bv" text-anchor="start"></text><text x="310" y="406" class="vc" id="bt" text-anchor="start"></text><text x="250" y="483" class="vd" id="db"></text><text x="250" y="498" class="vc" id="br" style="fill:var(--t1)"></text></g>
511- <rect id="ap" x="185 " y="512 " width="130 " height="16" class=" au-pill" fill ="var(--green)"/><text x="250 " y="521" class="au" id="va">< /text>
526+ <defs><clipPath id="au-clip"><rect x="452 " y="414 " width="70 " height="96" rx="9"/></clipPath></defs><rect x="452" y="414" width="70" height="96" rx="9" fill="rgba(12,14,24,0.92)"/><rect x="452" y="414" width="70" height="96" rx="9" fill="none" id=" au-border" stroke ="var(--green)" stroke-width="2.5" /><text x="487 " y="458" id="va" font-family="-apple-system,sans-serif" font-size="30" font-weight="800" fill="white" text-anchor="middle" dominant-baseline="middle"></text><g clip-path="url(#au-clip)"><rect x="452" y="478" width="70" height="32" id="au-bar" fill="var(--green)"/><text x="487" y="494" font-family="-apple-system,sans-serif" font-size="7" font-weight="700" fill="white" text-anchor="middle" dominant-baseline="middle" letter-spacing="0.04em">AUTOSSUFICIÊNCIA< /text></g >
512527</g></svg>
513528<div class="sr">
514529<div class="sb sg"><div class="sb-header"><span class="sl">${ L . grid24 } </span><span class="sv" id="hz"></span></div><svg viewBox="0 0 200 55" preserveAspectRatio="none"><path id="hga"/><path id="hg"/><line class="cursor" id="cg" x1="0" y1="0" x2="0" y2="55"/><circle class="cursor-dot" id="dg2" cx="0" cy="0" r="3"/></svg><span class="sb-tip" id="tg"></span></div>
@@ -561,7 +576,9 @@ _drawSparks(){this._spark('hs','hsa',this._hist.solar);this._spark('hl','hla',th
561576_update ( ) { if ( ! this . _h || ! this . shadowRoot . getElementById ( 'vs' ) ) return ;
562577const c = this . _c , L = this . _lang ;
563578
564- const sol = this . _gv ( c . solar ) ;
579+ const sol1 = this . _gv ( c . solar ) ;
580+ const sol2 = this . _gv ( c . solar2 ) ;
581+ const sol = sol1 !== null && sol2 !== null ?sol1 + sol2 :sol1 !== null ?sol1 :sol2 ;
565582const bat = this . _norm ( this . _gv ( c . battery ) , 'bat' ) ;
566583const soc = this . _gv ( c . soc ) ;
567584const grid = this . _norm ( this . _gv ( c . grid ) , 'grid' ) ;
@@ -570,6 +587,7 @@ const temp=this._gv(c.temperature);
570587const gv = this . _gv ( c . grid_voltage ) ;
571588const bv = this . _gv ( c . battery_voltage ) ;
572589const pvv = this . _gv ( c . pv_voltage ) ;
590+ const pvv2 = this . _gv ( c . pv_voltage2 ) ;
573591const freq = this . _gv ( c . frequency ) ;
574592
575593const p = this . _prev ;
@@ -585,8 +603,8 @@ this._$('bl').setAttribute('width',(26*(socVal/100)).toFixed(1));
585603const blEl = this . _$ ( 'bl' ) ; if ( blEl ) { if ( bat !== null && bat < - 10 ) { blEl . setAttribute ( 'fill' , '#4CD964' ) ; blEl . setAttribute ( 'class' , 'bat-charge' ) ; } else { blEl . setAttribute ( 'fill' , 'var(--battery)' ) ; blEl . removeAttribute ( 'class' ) ; } }
586604
587605if ( temp !== null ) this . _$ ( 'tp' ) . textContent = temp . toFixed ( 0 ) + '\u00B0C' ; else this . _$ ( 'tp' ) . textContent = '' ;
588- if ( pvv !== null ) this . _$ ( 'pv' ) . textContent = pvv . toFixed ( 0 ) + 'V' ; else this . _$ ( 'pv' ) . textContent = '' ;
589- if ( gv !== null && freq !== null ) this . _$ ( 'gv' ) . textContent = gv . toFixed ( 0 ) + 'V \u00B7 ' + freq . toFixed ( 1 ) + 'Hz' ; else this . _$ ( 'gv' ) . textContent = '' ;
606+ if ( pvv !== null && pvv2 !== null ) this . _$ ( 'pv' ) . textContent = pvv . toFixed ( 0 ) + 'V / ' + pvv2 . toFixed ( 0 ) + 'V' ; else if ( pvv !== null ) this . _$ ( 'pv' ) . textContent = pvv . toFixed ( 0 ) + 'V' ; else if ( pvv2 !== null ) this . _$ ( 'pv' ) . textContent = pvv2 . toFixed ( 0 ) + 'V' ; else this . _$ ( 'pv' ) . textContent = '' ;
607+ if ( gv !== null && freq !== null ) this . _$ ( 'gv' ) . textContent = gv . toFixed ( 0 ) + 'V \u00B7 ' + freq . toFixed ( 1 ) + 'Hz' ; else if ( gv !== null ) this . _$ ( 'gv' ) . textContent = gv . toFixed ( 0 ) + 'V' ; else if ( freq !== null ) this . _$ ( 'gv' ) . textContent = freq . toFixed ( 1 ) + 'Hz' ; else this . _$ ( 'gv' ) . textContent = '' ;
590608if ( bv !== null ) this . _$ ( 'bv' ) . textContent = bv . toFixed ( 1 ) + 'V' ; else this . _$ ( 'bv' ) . textContent = '' ;
591609const bt = this . _gv ( c . battery_temperature ) ; if ( bt !== null ) this . _$ ( 'bt' ) . textContent = bt . toFixed ( 0 ) + '\u00B0C' ; else this . _$ ( 'bt' ) . textContent = '' ;
592610
@@ -618,8 +636,8 @@ if(batF>RUNTIME_MIN_W&&socVal>shuSoc){
618636
619637const gridImp = gridF > 0 ?gridF :0 ;
620638const au = loadF > 0 ?Math . max ( 0 , Math . min ( 100 , ( ( loadF - gridImp ) / loadF ) * 100 ) ) :0 ;
621- this . _$ ( 'va' ) . textContent = L . autarky + ': ' + au . toFixed ( 0 ) + '%' ;
622- const ap = this . _$ ( 'ap' ) ; if ( ap ) { let pc ; if ( au >= 80 ) pc = '#66BB6A' ; else if ( au >= 50 ) pc = '#FFA726' ; else if ( au >= 25 ) pc = '#FF7043' ; else pc = '#EF5350' ; ap . setAttribute ( 'fill' , pc ) ; }
639+ this . _$ ( 'va' ) . textContent = au . toFixed ( 0 ) + '%' ;
640+ let auC ; if ( au >= 80 ) auC = '#66BB6A' ; else if ( au >= 50 ) auC = '#FFA726' ; else if ( au >= 25 ) auC = '#FF7043' ; else auC = '#EF5350' ; const auBar = this . _$ ( 'au-bar' ) ; if ( auBar ) auBar . setAttribute ( 'fill' , auC ) ; const auBorder = this . _$ ( 'au-border' ) ; if ( auBorder ) auBorder . setAttribute ( 'stroke' , auC ) ;
623641
624642const wtv = this . _gv ( c . weather_temp ) ; const whv = this . _gv ( c . weather_humidity ) ;
625643const wicons = this . _$ ( 'wicons' ) ; const wdrop = this . _$ ( 'wdrop' ) ; const wdiv = this . _$ ( 'wdiv' ) ;
@@ -672,7 +690,7 @@ const lcd=this._$('lcd');if(lcd){const total=solF+Math.abs(batF)+Math.abs(gridF)
672690const a = Math . abs ( total ) ; lcd . textContent = a >= 1000 ?( a / 1000 ) . toFixed ( 1 ) + 'kW' :a . toFixed ( 0 ) + 'W' ; }
673691
674692// #3 Pill glow — autarky >90%
675- const apEl = this . _$ ( 'ap ' ) ; if ( apEl ) { if ( au >= 90 ) { apEl . style . filter = 'drop-shadow(0 0 6px rgba(255,215,0,0.6))' ; apEl . setAttribute ( 'class' , 'au-pill au-glow' ) ; } else { apEl . style . filter = '' ; apEl . setAttribute ( 'class' , 'au-pill' ) ; } }
693+ const auBorderEl = this . _$ ( 'au-border ' ) ; if ( auBorderEl && au >= 90 ) { auBorderEl . style . filter = 'drop-shadow(0 0 4px rgba(255,215,0,0.6))' ; } else if ( auBorderEl ) { auBorderEl . style . filter = '' ; }
676694}
677695
678696getCardSize ( ) { return this . _c . compact ?4 :6 ; }
0 commit comments