<div dir="ltr"><div>Missed a few things; so how about this as a more general function:</div><div><br></div><div>* chargeVolt is probably not going to get used - may remove it </div><div>* chargewatts - is the current power being used to charge.</div><div>* availwatts - Is the reported available amount to use (can leave as 0)</div><div>* adjustTemp - true if 'tempCelcius' is valid.</div><div>* tempCelcius - The current battery temperature.</div><div>* fromSoc,toSoc - the current and target 'state of charge'</div><div>* batterySizeWh - The battery size in whatt-hours</div><div>* charge_steps - Array of steps. </div><div dir="ltr"><br></div><div dir="ltr">int CalcRemainingChargeMins(int chargeVolt, float chargewatts, float availwatts, bool adjustTemp, float tempCelcius, int fromSoc, int toSoc, int batterySizeWh, charging_step_t charge_steps[])<br>{<br>  if (chargewatts <= 0 || fromSoc >= toSoc)<br>    return 0;<br>  if (availwatts < chargewatts) // encompasses '0' case.<br>    availwatts = chargewatts;<br><br>  int last_to_soc = fromSoc;<br>  int last_charge_watts = chargewatts;<br><br>  // Track the sum of percent/kw.  Reduces floating point operations.<br>  // The 'batterySizeWh' * 60 / 100 would be used on every sum.. so factored to the end.<br>  float sumval = 0;<br>  bool finish = false;<br>  for (int i = 0; !finish; ++i) {<br>    const charging_step_t &step = charge_steps[i];<br>    if (step.maxChargeWatts <= 0)<br>      break;<br><br>    if (step.toPercent > last_to_soc) {<br>      int next_to_soc = step.toPercent;<br>      float maxCharge = step.maxChargeWatts ;<br>      if (adjustTemp && step.tempOptimal > 0) {<br>        maxCharge *= 1-(std::abs(tempCelcius - step.tempOptimal) * step.tempDegrade);<br>        if (maxCharge < 0) {<br>          // Shouldn't happen; it means the numbers are wrong.<br>          maxCharge = chargewatts;<br>        }<br>      }<br>      int percents_In_Step = next_to_soc - last_to_soc;<br>      if (toSoc < next_to_soc) { // This would be the last one.<br>        int new_percents_in_step = toSoc - last_to_soc;<br>        if (last_charge_watts < maxCharge) // Avoids -ve and 0 cases<br>          maxCharge += new_percents_in_step * (maxCharge - last_charge_watts) / percents_In_Step;<br>        next_to_soc = toSoc;<br>        percents_In_Step = new_percents_in_step;<br>        finish = true; // Last one.<br>      }<br>      float end_charge_watts = std::min(availwatts, maxCharge);<br>      // Keep it as a doubled value here to avoid a /2 which becomes *2 below.<br>      float curSpeedDbl = last_charge_watts + end_charge_watts;<br><br>      // our one main division.<br>      sumval += ( percents_In_Step * 2) / curSpeedDbl;<br>      last_to_soc = next_to_soc;<br>      last_charge_watts = end_charge_watts;<br>    }<br>  }<br>  if (last_to_soc < toSoc) {<br>    sumval +=  (toSoc - last_to_soc) / last_charge_watts;<br>  }<br>  // convert to minutes (summary value to minutes) <br>  // 60 (hours to mins) / 100 (percent to ratio) -> 0.6<br>  return (int)((batterySizeWh * sumval * 0.6) + 0.5);<br>}<br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sat, 31 Dec 2022 at 18:10, Michael Geddes <<a href="mailto:frog@bunyip.wheelycreek.net">frog@bunyip.wheelycreek.net</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr">Ok,<br><div>so I've reviewed my current charge curve and have another idea as to what was happening... which is that the charger itself was dynamically changing the power-available to be used for charging .. and stepping up and down in increments.  This makes sense of the difference between the two charge sessions I recorded by hand, and the stepped nature of the curve I recorded.</div><div><br></div><div>The only  vehicle code that takes temperature into account is the roadster .. which is definitely not generic... and is doing calculations in miles and degrees Celsius (weird) and is possibly not going to be relevant or easy to leverage.  All other code  (apart from VWUP) uses: </div><div>* A custom function (no table)</div><div>* a single flat value or </div><div>* a version of the flat stepped code with percent ranges.</div><div><br></div><div>I'll move mine to use a (calculated) gradient between points and lose the dependence on voltage.</div><div><br></div><div>I don't see that your code significantly reduces the number of division operations used (though I totally understand that aim) since it uses it for the first and last soc section only.</div><div><br></div><div>What might actually be better is a temperature efficiency gradient centred around 20 degrees. Either a single one - or in the table. See below.</div><div><br></div><div>//.ichael</div><div><br></div><div>----8<--</div><div><pre id="m_3380541375016793904gmail-vimCodeElement" style="font-size:13px;white-space:pre-wrap;color:rgb(0,0,0)"><span style="font-size:1em;color:rgb(0,0,255)"><br>// Charging profile</span>
<span style="font-size:1em;color:rgb(0,0,255)">//  - Must be from lowest to highest to%.</span>
<span style="font-size:1em;color:rgb(0,0,255)">//  - max watts is at optimal temperature.</span>
<span style="font-size:1em;color:rgb(0,0,255)">//  - MaxWattsUsed = maxChargeWatts * 1-(|battery_temp - tempOptimal| * tempDegrade)</span>

<span style="font-size:1em;color:rgb(0,0,255)">// 0.0182 means degrades by 40% of original at zero degress</span>
<span style="font-size:1em;color:rgb(0,0,255)">// - GUESTIMATE ONLY needs data.</span>
charging_step_t ioniq5_chargesteps[] = {
  <span style="font-size:1em;color:rgb(0,0,255)">//  to     max  optimal temp degrade</span>
  <span style="font-size:1em;color:rgb(0,0,255)">//   %   watts  deg C   gradient</span>
  {   <span style="font-size:1em;color:rgb(255,0,255)">10</span>, <span style="font-size:1em;color:rgb(255,0,255)">220000</span>,    <span style="font-size:1em;color:rgb(255,0,255)">22</span>,  <span style="font-size:1em;color:rgb(255,0,255)">0.0182</span> },
  {   <span style="font-size:1em;color:rgb(255,0,255)">25</span>, <span style="font-size:1em;color:rgb(255,0,255)">210000</span>,    <span style="font-size:1em;color:rgb(255,0,255)">22</span>,  <span style="font-size:1em;color:rgb(255,0,255)">0.0182</span> },
  {   <span style="font-size:1em;color:rgb(255,0,255)">45</span>, <span style="font-size:1em;color:rgb(255,0,255)">200000</span>,    <span style="font-size:1em;color:rgb(255,0,255)">22</span>,  <span style="font-size:1em;color:rgb(255,0,255)">0.0182</span> },
  {   <span style="font-size:1em;color:rgb(255,0,255)">75</span>, <span style="font-size:1em;color:rgb(255,0,255)">150000</span>,    <span style="font-size:1em;color:rgb(255,0,255)">22</span>,  <span style="font-size:1em;color:rgb(255,0,255)">0.0182</span> },
  {   <span style="font-size:1em;color:rgb(255,0,255)">80</span>,  <span style="font-size:1em;color:rgb(255,0,255)">80000</span>,    <span style="font-size:1em;color:rgb(255,0,255)">22</span>,  <span style="font-size:1em;color:rgb(255,0,255)">0.0182</span> },
  {   <span style="font-size:1em;color:rgb(255,0,255)">85</span>,  <span style="font-size:1em;color:rgb(255,0,255)">60000</span>,    <span style="font-size:1em;color:rgb(255,0,255)">22</span>,  <span style="font-size:1em;color:rgb(255,0,255)">0.0182</span> },
  {   <span style="font-size:1em;color:rgb(255,0,255)">90</span>,  <span style="font-size:1em;color:rgb(255,0,255)">40000</span>,    <span style="font-size:1em;color:rgb(255,0,255)">22</span>,  <span style="font-size:1em;color:rgb(255,0,255)">0.0182</span> },
  {   <span style="font-size:1em;color:rgb(255,0,255)">95</span>,  <span style="font-size:1em;color:rgb(255,0,255)">25000</span>,    <span style="font-size:1em;color:rgb(255,0,255)">22</span>,  <span style="font-size:1em;color:rgb(255,0,255)">0.0182</span> },
  {  <span style="font-size:1em;color:rgb(255,0,255)">100</span>,   <span style="font-size:1em;color:rgb(255,0,255)">7200</span>,    <span style="font-size:1em;color:rgb(255,0,255)">22</span>,  <span style="font-size:1em;color:rgb(255,0,255)">0.0182</span> },
  { <span style="font-size:1em;color:rgb(255,0,255)">0</span>, <span style="font-size:1em;color:rgb(255,0,255)">0</span>, <span style="font-size:1em;color:rgb(255,0,255)">0</span>, <span style="font-size:1em;color:rgb(255,0,255)">0</span>},
};
<span style="font-size:1em;color:rgb(255,139,139)">/**</span><span style="font-size:1em;color:rgb(255,139,139)"> </span><span style="font-size:1em;color:rgb(173,216,230);font-weight:bold;font-style:italic">Calculate the remaining minutes to charge.</span>
  <span style="font-size:1em;color:rgb(255,139,139)">*</span><span style="font-size:1em;color:rgb(0,0,255)"> Works on the premise that the charge curve applies to the MAXIMUM only.</span>
  <span style="font-size:1em;color:rgb(255,139,139)">*</span><span style="font-size:1em;color:rgb(0,0,255)"> Each section is calculated such that the 'max watts' is effectively the 'end' value at that 'to'.. </span>
  <span style="font-size:1em;color:rgb(255,139,139)">*</span><span style="font-size:1em;color:rgb(0,0,255)"> and the power use is assumed to be a gradient from the last 'to' value to the end 'to' value.</span>
<span style="font-size:1em;color:rgb(255,139,139)">*/</span>
<span style="font-size:1em;color:rgb(46,139,87);font-weight:bold">int</span> CalcRemainingChargeMins(<span style="font-size:1em;color:rgb(46,139,87);font-weight:bold">int</span> chargeVolt, <span style="font-size:1em;color:rgb(46,139,87);font-weight:bold">float</span> chargewatts, <span style="font-size:1em;color:rgb(46,139,87);font-weight:bold">float</span> availwatts, <span style="font-size:1em;color:rgb(46,139,87);font-weight:bold">bool</span> adjustTemp, <span style="font-size:1em;color:rgb(46,139,87);font-weight:bold">float</span> tempCelcius, <span style="font-size:1em;color:rgb(46,139,87);font-weight:bold">int</span> fromSoc, <span style="font-size:1em;color:rgb(46,139,87);font-weight:bold">int</span> toSoc, <span style="font-size:1em;color:rgb(46,139,87);font-weight:bold">int</span> batterySizeWh, charging_step_t charge_steps[])
{ .....
}</pre></div><div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, 30 Dec 2022 at 07:59, Michael Geddes <<a href="mailto:frog@bunyip.wheelycreek.net" target="_blank">frog@bunyip.wheelycreek.net</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr">Firstly, the Voltage architecture (800V, 400V, 240V 3Phase,  240V 1Phase) in use  is going to affect the amount of power able to be delivered because the limiting factor will be the current  and possibly the potential difference across the individual cell (?)- so it is my _thesis_ that this will matter, especially in the mid section of the charging;  I need to get more information on this so I have made a plugin to handle gathering data. I'm quite happy to ditch it if it doesn't bare out.</div><div>Ah, also there are two more metrics that I should log that you have reminded me of: The available power (I think I have that metric) and the temperature (? ave of the batteries perhaps).  </div><div dir="ltr"><br></div><div dir="ltr">I'll have to try and get to one of the few 800v chargers available in my state again - finding 50kw chargers is pretty easy.<div><br></div><div>I have mapped by hand the charging 'curve' at an 800V charger (only 150kw or so though - so yes, power also matters), and my experience was that it was quite 'stepped' - ie it seemed to go in stages.  Though I can see the advantage of the gradient, the only time it went to more of a curve/gradient is a little bit in the transition areas and  above 90% SOC ( which I'm not really fussed with being too precise above 90%).</div><div><br></div><div> I'm still a little uncertain how to handle coming from a low SOC as the lower SOC charges a bit slower, so still need to manage somehow the maximum power available to the charger (which I possibly have as a metric - I should probably include that in the logging and in the calculation, and yes, that might end up being a better input into the algorithm).</div><div><br></div><div>The way I have it working (btw) means that a higher voltage architecture defaults to the lower voltage curve (line) if a higher step isn't there. So where the curves (lines) converge I don't double up on data in the table.  The AC Charger seems to be pretty constant right up to 90% for example.</div></div><div><br></div><div>//.ichael</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, 30 Dec 2022 at 02:50, Michael Balzer <<a href="mailto:dexter@expeedo.de" target="_blank">dexter@expeedo.de</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Michael,<br>
<br>
while it makes sense to add a general standard utility for this, I don't <br>
think the Hyundai/Kia code is a good candidate for this.<br>
<br>
I find it confusing you need to take the voltage into account rather <br>
than the power. Also, the "WattHours" numbers don't seem to make sense <br>
-- why would you have different capacities in the same SOC differences, <br>
and why depending on the voltage? Also, the calculator seems to need / <br>
be based on rectangular steps instead of an actual charge curve?<br>
<br>
A more natural generalized definition would in my opinion be based on <br>
modeling the actual curve, like the one I've implemented for the VW Up, <br>
see `OvmsVehicleVWeUp::CalcChargeTime()`. That takes the charge curve as <br>
direct (SOC → max power) coordinates, just adding the section gradients <br>
as a speed optimization (float divisions are very CPU intense on the ESP32):<br>
<br>
   struct {<br>
     int soc;  float pwr;    float grd;<br>
   } ccurve[] = {<br>
     {     0,       30.0,    (32.5-30.0) / ( 30-  0) },<br>
     {    30,       32.5,    (26.5-32.5) / ( 55- 30) },<br>
     {    55,       26.5,    (18.5-26.5) / ( 76- 55) },<br>
     {    76,       18.5,    (11.0-18.5) / ( 81- 76) },<br>
     {    81,       11.0,    ( 6.5-11.0) / ( 91- 81) },<br>
     {    91,        6.5,    ( 3.0- 6.5) / (100- 91) },<br>
     {   100,        3.0,    0                       },<br>
   };<br>
<br>
But maybe some other vehicle already has an even more generalized & easy <br>
to use approach. My function lacks temperature compensation, but <br>
normally delivers sufficiently close results.<br>
<br>
Regards,<br>
Michael<br>
<br>
<br>
Am 29.12.22 um 13:02 schrieb Michael Geddes:<br>
> Hi,<br>
><br>
> There's a remaining charge calculator currently in the hyundai base <br>
> class which I have improved upon.  This configuration allows for the <br>
> charge voltage to be part of the equation.  This is the example from <br>
> data I collected with the ioniq 5 on 800v charging .. and I've written <br>
> a plugin so I can get more charge profile information at other charge <br>
> voltages.<br>
><br>
> I'm happy to leave it in the Ioniq 5 code, but was wondering if I <br>
> should put the implementation in a common area.. vehicle.h maybe?<br>
><br>
> //.ichael<br>
><br>
> ---8<----------------------<br>
><br>
> // Charging profile<br>
> //  - Must be from lowest to highest to%.<br>
> //  - Higher voltages must come before lower voltages for the same to%<br>
> charging_step_t ioniq5_chargesteps[] = {<br>
> // voltage, to%, WattHours<br>
>   { 750,     10, 100000 },<br>
>   { 750,     25, 190000 },<br>
>   { 750,     45, 220000 },<br>
>   { 750,     75, 120000 },<br>
>   { 750,     80,  80000 },<br>
>   { 400,     85,  60000 },<br>
>   { 400,     90,  40000 },<br>
>   { 100,     90,   1100 },<br>
>   { 400,     95,  25000 },<br>
>   { 100,     95,   7400 },<br>
>   { 100,    100,   7200 },<br>
>   { 0, 0, 0 },<br>
> };<br>
><br>
> int CalcRemainingChargeMins(int chargeVolt, float chargespeed, int <br>
> fromSoc, int toSoc, int batterySize, charging_step_t charge_steps[])<br>
> {<br>
><br>
> _______________________________________________<br>
> OvmsDev mailing list<br>
> <a href="mailto:OvmsDev@lists.openvehicles.com" target="_blank">OvmsDev@lists.openvehicles.com</a><br>
> <a href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev" rel="noreferrer" target="_blank">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a><br>
<br>
-- <br>
Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal<br>
Fon 02333 / 833 5735 * Handy 0176 / 206 989 26<br>
<br>
_______________________________________________<br>
OvmsDev mailing list<br>
<a href="mailto:OvmsDev@lists.openvehicles.com" target="_blank">OvmsDev@lists.openvehicles.com</a><br>
<a href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev" rel="noreferrer" target="_blank">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a><br>
</blockquote></div></div>
</blockquote></div></div>
</blockquote></div></div>