Electric Car - Need help creating rules based on logs

Hi,
I have an IONIQ electric car that does not have any SIM inside, good and bad. So i managed to manipulate the charging cable to send voltage, current, kwh, and power via MQTT + if the charger is “online”. Looks like this:
image

I have two rules i try to achieve but i have no clue how to do it smart and not end up with multiple variables that maybe i do not need as M4 may have some rule-support inside that i do not know.

I would love to detect charging start…so when either LWT changes from “Connection lost” to “connected” OR Power changes from 0W to something bigger than 1000W. If this event happens, I need to store the current EnergyImport (as this never goes to 0 and increases with every charging procedure). When it stops it should build the difference of the energy at start and send me a notification (email,…whatevery).
The Energy-Difference should be shown in an additional item i guess…like EnergyImpLast.

The second rule should trigger if the charging is still ongoing (not yet stopped due to Power going back from >1000W to 0W…and “Connection Lost” which means somebody unplugged the car…

Sorry if it looks that i’m too lazy to do it myself, but to me it looks complex and i have no real clue how i could do it…Thanks for any helping hand to do it most efficient with less overhead in terms of lines/variables needed.

Norbert

Your plans look reasonable.

Another way to detect charging start / end would be to look for current changing from / to zero.

Does, a bit.

Make complex things by taking small steps. Making this efforts avoids the lazy look.

Try to make rules to detect charging start / stop. Then afterwards, can work on the energy calc part.

Hi Norbert,
This is not a simple rule you want to write.

For the trigger look at: https://www.openhab.org/docs/configuration/rules-dsl.html#rule-triggers

For the energy over time you could look at: Rule for Total energy consumed in kWh for day or Best practice for handling energy consumption in a single cummulative value?

Search the forum there are more…

Have a go at writing your rule, if you get stuck, post your code and we’ll help. We will not write your code for you. You ask for a helping hand.

sure, current translates into the same…

hm…going simple steps…i would have to check with every update if now POWER is >1000W. I would have a variable named ChargeState that goes True when this happens…if boolean exists?

And it goes False if it was true and power suddenly is Zero again.

For the delivered energy part i’m more unsure, do i really need a StartEnergy variable that is stored when the True is set…and somehow subtract from the EnergyImp-State when finished…I guess so/or it needs to be like that if not a more simple way is possible. I do not want to have too many variables that nobody needs except for these calculations…Is there any of these “since…” calculations possible that are done automatically?

Kind Regards

As already suggested, look at what rule triggers are available.
when X > NN is not.
You can simulate with
when X changed
then
if (X > NN)

All rather depends what data you get from your charger.
I’d be investigating if this kind of thing was practical
when chargeCurrent changed from 0

In usual openHAB terms, you might have a Switch type Item called “charging” and turn it on and off.

Sure, why not.

They cost you nothing. If you store important data in Items you can exploit other openHAB features like for example persistence, so that they can survive a system reboot.

Just doing the first detection part right now, most of my rules are done with “received update”. you suggest “changed”. e.g. Item EVchargePower received update vs. changed

From what i understand “received update” would trigger even if the value stays the same, and changed will only be triggered if the value itself really changed…so if the new value is the same as the old, the rule would not be triggered.

Is that true?
because i would guess that received update is more safe in that it would not take into consideration that the value itself needs to be different…

OnTOP: one more question:
if the energy_previous has to be stored…is this declared within the rule itself or on a global level…or does local/global not exist for rules…so wherever you define them, they are available everywhere?

Please have a short critical view on my first part, does it look OK/efficient/stable…thanks!

Switch Car_Charging "CarCharging"
Number EVchargeMeter "EVcharge Meter"

rule "ChargeStart Detection"
when
    Item EVchargePower received update
then
    if ((EVchargePower > 500) && Car_Charging.state == OFF &&) 
	   { Car_Charging.sendCommand(ON)
	     EVchargeMeter.postUpdate(EVchargeWhImp.state)
		}
end

This is what I came up with for gathering charging data for one of our EVs. This uses the new rule engine, Jython and the helper libraries. The outlet that the car plugs into (just 120V) is also used to charge batteries for the lawn mower, weed eater, chainsaw, etc., so there’s logic built in to detect what is charging.

We have Alexa enabled devices and the OH skill. To start a charge, we just say something like, ‘Echo, set Full Charge Mileage to 5.5’. If the car was unplugged but not completely charged, then ‘Echo, set Partial Charge Mileage to 55.5’. I have notifications setup, so after a full charge mileage is set, you will get the stats of the last drive and also when charging (car or battery) is complete.

Hope this helps!

ITEMS
I haven’t gotten everything migrated to UoM yet :blush:

Number:Length  Volt_Mileage                "Full Charge Mileage [%.1f mi]"                <text>              (gBattery_Charging)    {alexa="RangeController.rangeValue" [supportedRange="0:500:1"]}
Number:Length  Volt_Mileage_Partial        "Partial Charge Mileage [%.1f mi]"             <text>              (gBattery_Charging)    {alexa="RangeController.rangeValue" [supportedRange="0:500:1"]}
Number:Length  Volt_Mileage_Total          "Volt Mileage (total) [%.1f mi]"               <text>              (gBattery_Charging)
Number:Energy  Volt_Energy                 "Volt Energy (last charge) [%.0f kWh]"          <energy>            (gBattery_Charging)
Number:Energy  Volt_Energy_Total           "Volt Energy (total) [%.0f kWh]"                <energy>            (gBattery_Charging)
Number  Volt_Efficiency             "Volt Efficiency (last charge) [%.0f Wh/mi]"  <energy>            (gBattery_Charging)
Number  Volt_Efficiency_Average     "Volt Efficiency (average) [%.0f Wh/mi]"      <energy>            (gBattery_Charging)
Number  Volt_Cost                   "Volt Cost (last charge) [$%.2f]"               <text>              (gBattery_Charging)
Number  Volt_Cost_Total             "Volt Cost (total) [$%.2f]"                     <text>              (gBattery_Charging)
Number  Volt_Cost_PerMile           "Volt Cost/Mile (last charge) [$%.3f/mi]"     <text>              (gBattery_Charging)
Number  Volt_Cost_PerMile_Average   "Volt Cost/Mile (average) [$%.3f/mi]"         <text>              (gBattery_Charging)

Switch	Outlet9	                        "Car Charger [%s]"	                                        <poweroutlet-us>	(gGarageAttached,gOutlet,gBattery_Charging)                                             {channel="zwave:device:55555:node225:switch_binary", alexa="PowerController.powerState" [category="SMARTPLUG"]}
Number:Power	Outlet9_Power	                "Car Charger: Power [%.0f W]"	                            <energy>	        (gGarageAttached,gOutlet,gBattery_Charging)		                                        {channel="zwave:device:55555:node225:meter_watts"}
Number:Energy	Outlet9_Energy	                "Car Charger: Energy [%.0f kWh]"	                        <energy>	        (gGarageAttached,gOutlet,gBattery_Charging)		                                        {channel="zwave:device:55555:node225:meter_kwh"}

SCRIPT

from threading import Timer

from core.rules import rule
from core.triggers import when
from core.actions import PersistenceExtensions

from personal.utils import notification

from org.joda.time import DateTime

ENERGY_COST_PER_KWH = 0.1238
CHARGER_TIMER_ATTACHED = None# Outlet9

@rule("Power: Battery charging")
@when("Item Outlet9 changed to OFF")
def battery_charging(event):
    battery_charging.log.debug("Battery charging: {}: start".format(event.itemState))
    date_of_last_full_charge = DateTime(PersistenceExtensions.previousState(ir.getItem("Volt_Mileage"), True).timestamp.time)
    date_of_last_partial_charge = DateTime(PersistenceExtensions.previousState(ir.getItem("Volt_Mileage_Partial")).timestamp.time)
    last_charge_was_full = date_of_last_full_charge.isAfter(date_of_last_partial_charge)

    charge_start = date_of_last_full_charge if last_charge_was_full else date_of_last_partial_charge
    battery_charging.log.debug("Battery charging: charge_start=[{}]".format(charge_start))
    if PersistenceExtensions.maximumSince(ir.getItem("Outlet9_Power"), charge_start).state > DecimalType(900):
        message = "Car charging has completed"
    else:
        message = "Battery charging has completed"
    notification("Battery charging", message)

@rule("Power: Battery charging monitor")
@when("Item Outlet9_Power changed")
def battery_charging_monitor(event):
    #battery_charging_monitor.log.debug("Battery charging monitor: {}: start".format(event.itemState))
    global CHARGER_TIMER_ATTACHED
    if items["Outlet9"] == ON and event.itemState <= QuantityType("8 W") and event.oldItemState <= QuantityType("8 W"):
        if CHARGER_TIMER_ATTACHED is None or str(CHARGER_TIMER_ATTACHED.getState()) == "TERMINATED":
            CHARGER_TIMER_ATTACHED = Timer(600, lambda: events.sendCommand("Outlet9", "OFF"))
            CHARGER_TIMER_ATTACHED.start()
            battery_charging_monitor.log.debug("Battery charging monitor: Started battery charging turn off timer: Outlet9_Power=[{}], oldItemState=[{}]".format(event.itemState, event.oldItemState))
    elif CHARGER_TIMER_ATTACHED is not None and str(CHARGER_TIMER_ATTACHED.getState()) == "TIMED_WAITING":
        CHARGER_TIMER_ATTACHED.stop()
        battery_charging_monitor.log.debug("Battery charging monitor: Cancelled battery charging turn off timer: Outlet9_Power=[{}], oldItemState=[{}]".format(event.itemState, event.oldItemState))

@rule("Power: Volt mileage received update")
@when("Item Volt_Mileage received update")
def volt_mileage(event):
    volt_mileage.log.debug("Volt mileage change: {}: start".format(event.itemState))
    # these calculations are all for the last charge
    date_of_last_full_charge = DateTime(PersistenceExtensions.previousState(ir.getItem("Volt_Mileage"), True).timestamp.time)
    date_of_last_partial_charge = DateTime(PersistenceExtensions.previousState(ir.getItem("Volt_Mileage_Partial")).timestamp.time)
    last_charge_was_full = date_of_last_full_charge.isAfter(date_of_last_partial_charge)

    volt_mileage.log.debug("Volt mileage change: date_of_last_full_charge=[{}], date_of_last_partial_charge=[{}], last_charge_was_full=[{}]".format(date_of_last_full_charge, date_of_last_partial_charge, last_charge_was_full))
    previous_mileage = PersistenceExtensions.previousState(ir.getItem("Volt_Mileage" if last_charge_was_full else "Volt_Mileage_Partial"), True).state.floatValue()
    volt_mileage.log.debug("Volt mileage change: previous_mileage=[{}]".format(previous_mileage))
    total_mileage = items["Volt_Mileage_Total"].floatValue() + previous_mileage
    events.sendCommand("Volt_Mileage_Total", str(total_mileage))
    volt_mileage.log.debug("Volt mileage change: Volt_Mileage_Total=[{}]".format(total_mileage))

    energy_start = PersistenceExtensions.historicState(ir.getItem("Outlet9_Energy"), date_of_last_full_charge).state.floatValue()
    volt_mileage.log.debug("Volt mileage change: energy_start=[{}]".format(energy_start))
    energy_now = items["Outlet9_Energy"].floatValue()
    volt_mileage.log.debug("Volt mileage change: energy_now=[{}]".format(energy_now))
    energy_drive = energy_now - energy_start
    volt_mileage.log.debug("Volt mileage change: energy_drive=[{}]".format(energy_drive))
    events.sendCommand("Volt_Energy", str(energy_drive))
    events.sendCommand("Volt_Energy_Total", str(items["Volt_Energy_Total"].floatValue() + energy_drive))

    global ENERGY_COST_PER_KWH
    energy_drive_cost = ENERGY_COST_PER_KWH * energy_drive
    volt_mileage.log.debug("Volt mileage change: energy_drive_cost=[{:.3f}]".format(energy_drive_cost))
    events.sendCommand("Volt_Cost", str(energy_drive_cost))
    events.sendCommand("Volt_Cost_Total", str(items["Volt_Energy_Total"].floatValue() + energy_drive_cost))

    cost_per_mile = energy_drive_cost / previous_mileage
    volt_mileage.log.debug("Volt mileage change: cost_per_mile=[{:.3f}]".format(cost_per_mile))
    events.sendCommand("Volt_Cost_PerMile", str(cost_per_mile))

    cost_per_mile_ave = (items["Volt_Cost_PerMile_Average"].floatValue() + cost_per_mile) / 2
    volt_mileage.log.debug("Volt mileage change: cost_per_mile_ave=[{:.1f}]".format(cost_per_mile_ave))
    events.sendCommand("Volt_Cost_PerMile_Average", str(cost_per_mile_ave))

    efficiency = 1000 * energy_drive / previous_mileage
    volt_mileage.log.debug("Volt mileage change: efficiency=[{:.0f}]".format(efficiency))
    events.sendCommand("Volt_Efficiency", str(efficiency))

    efficiency_ave = (items["Volt_Efficiency_Average"].floatValue() + efficiency) / 2
    volt_mileage.log.debug("Volt mileage change: efficiency_ave=[{:.0f}]".format(efficiency_ave))
    events.sendCommand("Volt_Efficiency_Average", str(efficiency_ave))

    message = "Previous Volt charge report: {:.1f} cents per mile, {:.0f} Watt hours per mile".format(100 * cost_per_mile, efficiency)
    volt_mileage.log.debug("Volt mileage change: efficiency_ave=[{}]".format(efficiency_ave))
    notification("Volt mileage change", message)
    events.sendCommand("Outlet9", "ON")

Yes. This is in the docs.
I understood you wanted to capture the kWh reading at the start of a charge cycle, and then capture again at the completion.
If that is what you intend, choose triggers with care.

Thanks for all the input. Impressive - but too complex for my current intention.

I now successfully finished the rules required - and they seem to work when in dryrun mode.

// ***************************************************************************************************
rule "ChargeStart Detection"
when
    Item EVchargePower received update
then
    if ((EVchargePower.state > 1800) && Car_Charging.state == OFF)
           { Car_Charging.sendCommand(ON)
             EVchargeStartMeter.postUpdate(EVchargeWhImp.state)
           }
end

// ***************************************************************************************************
rule "ChargeStop Detection"
when
    Item EVchargePower received update
then
    if (Car_Charging.state == ON) { EVsessionMeter.postUpdate((EVchargeWhImp.state as DecimalType) - (EVchargeStartMeter.state as DecimalType)) }

    if ((EVchargePower.state < 10) && Car_Charging.state == ON)
           { Car_Charging.sendCommand(OFF)
             sendPushoverMessage(pushoverBuilder("Charging finished | " + EVsessionMeter.state + "kWh").withTitle("IONIQ"))
           }
end

// ***************************************************************************************************
1 Like

I’ll note that both rules have the same trigger.
That’s fine, but also a clue that you could combine into one rule.
Both rules revolve around if() - for combining, you might find an if-else structure useful

if (xx) {
   // do something
} else if (yy) {
   // do something different
}

I’ll note further, the trigger is for received update. As you noted earlier, updates to the same value are perfectly possible and sensible. As such, your rules could run frequently without anything changing (and so no useful work to be done).
If your rule only needs to pay attention if something actually changed, there is an alternative rule trigger available for changed

One last note; your EVchargeStartMeter Item will get updated with every pass through the rule. Did you intend to only capture that value at the start of the charge cycle? You could test to see if the “charging” Item was already ON - if it is, you’ve already done the start capture.

Thanks!

You’re right - will combine in a single rule instead of two.

The EVchargeStartMeter will only be updated once i hope. I guess you mean the EVsessionMeter item?
That is intended to see the currently charged amount per session always up to date…so how much the car got already into the battery. I hope i understand it correctly. Thanks!!!

btw, is there a way to have a switch item also visible in my sitemap but the user is not allowed to change it by themselves? This would apply for my Car_Charging switch item.

Yes do this:

Text item=mySwitchItem

are you sure, i tried in the sitemap to change switch item = …to… Text item= and in the GUI you now see this paperscript symbol plus nothing at all on the right side. Did i do something wrong?

Yes I am sure
Please post the item definition
Thanks

hm…
this is the item definition:

Switch Car_Charging "CarCharging"

Here we have the sitemap entry:

Text item=Car_Charging

Hope i did not do something too stupid…

You get the little paper icon by default, because you haven’t specified any icon. Small problem.

Bigger problem, you haven’t asked for any display of the Item state, so you don’t get one. Try

Switch Car_Charging "CarCharging [%s]"
1 Like

Thanks, it works that way - but now there is no switch anymore but a simple text field. What i more was looking for is a way to still see the switch but being not able to change it manually.

Do you know if this is somehow possible?

Not possible in the sitemap based UIs. There is a fairly comprehensive set of widgets, but no means to make custom widgets.

The HABpanel UI allows extensive custom widgets.

In the sitemap usage, you can of course use icons to reflect the state (but they appear on the left). You can colour the text, label and/or state areas. You can make the line disappear when it is in one state and reappear in another. You can use a MAP transform on the Item state to substitute fancy characters into the right hand area (and also colour them).
But you can’t put an arbitrary image on the right hand side, unless you can encode it as text.